Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

401 line
9.7 KiB

  1. import * as React from 'react';
  2. import {
  3. render,
  4. screen,
  5. cleanup,
  6. fireEvent,
  7. } from '@testing-library/react';
  8. import userEvent from '@testing-library/user-event';
  9. import {
  10. vi,
  11. describe,
  12. it,
  13. expect,
  14. afterEach,
  15. } from 'vitest';
  16. import matchers from '@testing-library/jest-dom/matchers';
  17. import {
  18. FileSelectBox,
  19. FileSelectBoxDerivedElement,
  20. DELETE_KEYS, SELECT_KEYS,
  21. } from '.';
  22. expect.extend(matchers);
  23. const userEventDrop = async (
  24. dropZoneElement: HTMLElement,
  25. fileOrFiles: File | File[],
  26. targetInputElement: HTMLInputElement,
  27. ) => {
  28. const dummyInput = window.document.createElement('input');
  29. dummyInput.type = 'file';
  30. await userEvent.upload(dummyInput, fileOrFiles);
  31. // targeting change event on the input element
  32. await userEvent.upload(targetInputElement, fileOrFiles);
  33. fireEvent.drop(dropZoneElement, {
  34. dataTransfer: {
  35. files: dummyInput.files,
  36. },
  37. });
  38. };
  39. const userEventPaste = async (
  40. dropZoneElement: HTMLElement,
  41. fileOrFiles: File | File[],
  42. ) => {
  43. const dummyInput = window.document.createElement('input');
  44. dummyInput.type = 'file';
  45. await userEvent.upload(dummyInput, fileOrFiles);
  46. // targeting change event on the input element
  47. await userEvent.upload(dropZoneElement, fileOrFiles);
  48. fireEvent.paste(dropZoneElement, {
  49. clipboardData: {
  50. files: dummyInput.files,
  51. },
  52. });
  53. };
  54. describe('FileSelectBox', () => {
  55. afterEach(() => {
  56. cleanup();
  57. });
  58. it('renders a file input', () => {
  59. render(
  60. <FileSelectBox />,
  61. );
  62. const input = screen.getByTestId('input');
  63. expect(input).toBeInTheDocument();
  64. });
  65. it('renders a border', () => {
  66. render(
  67. <FileSelectBox
  68. border
  69. />,
  70. );
  71. const border = screen.getByTestId('border');
  72. expect(border).toBeInTheDocument();
  73. });
  74. it('renders a block component', () => {
  75. render(
  76. <FileSelectBox
  77. block
  78. />,
  79. );
  80. const root = screen.getByTestId('root');
  81. expect(root).toHaveClass('flex w-full');
  82. });
  83. it('renders a label', () => {
  84. render(
  85. <FileSelectBox
  86. label="foo"
  87. />,
  88. );
  89. const textbox = screen.getByLabelText('foo');
  90. expect(textbox).toBeInTheDocument();
  91. const label = screen.getByTestId('label');
  92. expect(label).toHaveTextContent('foo');
  93. });
  94. it('renders a hidden label', () => {
  95. render(
  96. <FileSelectBox
  97. label="foo"
  98. hiddenLabel
  99. />,
  100. );
  101. const textbox = screen.getByLabelText('foo');
  102. expect(textbox).toBeInTheDocument();
  103. const label = screen.queryByTestId('label');
  104. expect(label).toBeInTheDocument();
  105. expect(label).toHaveClass('sr-only');
  106. });
  107. it('handles a change event', async () => {
  108. const onChange = vi.fn();
  109. render(
  110. <FileSelectBox
  111. onChange={onChange}
  112. />,
  113. );
  114. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  115. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  116. await userEvent.upload(input, file);
  117. expect(onChange).toBeCalledTimes(1);
  118. });
  119. it('handles a blur event', async () => {
  120. const onBlur = vi.fn();
  121. render(
  122. <FileSelectBox
  123. onBlur={onBlur}
  124. />,
  125. );
  126. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  127. input.focus();
  128. input.blur();
  129. expect(onBlur).toBeCalledTimes(1);
  130. });
  131. describe('when enhanced', () => {
  132. it('renders a hint', () => {
  133. render(
  134. <FileSelectBox
  135. enhanced
  136. hint="foo"
  137. />,
  138. );
  139. const hint = screen.getByTestId('hint');
  140. expect(hint).toBeInTheDocument();
  141. });
  142. it('renders a preview for a single file', async () => {
  143. render(
  144. <FileSelectBox
  145. enhanced
  146. />,
  147. );
  148. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  149. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  150. await userEvent.upload(input, file);
  151. expect(input.files).toHaveLength(1);
  152. const preview = screen.getByTestId('preview');
  153. expect(preview).toBeInTheDocument();
  154. });
  155. it('renders a preview for a single file without name', async () => {
  156. render(
  157. <FileSelectBox
  158. enhanced
  159. />,
  160. );
  161. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  162. const file = new Blob(['foo'], {type: 'text/plain'});
  163. await userEvent.upload(input, file as unknown as File);
  164. expect(input.files).toHaveLength(1);
  165. const preview = screen.getByTestId('preview');
  166. expect(preview).toBeInTheDocument();
  167. });
  168. it('renders a preview for a single file without name', async () => {
  169. render(
  170. <FileSelectBox
  171. enhanced
  172. />,
  173. );
  174. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  175. const { size: _size, ...partialFile } = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  176. await userEvent.upload(input, partialFile as unknown as File);
  177. expect(input.files).toHaveLength(1);
  178. const preview = screen.getByTestId('preview');
  179. expect(preview).toBeInTheDocument();
  180. });
  181. it('renders a preview for multiple files', async () => {
  182. render(
  183. <FileSelectBox
  184. enhanced
  185. multiple
  186. />,
  187. );
  188. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  189. const files = [
  190. new File(['foo'], 'foo.txt', {type: 'text/plain'}),
  191. new File(['bar'], 'bar.txt', {type: 'text/plain'}),
  192. ];
  193. await userEvent.upload(input, files);
  194. expect(input.files).toHaveLength(files.length);
  195. const preview = screen.getByTestId('preview');
  196. expect(preview).toBeInTheDocument();
  197. });
  198. it('renders a preview for multiple files without names', async () => {
  199. render(
  200. <FileSelectBox
  201. enhanced
  202. multiple
  203. />,
  204. );
  205. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  206. const files = [
  207. new Blob(['foo'], {type: 'text/plain'}),
  208. new Blob(['bar'], {type: 'text/plain'}),
  209. ];
  210. await userEvent.upload(input, files as unknown as File[]);
  211. expect(input.files).toHaveLength(files.length);
  212. const preview = screen.getByTestId('preview');
  213. expect(preview).toBeInTheDocument();
  214. });
  215. it('renders actions when at least one file is selected', async () => {
  216. render(
  217. <FileSelectBox
  218. enhanced
  219. multiple
  220. />,
  221. );
  222. const input = screen.getByTestId('input');
  223. const files = [
  224. new File(['foo'], 'foo.txt', {type: 'text/plain'}),
  225. new File(['bar'], 'bar.txt', {type: 'text/plain'}),
  226. ];
  227. await userEvent.upload(input, files);
  228. const actions = screen.getByTestId('actions');
  229. expect(actions).toBeInTheDocument();
  230. });
  231. it('clears selected files through the clear button', async () => {
  232. render(
  233. <FileSelectBox
  234. enhanced
  235. />,
  236. );
  237. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  238. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  239. await userEvent.upload(input, file);
  240. const clearButton = screen.getByTestId('clear');
  241. await userEvent.click(clearButton);
  242. expect(input.files).toHaveLength(0);
  243. });
  244. it.each(DELETE_KEYS)('clears selected files through pressing %s key', async (key) => {
  245. render(
  246. <FileSelectBox
  247. enhanced
  248. />,
  249. );
  250. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  251. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  252. await userEvent.upload(input, file);
  253. input.focus();
  254. await userEvent.keyboard(`{${key}}`);
  255. expect(input.files).toHaveLength(0);
  256. });
  257. it('ignores other key presses when input is focused', async () => {
  258. render(
  259. <FileSelectBox
  260. enhanced
  261. />,
  262. );
  263. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  264. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  265. await userEvent.upload(input, file);
  266. input.focus();
  267. await userEvent.keyboard('a');
  268. expect(input.files).toHaveLength(1);
  269. });
  270. it.each(SELECT_KEYS)('opens picker on pressing %s when input is in focus', async (key) => {
  271. render(
  272. <FileSelectBox
  273. enhanced
  274. />,
  275. );
  276. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  277. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  278. await userEvent.upload(input, file);
  279. input.focus();
  280. await userEvent.keyboard(`{${key}}`);
  281. // how to assert?
  282. });
  283. it('opens picker when files are previously selected (showPicker)', async () => {
  284. render(
  285. <FileSelectBox
  286. enhanced
  287. />,
  288. );
  289. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  290. const showPicker = vi.fn();
  291. input.showPicker = showPicker;
  292. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  293. await userEvent.upload(input, file);
  294. const clearButton = screen.getByTestId('reselect');
  295. await userEvent.click(clearButton);
  296. expect(showPicker).toBeCalledTimes(1);
  297. });
  298. it('opens picker when files are previously selected (no showPicker)', async () => {
  299. render(
  300. <FileSelectBox
  301. enhanced
  302. />,
  303. );
  304. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  305. const showPicker = vi.spyOn(input, 'click');
  306. const file = new File(['foo'], 'foo.txt', {type: 'text/plain'});
  307. await userEvent.upload(input, file);
  308. const clearButton = screen.getByTestId('reselect');
  309. await userEvent.click(clearButton);
  310. expect(showPicker).toBeCalledTimes(1);
  311. });
  312. it('accepts drop files', async () => {
  313. const onChange = vi.fn();
  314. render(
  315. <FileSelectBox
  316. enhanced
  317. onChange={onChange}
  318. />,
  319. );
  320. const root = screen.getByTestId('root');
  321. const file = new File(['foo'], 'foo.txt', { type: 'text/plain' });
  322. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  323. await userEventDrop(root, file, input);
  324. expect(onChange).toBeCalledTimes(1);
  325. });
  326. it('accepts pasted files', async () => {
  327. const onChange = vi.fn();
  328. render(
  329. <FileSelectBox
  330. enhanced
  331. onChange={onChange}
  332. />,
  333. );
  334. const input = screen.getByTestId('input') as FileSelectBoxDerivedElement;
  335. const file = new File(['foo'], 'foo.txt', { type: 'text/plain' });
  336. await userEventPaste(input, file);
  337. expect(onChange).toBeCalledTimes(1);
  338. });
  339. });
  340. });