react-dropzone 15.0.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +3 -3
  2. package/dist/index.cjs +1077 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +1046 -2
  5. package/dist/index.js.map +1 -0
  6. package/package.json +62 -161
  7. package/typings/tests/tsconfig.json +12 -11
  8. package/.babelrc.js +0 -28
  9. package/.codeclimate.yml +0 -15
  10. package/.editorconfig +0 -13
  11. package/.eslintignore +0 -3
  12. package/.eslintrc +0 -37
  13. package/.gitpod.yml +0 -6
  14. package/.husky/commit-msg +0 -5
  15. package/.husky/pre-commit +0 -5
  16. package/.nvmrc +0 -1
  17. package/.releaserc.json +0 -27
  18. package/CHANGELOG.md +0 -9
  19. package/commitlint.config.js +0 -1
  20. package/dist/es/index.js +0 -1051
  21. package/dist/es/package.json +0 -1
  22. package/dist/es/utils/index.js +0 -366
  23. package/examples/.eslintrc +0 -5
  24. package/examples/accept/README.md +0 -144
  25. package/examples/basic/README.md +0 -70
  26. package/examples/class-component/README.md +0 -45
  27. package/examples/drag-overlay/README.md +0 -132
  28. package/examples/events/README.md +0 -164
  29. package/examples/file-dialog/README.md +0 -90
  30. package/examples/forms/README.md +0 -69
  31. package/examples/maxFiles/README.md +0 -58
  32. package/examples/no-jsx/README.md +0 -32
  33. package/examples/pintura/README.md +0 -146
  34. package/examples/plugins/README.md +0 -55
  35. package/examples/previews/README.md +0 -86
  36. package/examples/styling/README.md +0 -126
  37. package/examples/theme.css +0 -37
  38. package/examples/validator/README.md +0 -68
  39. package/rollup.config.js +0 -33
  40. package/src/.eslintrc +0 -37
  41. package/src/__snapshots__/index.spec.js.snap +0 -3
  42. package/src/index.spec.js +0 -3838
  43. package/src/utils/index.spec.js +0 -625
  44. package/styleguide.config.js +0 -107
  45. package/testSetup.js +0 -8
  46. package/typings/.eslintrc +0 -28
  47. /package/src/{index.js → index.jsx} +0 -0
package/src/index.spec.js DELETED
@@ -1,3838 +0,0 @@
1
- /* eslint react/prop-types: 0, jsx-a11y/label-has-for: 0 */
2
- import React, { createRef } from "react";
3
- import { act, cleanup, fireEvent, render } from "@testing-library/react";
4
- import { renderHook } from "@testing-library/react-hooks";
5
- import { fromEvent } from "file-selector";
6
- import * as utils from "./utils";
7
- import Dropzone, { useDropzone } from "./index";
8
-
9
- describe("useDropzone() hook", () => {
10
- let files;
11
- let images;
12
-
13
- beforeEach(() => {
14
- files = [createFile("file1.pdf", 1111, "application/pdf")];
15
- images = [
16
- createFile("cats.gif", 1234, "image/gif"),
17
- createFile("dogs.gif", 2345, "image/jpeg"),
18
- ];
19
- });
20
-
21
- afterEach(cleanup);
22
-
23
- describe("behavior", () => {
24
- it("renders the root and input nodes with the necessary props", () => {
25
- const { container } = render(
26
- <Dropzone>
27
- {({ getRootProps, getInputProps }) => (
28
- <div {...getRootProps()}>
29
- <input {...getInputProps()} />
30
- </div>
31
- )}
32
- </Dropzone>
33
- );
34
- expect(container.innerHTML).toMatchSnapshot();
35
- });
36
-
37
- it("sets {accept} prop on the <input>", () => {
38
- const accept = {
39
- "image/jpeg": [],
40
- };
41
- const { container } = render(
42
- <Dropzone accept={accept}>
43
- {({ getRootProps, getInputProps }) => (
44
- <div {...getRootProps()}>
45
- <input {...getInputProps()} />
46
- </div>
47
- )}
48
- </Dropzone>
49
- );
50
-
51
- expect(container.querySelector("input")).toHaveAttribute(
52
- "accept",
53
- "image/jpeg"
54
- );
55
- });
56
-
57
- it("updates {multiple} prop on the <input> when it changes", () => {
58
- const { container, rerender } = render(
59
- <Dropzone
60
- accept={{
61
- "image/jpeg": [],
62
- }}
63
- >
64
- {({ getRootProps, getInputProps }) => (
65
- <div {...getRootProps()}>
66
- <input {...getInputProps()} />
67
- </div>
68
- )}
69
- </Dropzone>
70
- );
71
-
72
- expect(container.querySelector("input")).toHaveAttribute(
73
- "accept",
74
- "image/jpeg"
75
- );
76
-
77
- rerender(
78
- <Dropzone
79
- accept={{
80
- "image/png": [],
81
- }}
82
- >
83
- {({ getRootProps, getInputProps }) => (
84
- <div {...getRootProps()}>
85
- <input {...getInputProps()} />
86
- </div>
87
- )}
88
- </Dropzone>
89
- );
90
-
91
- expect(container.querySelector("input")).toHaveAttribute(
92
- "accept",
93
- "image/png"
94
- );
95
- });
96
-
97
- it("sets {multiple} prop on the <input>", () => {
98
- const { container } = render(
99
- <Dropzone multiple>
100
- {({ getRootProps, getInputProps }) => (
101
- <div {...getRootProps()}>
102
- <input {...getInputProps()} />
103
- </div>
104
- )}
105
- </Dropzone>
106
- );
107
-
108
- expect(container.querySelector("input")).toHaveAttribute("multiple");
109
- });
110
-
111
- it("updates {multiple} prop on the <input> when it changes", () => {
112
- const { container, rerender } = render(
113
- <Dropzone multiple={false}>
114
- {({ getRootProps, getInputProps }) => (
115
- <div {...getRootProps()}>
116
- <input {...getInputProps()} />
117
- </div>
118
- )}
119
- </Dropzone>
120
- );
121
-
122
- expect(container.querySelector("input")).not.toHaveAttribute("multiple");
123
-
124
- rerender(
125
- <Dropzone multiple>
126
- {({ getRootProps, getInputProps }) => (
127
- <div {...getRootProps()}>
128
- <input {...getInputProps()} />
129
- </div>
130
- )}
131
- </Dropzone>
132
- );
133
-
134
- expect(container.querySelector("input")).toHaveAttribute("multiple");
135
- });
136
-
137
- it("sets any props passed to the input props getter on the <input>", () => {
138
- const name = "dropzone-input";
139
- const { container } = render(
140
- <Dropzone multiple>
141
- {({ getRootProps, getInputProps }) => (
142
- <div {...getRootProps()}>
143
- <input {...getInputProps({ name })} />
144
- </div>
145
- )}
146
- </Dropzone>
147
- );
148
-
149
- expect(container.querySelector("input")).toHaveAttribute("name", name);
150
- });
151
-
152
- it("sets any props passed to the root props getter on the root node", () => {
153
- const ariaLabel = "Dropzone area";
154
- const { container } = render(
155
- <Dropzone multiple>
156
- {({ getRootProps, getInputProps }) => (
157
- <div {...getRootProps({ "aria-label": ariaLabel })}>
158
- <input {...getInputProps()} />
159
- </div>
160
- )}
161
- </Dropzone>
162
- );
163
-
164
- expect(container.querySelector("div")).toHaveAttribute(
165
- "aria-label",
166
- ariaLabel
167
- );
168
- });
169
-
170
- it("runs the custom callback handlers provided to the root props getter", async () => {
171
- const event = createDtWithFiles(files);
172
-
173
- const rootProps = {
174
- onClick: jest.fn(),
175
- onKeyDown: jest.fn(),
176
- onFocus: jest.fn(),
177
- onBlur: jest.fn(),
178
- onDragEnter: jest.fn(),
179
- onDragOver: jest.fn(),
180
- onDragLeave: jest.fn(),
181
- onDrop: jest.fn(),
182
- };
183
-
184
- const { container } = render(
185
- <Dropzone>
186
- {({ getRootProps, getInputProps }) => (
187
- <div {...getRootProps(rootProps)}>
188
- <input {...getInputProps()} />
189
- </div>
190
- )}
191
- </Dropzone>
192
- );
193
-
194
- const dropzone = container.querySelector("div");
195
-
196
- fireEvent.click(dropzone);
197
- expect(rootProps.onClick).toHaveBeenCalled();
198
-
199
- fireEvent.focus(dropzone);
200
- fireEvent.keyDown(dropzone);
201
- expect(rootProps.onFocus).toHaveBeenCalled();
202
- expect(rootProps.onKeyDown).toHaveBeenCalled();
203
-
204
- fireEvent.blur(dropzone);
205
- expect(rootProps.onBlur).toHaveBeenCalled();
206
-
207
- await act(() => fireEvent.dragEnter(dropzone, event));
208
- expect(rootProps.onDragEnter).toHaveBeenCalled();
209
-
210
- fireEvent.dragOver(dropzone, event);
211
- expect(rootProps.onDragOver).toHaveBeenCalled();
212
-
213
- fireEvent.dragLeave(dropzone, event);
214
- expect(rootProps.onDragLeave).toHaveBeenCalled();
215
-
216
- await act(() => fireEvent.drop(dropzone, event));
217
- expect(rootProps.onDrop).toHaveBeenCalled();
218
- });
219
-
220
- it("runs the custom callback handlers provided to the input props getter", async () => {
221
- const inputProps = {
222
- onClick: jest.fn(),
223
- onChange: jest.fn(),
224
- };
225
-
226
- const { container } = render(
227
- <Dropzone>
228
- {({ getRootProps, getInputProps }) => (
229
- <div {...getRootProps()}>
230
- <input {...getInputProps(inputProps)} />
231
- </div>
232
- )}
233
- </Dropzone>
234
- );
235
-
236
- const input = container.querySelector("input");
237
-
238
- fireEvent.click(input);
239
- expect(inputProps.onClick).toHaveBeenCalled();
240
-
241
- await act(async () => fireEvent.change(input, { target: { files: [] } }));
242
- expect(inputProps.onChange).toHaveBeenCalled();
243
- });
244
-
245
- it("runs no callback handlers if {disabled} is true", async () => {
246
- const event = createDtWithFiles(files);
247
-
248
- const rootProps = {
249
- onClick: jest.fn(),
250
- onKeyDown: jest.fn(),
251
- onFocus: jest.fn(),
252
- onBlur: jest.fn(),
253
- onDragEnter: jest.fn(),
254
- onDragOver: jest.fn(),
255
- onDragLeave: jest.fn(),
256
- onDrop: jest.fn(),
257
- };
258
-
259
- const inputProps = {
260
- onClick: jest.fn(),
261
- onChange: jest.fn(),
262
- };
263
-
264
- const { container } = render(
265
- <Dropzone disabled>
266
- {({ getRootProps, getInputProps }) => (
267
- <div {...getRootProps(rootProps)}>
268
- <input {...getInputProps(inputProps)} />
269
- </div>
270
- )}
271
- </Dropzone>
272
- );
273
-
274
- const dropzone = container.querySelector("div");
275
-
276
- fireEvent.click(dropzone);
277
- expect(rootProps.onClick).not.toHaveBeenCalled();
278
-
279
- fireEvent.focus(dropzone);
280
- fireEvent.keyDown(dropzone);
281
- expect(rootProps.onFocus).not.toHaveBeenCalled();
282
- expect(rootProps.onKeyDown).not.toHaveBeenCalled();
283
-
284
- fireEvent.blur(dropzone);
285
- expect(rootProps.onBlur).not.toHaveBeenCalled();
286
-
287
- await act(() => fireEvent.dragEnter(dropzone, event));
288
- expect(rootProps.onDragEnter).not.toHaveBeenCalled();
289
-
290
- fireEvent.dragOver(dropzone, event);
291
- expect(rootProps.onDragOver).not.toHaveBeenCalled();
292
-
293
- fireEvent.dragLeave(dropzone, event);
294
- expect(rootProps.onDragLeave).not.toHaveBeenCalled();
295
-
296
- await act(() => fireEvent.drop(dropzone, event));
297
- expect(rootProps.onDrop).not.toHaveBeenCalled();
298
-
299
- const input = container.querySelector("input");
300
-
301
- fireEvent.click(input);
302
- expect(inputProps.onClick).not.toHaveBeenCalled();
303
-
304
- await act(() => fireEvent.change(input));
305
- expect(inputProps.onChange).not.toHaveBeenCalled();
306
- });
307
-
308
- test("{rootRef, inputRef} are exposed", () => {
309
- const { result } = renderHook(() => useDropzone());
310
- const { rootRef, inputRef, getRootProps, getInputProps } = result.current;
311
-
312
- const { container } = render(
313
- <div {...getRootProps()}>
314
- <input {...getInputProps()} />
315
- </div>
316
- );
317
-
318
- expect(container.querySelector("div")).toEqual(rootRef.current);
319
- expect(container.querySelector("input")).toEqual(inputRef.current);
320
- });
321
-
322
- test("<Dropzone> exposes and sets the ref if using a ref object", () => {
323
- const dropzoneRef = createRef();
324
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
325
-
326
- const ui = (
327
- <Dropzone ref={dropzoneRef}>
328
- {({ getRootProps, getInputProps, isFocused }) => (
329
- <div {...getRootProps()}>
330
- <input {...getInputProps()} />
331
- {isFocused && <div id="focus" />}
332
- </div>
333
- )}
334
- </Dropzone>
335
- );
336
-
337
- const { rerender } = render(ui);
338
-
339
- expect(dropzoneRef.current).not.toBeNull();
340
- expect(typeof dropzoneRef.current.open).toEqual("function");
341
-
342
- act(() => dropzoneRef.current.open());
343
- expect(onClickSpy).toHaveBeenCalled();
344
-
345
- rerender(null);
346
-
347
- expect(dropzoneRef.current).toBeNull();
348
- });
349
-
350
- test("<Dropzone> exposes and sets the ref if using a ref fn", () => {
351
- let dropzoneRef;
352
- const setRef = (ref) => (dropzoneRef = ref);
353
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
354
-
355
- const ui = (
356
- <Dropzone ref={setRef}>
357
- {({ getRootProps, getInputProps, isFocused }) => (
358
- <div {...getRootProps()}>
359
- <input {...getInputProps()} />
360
- {isFocused && <div id="focus" />}
361
- </div>
362
- )}
363
- </Dropzone>
364
- );
365
-
366
- const { rerender } = render(ui);
367
-
368
- expect(dropzoneRef).not.toBeNull();
369
- expect(typeof dropzoneRef.open).toEqual("function");
370
-
371
- act(() => dropzoneRef.open());
372
- expect(onClickSpy).toHaveBeenCalled();
373
-
374
- rerender(null);
375
- expect(dropzoneRef).toBeNull();
376
- });
377
-
378
- test("<Dropzone> doesn't invoke the ref fn if it hasn't changed", () => {
379
- const setRef = jest.fn();
380
-
381
- const { rerender } = render(
382
- <Dropzone ref={setRef}>
383
- {({ getRootProps, getInputProps }) => (
384
- <div {...getRootProps()}>
385
- <input {...getInputProps()} />
386
- </div>
387
- )}
388
- </Dropzone>
389
- );
390
-
391
- rerender(
392
- <Dropzone ref={setRef}>
393
- {({ getRootProps }) => <div {...getRootProps()} />}
394
- </Dropzone>
395
- );
396
-
397
- expect(setRef).toHaveBeenCalledTimes(1);
398
- });
399
-
400
- it("sets {isFocused} to false if {disabled} is true", () => {
401
- const { container, rerender } = render(
402
- <Dropzone>
403
- {({ getRootProps, getInputProps, isFocused }) => (
404
- <div {...getRootProps()}>
405
- <input {...getInputProps()} />
406
- {isFocused && <div id="focus" />}
407
- </div>
408
- )}
409
- </Dropzone>
410
- );
411
-
412
- const dropzone = container.querySelector("div");
413
-
414
- fireEvent.focus(dropzone);
415
- expect(dropzone.querySelector("#focus")).not.toBeNull();
416
-
417
- rerender(
418
- <Dropzone disabled>
419
- {({ getRootProps, getInputProps, isFocused }) => (
420
- <div {...getRootProps()}>
421
- <input {...getInputProps()} />
422
- {isFocused && <div id="focus" />}
423
- </div>
424
- )}
425
- </Dropzone>
426
- );
427
-
428
- expect(dropzone.querySelector("#focus")).toBeNull();
429
- });
430
-
431
- test("{tabindex} is 0 if {disabled} is false", () => {
432
- const { container } = render(
433
- <Dropzone>
434
- {({ getRootProps, getInputProps }) => (
435
- <div {...getRootProps()}>
436
- <input {...getInputProps()} />
437
- </div>
438
- )}
439
- </Dropzone>
440
- );
441
- expect(container.querySelector("div")).toHaveAttribute("tabindex", "0");
442
- });
443
-
444
- test("{tabindex} is not set if {disabled} is true", () => {
445
- const { container, rerender } = render(
446
- <Dropzone>
447
- {({ getRootProps, getInputProps }) => (
448
- <div {...getRootProps()}>
449
- <input {...getInputProps()} />
450
- </div>
451
- )}
452
- </Dropzone>
453
- );
454
-
455
- expect(container.querySelector("div")).toHaveAttribute("tabindex", "0");
456
-
457
- rerender(
458
- <Dropzone disabled>
459
- {({ getRootProps, getInputProps }) => (
460
- <div {...getRootProps()}>
461
- <input {...getInputProps()} />
462
- </div>
463
- )}
464
- </Dropzone>
465
- );
466
-
467
- expect(container.querySelector("div")).not.toHaveAttribute("tabindex");
468
- });
469
-
470
- test("{tabindex} is not set if {noKeyboard} is true", () => {
471
- const { container, rerender } = render(
472
- <Dropzone>
473
- {({ getRootProps, getInputProps }) => (
474
- <div {...getRootProps()}>
475
- <input {...getInputProps()} />
476
- </div>
477
- )}
478
- </Dropzone>
479
- );
480
-
481
- expect(container.querySelector("div")).toHaveAttribute("tabindex", "0");
482
-
483
- rerender(
484
- <Dropzone noKeyboard>
485
- {({ getRootProps, getInputProps }) => (
486
- <div {...getRootProps()}>
487
- <input {...getInputProps()} />
488
- </div>
489
- )}
490
- </Dropzone>
491
- );
492
-
493
- expect(container.querySelector("div")).not.toHaveAttribute("tabindex");
494
- });
495
-
496
- test("refs are set when {refKey} is set to a different value", (done) => {
497
- const data = createDtWithFiles(files);
498
-
499
- class MyView extends React.Component {
500
- render() {
501
- const { children, innerRef, ...rest } = this.props;
502
- return (
503
- <div id="dropzone" ref={innerRef} {...rest}>
504
- <div>{children}</div>
505
- </div>
506
- );
507
- }
508
- }
509
-
510
- const ui = (
511
- <Dropzone>
512
- {({ getRootProps }) => (
513
- <MyView {...getRootProps({ refKey: "innerRef" })}>
514
- <span>Drop some files here ...</span>
515
- </MyView>
516
- )}
517
- </Dropzone>
518
- );
519
-
520
- const { container, rerender } = render(ui);
521
- const dropzone = container.querySelector("#dropzone");
522
-
523
- const fn = async () => {
524
- await act(() => fireEvent.drop(dropzone, data));
525
- rerender(ui);
526
- done();
527
- };
528
-
529
- expect(fn).not.toThrow();
530
- });
531
-
532
- test("click events originating from <label> should not trigger file dialog open twice", () => {
533
- const activeRef = createRef();
534
- const active = <span ref={activeRef}>I am active</span>;
535
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
536
-
537
- const { container } = render(
538
- <Dropzone>
539
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
540
- <label {...getRootProps()}>
541
- <input {...getInputProps()} />
542
- {isFileDialogActive && active}
543
- </label>
544
- )}
545
- </Dropzone>
546
- );
547
-
548
- const dropzone = container.querySelector("label");
549
-
550
- fireEvent.click(dropzone, { bubbles: true, cancelable: true });
551
-
552
- expect(activeRef.current).not.toBeNull();
553
- expect(dropzone).toContainElement(activeRef.current);
554
- expect(onClickSpy).toHaveBeenCalledTimes(1);
555
- });
556
- });
557
-
558
- describe("document drop protection", () => {
559
- const addEventListenerSpy = jest.spyOn(document, "addEventListener");
560
- const removeEventListenerSpy = jest.spyOn(document, "removeEventListener");
561
- // Collect the list of addEventListener/removeEventListener spy calls into an object keyed by event name
562
- const collectEventListenerCalls = (spy) =>
563
- spy.mock.calls.reduce(
564
- (acc, [eventName, ...rest]) => ({
565
- ...acc,
566
- [eventName]: rest,
567
- }),
568
- {}
569
- );
570
-
571
- it("installs hooks to prevent stray drops from taking over the browser window", () => {
572
- render(
573
- <Dropzone>
574
- {({ getRootProps, getInputProps }) => (
575
- <div {...getRootProps()}>
576
- <input {...getInputProps()} />
577
- </div>
578
- )}
579
- </Dropzone>
580
- );
581
-
582
- expect(addEventListenerSpy).toHaveBeenCalledTimes(6);
583
-
584
- const addEventCalls = collectEventListenerCalls(addEventListenerSpy);
585
- const events = Object.keys(addEventCalls);
586
-
587
- expect(events).toContain("dragover");
588
- expect(events).toContain("drop");
589
- expect(events).toContain("dragenter");
590
- expect(events).toContain("dragleave");
591
- expect(events).toContain("dragend");
592
-
593
- events.forEach((eventName) => {
594
- const [fn, options] = addEventCalls[eventName];
595
- expect(fn).toBeDefined();
596
- expect(options).toBe(false);
597
- });
598
- });
599
-
600
- it("removes document hooks when unmounted", () => {
601
- const { unmount } = render(
602
- <Dropzone>
603
- {({ getRootProps, getInputProps }) => (
604
- <div {...getRootProps()}>
605
- <input {...getInputProps()} />
606
- </div>
607
- )}
608
- </Dropzone>
609
- );
610
-
611
- unmount();
612
-
613
- expect(removeEventListenerSpy).toHaveBeenCalledTimes(6);
614
-
615
- const addEventCalls = collectEventListenerCalls(addEventListenerSpy);
616
- const removeEventCalls = collectEventListenerCalls(
617
- removeEventListenerSpy
618
- );
619
- const events = Object.keys(removeEventCalls);
620
-
621
- expect(events).toContain("dragover");
622
- expect(events).toContain("drop");
623
- expect(events).toContain("dragenter");
624
- expect(events).toContain("dragleave");
625
- expect(events).toContain("dragend");
626
-
627
- events.forEach((eventName) => {
628
- const [a] = addEventCalls[eventName];
629
- const [b] = removeEventCalls[eventName];
630
- expect(a).toEqual(b);
631
- });
632
- });
633
-
634
- it("terminates drags and drops on elements outside our dropzone", () => {
635
- render(
636
- <Dropzone>
637
- {({ getRootProps, getInputProps }) => (
638
- <div {...getRootProps()}>
639
- <input {...getInputProps()} />
640
- </div>
641
- )}
642
- </Dropzone>
643
- );
644
-
645
- const dragEvt = new Event("dragover", { bubbles: true });
646
- const dragEvtPreventDefaultSpy = jest.spyOn(dragEvt, "preventDefault");
647
- fireEvent(document.body, dragEvt);
648
- expect(dragEvtPreventDefaultSpy).toHaveBeenCalled();
649
-
650
- const dropEvt = new Event("drop", { bubbles: true });
651
- const dropEvtPreventDefaultSpy = jest.spyOn(dropEvt, "preventDefault");
652
- fireEvent(document.body, dropEvt);
653
- expect(dropEvtPreventDefaultSpy).toHaveBeenCalled();
654
- });
655
-
656
- it("permits drags and drops on elements inside our dropzone", () => {
657
- const { container } = render(
658
- <Dropzone>
659
- {({ getRootProps, getInputProps }) => (
660
- <div {...getRootProps()}>
661
- <input {...getInputProps()} />
662
- </div>
663
- )}
664
- </Dropzone>
665
- );
666
-
667
- const dropEvt = new Event("drop", { bubbles: true });
668
- const dropEvtPreventDefaultSpy = jest.spyOn(dropEvt, "preventDefault");
669
-
670
- fireEvent(container.querySelector("div"), dropEvt);
671
- // A call is from the onDrop handler for the dropzone,
672
- // but there should be no more than 1
673
- expect(dropEvtPreventDefaultSpy).toHaveBeenCalled();
674
- });
675
-
676
- it("does not prevent stray drops when {preventDropOnDocument} is false", () => {
677
- render(
678
- <Dropzone preventDropOnDocument={false}>
679
- {({ getRootProps, getInputProps }) => (
680
- <div {...getRootProps()}>
681
- <input {...getInputProps()} />
682
- </div>
683
- )}
684
- </Dropzone>
685
- );
686
-
687
- const dropEvt = new Event("drop", { bubbles: true });
688
- const dropEvtPreventDefaultSpy = jest.spyOn(dropEvt, "preventDefault");
689
- fireEvent(document.body, dropEvt);
690
- expect(dropEvtPreventDefaultSpy).toHaveBeenCalledTimes(0);
691
- });
692
- });
693
-
694
- describe("event propagation", () => {
695
- const data = createDtWithFiles(files);
696
-
697
- test("drag events propagate from the inner dropzone to parents", async () => {
698
- const innerProps = {
699
- onDragEnter: jest.fn(),
700
- onDragOver: jest.fn(),
701
- onDragLeave: jest.fn(),
702
- onDrop: jest.fn(),
703
- };
704
-
705
- const InnerDropzone = () => (
706
- <Dropzone {...innerProps}>
707
- {({ getRootProps, getInputProps }) => (
708
- <div id="inner-dropzone" {...getRootProps()}>
709
- <input {...getInputProps()} />
710
- </div>
711
- )}
712
- </Dropzone>
713
- );
714
-
715
- const parentProps = {
716
- onDragEnter: jest.fn(),
717
- onDragOver: jest.fn(),
718
- onDragLeave: jest.fn(),
719
- onDrop: jest.fn(),
720
- };
721
-
722
- const { container } = render(
723
- <Dropzone {...parentProps}>
724
- {({ getRootProps, getInputProps }) => (
725
- <div {...getRootProps()}>
726
- <input {...getInputProps()} />
727
- <InnerDropzone />
728
- </div>
729
- )}
730
- </Dropzone>
731
- );
732
-
733
- const innerDropzone = container.querySelector("#inner-dropzone");
734
-
735
- await act(() => fireEvent.dragEnter(innerDropzone, data));
736
- expect(innerProps.onDragEnter).toHaveBeenCalled();
737
- expect(parentProps.onDragEnter).toHaveBeenCalled();
738
-
739
- fireEvent.dragOver(innerDropzone, data);
740
- expect(innerProps.onDragOver).toHaveBeenCalled();
741
- expect(parentProps.onDragOver).toHaveBeenCalled();
742
-
743
- fireEvent.dragLeave(innerDropzone, data);
744
- expect(innerProps.onDragLeave).toHaveBeenCalled();
745
- expect(parentProps.onDragLeave).toHaveBeenCalled();
746
-
747
- await act(() => fireEvent.drop(innerDropzone, data));
748
- expect(innerProps.onDrop).toHaveBeenCalled();
749
- expect(parentProps.onDrop).toHaveBeenCalled();
750
- });
751
-
752
- test("drag events do not propagate from the inner dropzone to parent dropzone if user invoked stopPropagation() on the events", async () => {
753
- const innerProps = {
754
- onDragEnter: jest.fn(),
755
- onDragOver: jest.fn(),
756
- onDragLeave: jest.fn(),
757
- onDrop: jest.fn(),
758
- };
759
-
760
- Object.keys(innerProps).forEach((prop) =>
761
- innerProps[prop].mockImplementation((...args) => {
762
- const event = prop === "onDrop" ? args.pop() : args.shift();
763
- event.stopPropagation();
764
- })
765
- );
766
-
767
- const InnerDropzone = () => (
768
- <Dropzone {...innerProps}>
769
- {({ getRootProps, getInputProps }) => (
770
- <div id="inner-dropzone" {...getRootProps()}>
771
- <input {...getInputProps()} />
772
- </div>
773
- )}
774
- </Dropzone>
775
- );
776
-
777
- const parentProps = {
778
- onDragEnter: jest.fn(),
779
- onDragOver: jest.fn(),
780
- onDragLeave: jest.fn(),
781
- onDrop: jest.fn(),
782
- };
783
-
784
- const { container } = render(
785
- <Dropzone {...parentProps}>
786
- {({ getRootProps, getInputProps }) => (
787
- <div {...getRootProps()}>
788
- <input {...getInputProps()} />
789
- <InnerDropzone />
790
- </div>
791
- )}
792
- </Dropzone>
793
- );
794
-
795
- const innerDropzone = container.querySelector("#inner-dropzone");
796
-
797
- await act(() => fireEvent.dragEnter(innerDropzone, data));
798
- expect(innerProps.onDragEnter).toHaveBeenCalled();
799
- expect(parentProps.onDragEnter).not.toHaveBeenCalled();
800
-
801
- fireEvent.dragOver(innerDropzone, data);
802
- expect(innerProps.onDragOver).toHaveBeenCalled();
803
- expect(parentProps.onDragOver).not.toHaveBeenCalled();
804
-
805
- fireEvent.dragLeave(innerDropzone, data);
806
- expect(innerProps.onDragLeave).toHaveBeenCalled();
807
- expect(parentProps.onDragLeave).not.toHaveBeenCalled();
808
-
809
- await act(() => fireEvent.drop(innerDropzone, data));
810
- expect(innerProps.onDrop).toHaveBeenCalled();
811
- expect(parentProps.onDrop).not.toHaveBeenCalled();
812
- });
813
-
814
- test("drag events do not propagate from the inner dropzone to parent dropzone if {noDragEventsBubbling} is true", async () => {
815
- const innerProps = {
816
- onDragEnter: jest.fn(),
817
- onDragOver: jest.fn(),
818
- onDragLeave: jest.fn(),
819
- onDrop: jest.fn(),
820
- };
821
-
822
- const InnerDropzone = () => (
823
- <Dropzone {...innerProps} noDragEventsBubbling>
824
- {({ getRootProps, getInputProps }) => (
825
- <div id="inner-dropzone" {...getRootProps()}>
826
- <input {...getInputProps()} />
827
- </div>
828
- )}
829
- </Dropzone>
830
- );
831
-
832
- const parentProps = {
833
- onDragEnter: jest.fn(),
834
- onDragOver: jest.fn(),
835
- onDragLeave: jest.fn(),
836
- onDrop: jest.fn(),
837
- };
838
-
839
- const { container } = render(
840
- <Dropzone {...parentProps}>
841
- {({ getRootProps, getInputProps }) => (
842
- <div id="outer-dropzone" {...getRootProps()}>
843
- <input {...getInputProps()} />
844
- <InnerDropzone />
845
- </div>
846
- )}
847
- </Dropzone>
848
- );
849
-
850
- const outerDropzone = container.querySelector("#outer-dropzone");
851
- const innerDropzone = container.querySelector("#inner-dropzone");
852
-
853
- // Sets drag targets on the outer dropzone
854
- await act(() => fireEvent.dragEnter(outerDropzone, data));
855
-
856
- await act(() => fireEvent.dragEnter(innerDropzone, data));
857
- expect(innerProps.onDragEnter).toHaveBeenCalled();
858
- expect(parentProps.onDragEnter).toHaveBeenCalledTimes(1);
859
-
860
- fireEvent.dragOver(innerDropzone, data);
861
- expect(innerProps.onDragOver).toHaveBeenCalled();
862
- expect(parentProps.onDragOver).not.toHaveBeenCalled();
863
-
864
- fireEvent.dragLeave(innerDropzone, data);
865
- expect(innerProps.onDragLeave).toHaveBeenCalled();
866
- expect(parentProps.onDragLeave).not.toHaveBeenCalled();
867
-
868
- await act(() => fireEvent.drop(innerDropzone, data));
869
- expect(innerProps.onDrop).toHaveBeenCalled();
870
- expect(parentProps.onDrop).not.toHaveBeenCalled();
871
- });
872
-
873
- test("onDragLeave is not invoked for the parent dropzone if it was invoked for an inner dropzone", async () => {
874
- const innerDragLeave = jest.fn();
875
- const InnerDropzone = () => (
876
- <Dropzone onDragLeave={innerDragLeave}>
877
- {({ getRootProps, getInputProps }) => (
878
- <div id="inner-dropzone" {...getRootProps()}>
879
- <input {...getInputProps()} />
880
- </div>
881
- )}
882
- </Dropzone>
883
- );
884
-
885
- const parentDragLeave = jest.fn();
886
-
887
- const { container } = render(
888
- <Dropzone onDragLeave={parentDragLeave}>
889
- {({ getRootProps, getInputProps }) => (
890
- <div id="parent-dropzone" {...getRootProps()}>
891
- <input {...getInputProps()} />
892
- <InnerDropzone />
893
- </div>
894
- )}
895
- </Dropzone>
896
- );
897
-
898
- const parentDropzone = container.querySelector("#parent-dropzone");
899
-
900
- await act(() => fireEvent.dragEnter(parentDropzone, data));
901
-
902
- const innerDropzone = container.querySelector("#inner-dropzone");
903
- await act(() => fireEvent.dragEnter(innerDropzone, data));
904
-
905
- fireEvent.dragLeave(innerDropzone, data);
906
- expect(innerDragLeave).toHaveBeenCalled();
907
- expect(parentDragLeave).not.toHaveBeenCalled();
908
- });
909
- });
910
-
911
- describe("plugin integration", () => {
912
- it("uses provided getFilesFromEvent()", async () => {
913
- const data = createDtWithFiles(files);
914
-
915
- const props = {
916
- getFilesFromEvent: jest
917
- .fn()
918
- .mockImplementation((event) => fromEvent(event)),
919
- onDragEnter: jest.fn(),
920
- onDragOver: jest.fn(),
921
- onDragLeave: jest.fn(),
922
- onDrop: jest.fn(),
923
- };
924
-
925
- const { container } = render(
926
- <Dropzone {...props}>
927
- {({ getRootProps, getInputProps }) => (
928
- <div {...getRootProps()}>
929
- <input {...getInputProps()} />
930
- </div>
931
- )}
932
- </Dropzone>
933
- );
934
- const dropzone = container.querySelector("div");
935
-
936
- await act(() => fireEvent.dragEnter(dropzone, data));
937
- expect(props.onDragEnter).toHaveBeenCalled();
938
-
939
- fireEvent.dragOver(dropzone, data);
940
- expect(props.onDragOver).toHaveBeenCalled();
941
-
942
- fireEvent.dragLeave(dropzone, data);
943
- expect(props.onDragLeave).toHaveBeenCalled();
944
-
945
- await act(() => fireEvent.drop(dropzone, data));
946
- expect(props.onDrop).toHaveBeenCalled();
947
- expect(props.getFilesFromEvent).toHaveBeenCalledTimes(2);
948
- });
949
-
950
- it("calls {onError} when getFilesFromEvent() rejects", async () => {
951
- const data = createDtWithFiles(files);
952
-
953
- const props = {
954
- getFilesFromEvent: jest
955
- .fn()
956
- .mockImplementation(() => Promise.reject("oops :(")),
957
- onDragEnter: jest.fn(),
958
- onDrop: jest.fn(),
959
- onError: jest.fn(),
960
- };
961
-
962
- const ui = (
963
- <Dropzone {...props}>
964
- {({ getRootProps, getInputProps }) => (
965
- <div {...getRootProps()}>
966
- <input {...getInputProps()} />
967
- </div>
968
- )}
969
- </Dropzone>
970
- );
971
- const { container } = render(ui);
972
- const dropzone = container.querySelector("div");
973
-
974
- await act(() => fireEvent.dragEnter(dropzone, data));
975
- expect(props.onDragEnter).not.toHaveBeenCalled();
976
-
977
- await act(() => fireEvent.drop(dropzone, data));
978
- expect(props.onDrop).not.toHaveBeenCalled();
979
-
980
- expect(props.getFilesFromEvent).toHaveBeenCalledTimes(2);
981
- expect(props.onError).toHaveBeenCalledTimes(2);
982
- });
983
- });
984
-
985
- describe("onFocus", () => {
986
- it("sets focus state", () => {
987
- const { container } = render(
988
- <Dropzone>
989
- {({ getRootProps, getInputProps, isFocused }) => (
990
- <div {...getRootProps()}>
991
- <input {...getInputProps()} />
992
- {isFocused && <div id="focus" />}
993
- </div>
994
- )}
995
- </Dropzone>
996
- );
997
-
998
- const dropzone = container.querySelector("div");
999
-
1000
- fireEvent.focus(dropzone);
1001
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1002
- });
1003
-
1004
- it("does not set focus state if user stopped event propagation", () => {
1005
- const { container } = render(
1006
- <Dropzone>
1007
- {({ getRootProps, getInputProps, isFocused }) => (
1008
- <div
1009
- {...getRootProps({ onFocus: (event) => event.stopPropagation() })}
1010
- >
1011
- <input {...getInputProps()} />
1012
- {isFocused && <div id="focus" />}
1013
- </div>
1014
- )}
1015
- </Dropzone>
1016
- );
1017
-
1018
- const dropzone = container.querySelector("div");
1019
-
1020
- fireEvent.focus(dropzone);
1021
- expect(dropzone.querySelector("#focus")).toBeNull();
1022
- });
1023
-
1024
- it("does not set focus state if {noKeyboard} is true", () => {
1025
- const { container } = render(
1026
- <Dropzone noKeyboard>
1027
- {({ getRootProps, getInputProps, isFocused }) => (
1028
- <div {...getRootProps()}>
1029
- <input {...getInputProps()} />
1030
- {isFocused && <div id="focus" />}
1031
- </div>
1032
- )}
1033
- </Dropzone>
1034
- );
1035
-
1036
- const dropzone = container.querySelector("div");
1037
-
1038
- fireEvent.focus(dropzone);
1039
- expect(dropzone.querySelector("#focus")).toBeNull();
1040
- });
1041
-
1042
- it("restores focus behavior if {noKeyboard} is set back to false", () => {
1043
- const { container, rerender } = render(
1044
- <Dropzone noKeyboard>
1045
- {({ getRootProps, getInputProps, isFocused }) => (
1046
- <div {...getRootProps()}>
1047
- <input {...getInputProps()} />
1048
- {isFocused && <div id="focus" />}
1049
- </div>
1050
- )}
1051
- </Dropzone>
1052
- );
1053
-
1054
- const dropzone = container.querySelector("div");
1055
-
1056
- fireEvent.focus(dropzone);
1057
- expect(dropzone.querySelector("#focus")).toBeNull();
1058
-
1059
- rerender(
1060
- <Dropzone noKeyboard={false}>
1061
- {({ getRootProps, getInputProps, isFocused }) => (
1062
- <div {...getRootProps()}>
1063
- <input {...getInputProps()} />
1064
- {isFocused && <div id="focus" />}
1065
- </div>
1066
- )}
1067
- </Dropzone>
1068
- );
1069
-
1070
- fireEvent.focus(dropzone);
1071
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1072
- });
1073
-
1074
- it("{autoFocus} sets the focus state on render", () => {
1075
- const { container, rerender } = render(
1076
- <Dropzone>
1077
- {({ getRootProps, getInputProps, isFocused }) => (
1078
- <div {...getRootProps()}>
1079
- <input {...getInputProps()} />
1080
- {isFocused && <div id="focus" />}
1081
- </div>
1082
- )}
1083
- </Dropzone>
1084
- );
1085
-
1086
- const dropzone = container.querySelector("div");
1087
-
1088
- expect(dropzone.querySelector("#focus")).toBeNull();
1089
-
1090
- rerender(
1091
- /* eslint-disable-next-line jsx-a11y/no-autofocus */
1092
- <Dropzone autoFocus>
1093
- {({ getRootProps, getInputProps, isFocused }) => (
1094
- <div {...getRootProps()}>
1095
- <input {...getInputProps()} />
1096
- {isFocused && <div id="focus" />}
1097
- </div>
1098
- )}
1099
- </Dropzone>
1100
- );
1101
-
1102
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1103
-
1104
- rerender(
1105
- /* eslint-disable-next-line jsx-a11y/no-autofocus */
1106
- <Dropzone autoFocus disabled>
1107
- {({ getRootProps, getInputProps, isFocused }) => (
1108
- <div {...getRootProps()}>
1109
- <input {...getInputProps()} />
1110
- {isFocused && <div id="focus" />}
1111
- </div>
1112
- )}
1113
- </Dropzone>
1114
- );
1115
-
1116
- expect(dropzone.querySelector("#focus")).toBeNull();
1117
- });
1118
- });
1119
-
1120
- describe("onBlur", () => {
1121
- it("unsets focus state", () => {
1122
- const { container } = render(
1123
- <Dropzone>
1124
- {({ getRootProps, getInputProps, isFocused }) => (
1125
- <div {...getRootProps()}>
1126
- <input {...getInputProps()} />
1127
- {isFocused && <div id="focus" />}
1128
- </div>
1129
- )}
1130
- </Dropzone>
1131
- );
1132
-
1133
- const dropzone = container.querySelector("div");
1134
-
1135
- fireEvent.focus(dropzone);
1136
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1137
-
1138
- fireEvent.blur(dropzone);
1139
- expect(dropzone.querySelector("#focus")).toBeNull();
1140
- });
1141
-
1142
- it("does not unset focus state if user stopped event propagation", () => {
1143
- const { container } = render(
1144
- <Dropzone>
1145
- {({ getRootProps, getInputProps, isFocused }) => (
1146
- <div
1147
- {...getRootProps({ onBlur: (event) => event.stopPropagation() })}
1148
- >
1149
- <input {...getInputProps()} />
1150
- {isFocused && <div id="focus" />}
1151
- </div>
1152
- )}
1153
- </Dropzone>
1154
- );
1155
-
1156
- const dropzone = container.querySelector("div");
1157
-
1158
- fireEvent.focus(dropzone);
1159
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1160
- fireEvent.blur(dropzone);
1161
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1162
- });
1163
-
1164
- it("does not unset focus state if {noKeyboard} is true", () => {
1165
- const { container, rerender } = render(
1166
- <Dropzone>
1167
- {({ getRootProps, getInputProps, isFocused }) => (
1168
- <div {...getRootProps()}>
1169
- <input {...getInputProps()} />
1170
- {isFocused && <div id="focus" />}
1171
- </div>
1172
- )}
1173
- </Dropzone>
1174
- );
1175
-
1176
- const dropzone = container.querySelector("div");
1177
-
1178
- fireEvent.focus(dropzone);
1179
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1180
-
1181
- rerender(
1182
- <Dropzone noKeyboard>
1183
- {({ getRootProps, getInputProps, isFocused }) => (
1184
- <div {...getRootProps()}>
1185
- <input {...getInputProps()} />
1186
- {isFocused && <div id="focus" />}
1187
- </div>
1188
- )}
1189
- </Dropzone>
1190
- );
1191
-
1192
- fireEvent.blur(dropzone);
1193
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1194
- });
1195
-
1196
- it("restores blur behavior if {noKeyboard} is set back to false", () => {
1197
- const { container, rerender } = render(
1198
- <Dropzone>
1199
- {({ getRootProps, getInputProps, isFocused }) => (
1200
- <div {...getRootProps()}>
1201
- <input {...getInputProps()} />
1202
- {isFocused && <div id="focus" />}
1203
- </div>
1204
- )}
1205
- </Dropzone>
1206
- );
1207
-
1208
- const dropzone = container.querySelector("div");
1209
-
1210
- fireEvent.focus(dropzone);
1211
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1212
-
1213
- rerender(
1214
- <Dropzone noKeyboard>
1215
- {({ getRootProps, getInputProps, isFocused }) => (
1216
- <div {...getRootProps()}>
1217
- <input {...getInputProps()} />
1218
- {isFocused && <div id="focus" />}
1219
- </div>
1220
- )}
1221
- </Dropzone>
1222
- );
1223
-
1224
- fireEvent.blur(dropzone);
1225
- expect(dropzone.querySelector("#focus")).not.toBeNull();
1226
-
1227
- rerender(
1228
- <Dropzone noKeyboard={false}>
1229
- {({ getRootProps, getInputProps, isFocused }) => (
1230
- <div {...getRootProps()}>
1231
- <input {...getInputProps()} />
1232
- {isFocused && <div id="focus" />}
1233
- </div>
1234
- )}
1235
- </Dropzone>
1236
- );
1237
-
1238
- fireEvent.blur(dropzone);
1239
- expect(dropzone.querySelector("#focus")).toBeNull();
1240
- });
1241
- });
1242
-
1243
- describe("onClick", () => {
1244
- let currentShowOpenFilePicker;
1245
-
1246
- beforeEach(() => {
1247
- currentShowOpenFilePicker = window.showOpenFilePicker;
1248
- });
1249
-
1250
- afterEach(() => {
1251
- if (currentShowOpenFilePicker) {
1252
- window.showOpenFilePicker = currentShowOpenFilePicker;
1253
- } else {
1254
- delete window.showOpenFilePicker;
1255
- }
1256
- });
1257
-
1258
- it("should proxy the click event to the input", () => {
1259
- const activeRef = createRef();
1260
- const active = <span ref={activeRef}>I am active</span>;
1261
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1262
-
1263
- const { container } = render(
1264
- <Dropzone>
1265
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1266
- <div {...getRootProps()}>
1267
- <input {...getInputProps()} />
1268
- {isFileDialogActive && active}
1269
- </div>
1270
- )}
1271
- </Dropzone>
1272
- );
1273
-
1274
- const dropzone = container.querySelector("div");
1275
-
1276
- fireEvent.click(dropzone);
1277
- expect(activeRef.current).not.toBeNull();
1278
- expect(dropzone).toContainElement(activeRef.current);
1279
- expect(onClickSpy).toHaveBeenCalled();
1280
- });
1281
-
1282
- it("should not not proxy the click event to the input if event propagation was stopped", () => {
1283
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1284
- const { container } = render(
1285
- <Dropzone>
1286
- {({ getRootProps, getInputProps }) => (
1287
- <div
1288
- {...getRootProps({ onClick: (event) => event.stopPropagation() })}
1289
- >
1290
- <input {...getInputProps()} />
1291
- </div>
1292
- )}
1293
- </Dropzone>
1294
- );
1295
-
1296
- fireEvent.click(container.querySelector("div"));
1297
- expect(onClickSpy).not.toHaveBeenCalled();
1298
- });
1299
-
1300
- it("should not not proxy the click event to the input if {noClick} is true", () => {
1301
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1302
- const { container } = render(
1303
- <Dropzone noClick>
1304
- {({ getRootProps, getInputProps }) => (
1305
- <div {...getRootProps()}>
1306
- <input {...getInputProps()} />
1307
- </div>
1308
- )}
1309
- </Dropzone>
1310
- );
1311
-
1312
- fireEvent.click(container.querySelector("div"));
1313
- expect(onClickSpy).not.toHaveBeenCalled();
1314
- });
1315
-
1316
- it("restores click behavior if {noClick} is set back to false", () => {
1317
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1318
- const { container, rerender } = render(
1319
- <Dropzone noClick>
1320
- {({ getRootProps, getInputProps }) => (
1321
- <div {...getRootProps()}>
1322
- <input {...getInputProps()} />
1323
- </div>
1324
- )}
1325
- </Dropzone>
1326
- );
1327
-
1328
- const dropzone = container.querySelector("div");
1329
-
1330
- fireEvent.click(dropzone);
1331
- expect(onClickSpy).not.toHaveBeenCalled();
1332
-
1333
- rerender(
1334
- <Dropzone noClick={false}>
1335
- {({ getRootProps, getInputProps }) => (
1336
- <div {...getRootProps()}>
1337
- <input {...getInputProps()} />
1338
- </div>
1339
- )}
1340
- </Dropzone>
1341
- );
1342
-
1343
- fireEvent.click(dropzone);
1344
- expect(onClickSpy).toHaveBeenCalled();
1345
- });
1346
-
1347
- // https://github.com/react-dropzone/react-dropzone/issues/783
1348
- it("should continue event propagation if {noClick} is true", () => {
1349
- const btnClickSpy = jest.fn();
1350
- const inputClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1351
- const { container } = render(
1352
- <Dropzone noClick>
1353
- {({ getRootProps, getInputProps }) => (
1354
- <div {...getRootProps()}>
1355
- <input {...getInputProps()} />
1356
- <button onClick={btnClickSpy} />
1357
- </div>
1358
- )}
1359
- </Dropzone>
1360
- );
1361
-
1362
- fireEvent.click(container.querySelector("div"));
1363
- expect(inputClickSpy).not.toHaveBeenCalled();
1364
-
1365
- fireEvent.click(container.querySelector("button"));
1366
- expect(btnClickSpy).toHaveBeenCalled();
1367
- });
1368
-
1369
- it("should schedule input click on next tick in Edge", () => {
1370
- jest.useFakeTimers();
1371
-
1372
- const isIeOrEdgeSpy = jest
1373
- .spyOn(utils, "isIeOrEdge")
1374
- .mockReturnValueOnce(true);
1375
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1376
-
1377
- const { container } = render(
1378
- <Dropzone>
1379
- {({ getRootProps, getInputProps }) => (
1380
- <div {...getRootProps()}>
1381
- <input {...getInputProps()} />
1382
- </div>
1383
- )}
1384
- </Dropzone>
1385
- );
1386
-
1387
- fireEvent.click(container.querySelector("div"));
1388
- drainPendingTimers();
1389
-
1390
- expect(onClickSpy).toHaveBeenCalled();
1391
- jest.useRealTimers();
1392
- isIeOrEdgeSpy.mockClear();
1393
- });
1394
-
1395
- it("should not use showOpenFilePicker() if supported and {useFsAccessApi} is not true", () => {
1396
- jest.useFakeTimers();
1397
-
1398
- const activeRef = createRef();
1399
- const active = <span ref={activeRef}>I am active</span>;
1400
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1401
-
1402
- const showOpenFilePickerMock = jest.fn();
1403
-
1404
- window.showOpenFilePicker = showOpenFilePickerMock;
1405
-
1406
- const onDropSpy = jest.fn();
1407
- const onFileDialogOpenSpy = jest.fn();
1408
-
1409
- const { container } = render(
1410
- <Dropzone
1411
- onDrop={onDropSpy}
1412
- onFileDialogOpen={onFileDialogOpenSpy}
1413
- accept={{
1414
- "application/pdf": [],
1415
- }}
1416
- multiple
1417
- >
1418
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1419
- <div {...getRootProps()}>
1420
- <input {...getInputProps()} />
1421
- {isFileDialogActive && active}
1422
- </div>
1423
- )}
1424
- </Dropzone>
1425
- );
1426
-
1427
- const dropzone = container.querySelector("div");
1428
-
1429
- fireEvent.click(dropzone);
1430
-
1431
- expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1432
- expect(onClickSpy).toHaveBeenCalled();
1433
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1434
- expect(activeRef.current).not.toBeNull();
1435
- expect(dropzone).toContainElement(activeRef.current);
1436
-
1437
- focusWindow();
1438
- drainPendingTimers();
1439
-
1440
- expect(activeRef.current).toBeNull();
1441
- expect(dropzone).not.toContainElement(activeRef.current);
1442
- expect(onDropSpy).not.toHaveBeenCalled();
1443
-
1444
- jest.useRealTimers();
1445
- });
1446
-
1447
- it("should not use showOpenFilePicker() if supported and {isSecureContext} is not true", () => {
1448
- jest.useFakeTimers();
1449
-
1450
- const activeRef = createRef();
1451
- const active = <span ref={activeRef}>I am active</span>;
1452
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1453
-
1454
- const showOpenFilePickerMock = jest.fn();
1455
-
1456
- window.showOpenFilePicker = showOpenFilePickerMock;
1457
- window.isSecureContext = false;
1458
-
1459
- const onDropSpy = jest.fn();
1460
- const onFileDialogOpenSpy = jest.fn();
1461
-
1462
- const { container } = render(
1463
- <Dropzone
1464
- onDrop={onDropSpy}
1465
- onFileDialogOpen={onFileDialogOpenSpy}
1466
- accept={{
1467
- "application/pdf": [],
1468
- }}
1469
- multiple
1470
- >
1471
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1472
- <div {...getRootProps()}>
1473
- <input {...getInputProps()} />
1474
- {isFileDialogActive && active}
1475
- </div>
1476
- )}
1477
- </Dropzone>
1478
- );
1479
-
1480
- const dropzone = container.querySelector("div");
1481
-
1482
- fireEvent.click(dropzone);
1483
-
1484
- expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1485
- expect(onClickSpy).toHaveBeenCalled();
1486
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1487
- expect(activeRef.current).not.toBeNull();
1488
- expect(dropzone).toContainElement(activeRef.current);
1489
-
1490
- focusWindow();
1491
- drainPendingTimers();
1492
-
1493
- expect(activeRef.current).toBeNull();
1494
- expect(dropzone).not.toContainElement(activeRef.current);
1495
- expect(onDropSpy).not.toHaveBeenCalled();
1496
-
1497
- jest.useRealTimers();
1498
-
1499
- window.isSecureContext = true;
1500
- });
1501
-
1502
- it("should use showOpenFilePicker() if supported and {useFsAccessApi} is true, and not trigger click on input", async () => {
1503
- const activeRef = createRef();
1504
- const active = <span ref={activeRef}>I am active</span>;
1505
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1506
-
1507
- const handlers = files.map((f) => createFileSystemFileHandle(f));
1508
- const thenable = createThenable();
1509
- const showOpenFilePickerMock = jest
1510
- .fn()
1511
- .mockReturnValue(thenable.promise);
1512
-
1513
- window.showOpenFilePicker = showOpenFilePickerMock;
1514
-
1515
- const onDropSpy = jest.fn();
1516
- const onFileDialogOpenSpy = jest.fn();
1517
-
1518
- const { container } = render(
1519
- <Dropzone
1520
- onDrop={onDropSpy}
1521
- onFileDialogOpen={onFileDialogOpenSpy}
1522
- accept={{
1523
- "application/pdf": [],
1524
- }}
1525
- multiple
1526
- useFsAccessApi
1527
- >
1528
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1529
- <div {...getRootProps()}>
1530
- <input {...getInputProps()} />
1531
- {isFileDialogActive && active}
1532
- </div>
1533
- )}
1534
- </Dropzone>
1535
- );
1536
-
1537
- const dropzone = container.querySelector("div");
1538
-
1539
- fireEvent.click(dropzone);
1540
-
1541
- expect(showOpenFilePickerMock).toHaveBeenCalledWith({
1542
- multiple: true,
1543
- types: [
1544
- {
1545
- description: "Files",
1546
- accept: { "application/pdf": [] },
1547
- },
1548
- ],
1549
- });
1550
- expect(onClickSpy).not.toHaveBeenCalled();
1551
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1552
-
1553
- expect(activeRef.current).not.toBeNull();
1554
- expect(dropzone).toContainElement(activeRef.current);
1555
-
1556
- await act(() => thenable.done(handlers));
1557
-
1558
- expect(activeRef.current).toBeNull();
1559
- expect(dropzone).not.toContainElement(activeRef.current);
1560
-
1561
- expect(onDropSpy).toHaveBeenCalledWith(files, [], null);
1562
- });
1563
-
1564
- test("if showOpenFilePicker() is supported and {useFsAccessApi} is true, it should work without the <input>", async () => {
1565
- const activeRef = createRef();
1566
- const active = <span ref={activeRef}>I am active</span>;
1567
-
1568
- const handlers = files.map((f) => createFileSystemFileHandle(f));
1569
- const thenable = createThenable();
1570
- const showOpenFilePickerMock = jest
1571
- .fn()
1572
- .mockReturnValue(thenable.promise);
1573
-
1574
- window.showOpenFilePicker = showOpenFilePickerMock;
1575
-
1576
- const onDropSpy = jest.fn();
1577
- const onFileDialogOpenSpy = jest.fn();
1578
-
1579
- const { container } = render(
1580
- <Dropzone
1581
- onDrop={onDropSpy}
1582
- onFileDialogOpen={onFileDialogOpenSpy}
1583
- useFsAccessApi
1584
- >
1585
- {({ getRootProps, isFileDialogActive }) => (
1586
- <div {...getRootProps()}>{isFileDialogActive && active}</div>
1587
- )}
1588
- </Dropzone>
1589
- );
1590
-
1591
- const dropzone = container.querySelector("div");
1592
-
1593
- fireEvent.click(dropzone);
1594
-
1595
- expect(showOpenFilePickerMock).toHaveBeenCalled();
1596
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1597
-
1598
- await act(() => thenable.done(handlers));
1599
-
1600
- expect(activeRef.current).toBeNull();
1601
- expect(dropzone).not.toContainElement(activeRef.current);
1602
- expect(onDropSpy).toHaveBeenCalledWith(files, [], null);
1603
- });
1604
-
1605
- test("if showOpenFilePicker() is supported and {useFsAccessApi} is true, and the user cancels it should call onFileDialogCancel", async () => {
1606
- const activeRef = createRef();
1607
- const active = <span ref={activeRef}>I am active</span>;
1608
-
1609
- const thenable = createThenable();
1610
- const showOpenFilePickerMock = jest
1611
- .fn()
1612
- .mockReturnValue(thenable.promise);
1613
-
1614
- window.showOpenFilePicker = showOpenFilePickerMock;
1615
-
1616
- const onDropSpy = jest.fn();
1617
- const onFileDialogCancelSpy = jest.fn();
1618
-
1619
- const { container } = render(
1620
- <Dropzone
1621
- onDrop={onDropSpy}
1622
- onFileDialogCancel={onFileDialogCancelSpy}
1623
- useFsAccessApi
1624
- >
1625
- {({ getRootProps, isFileDialogActive }) => (
1626
- <div {...getRootProps()}>{isFileDialogActive && active}</div>
1627
- )}
1628
- </Dropzone>
1629
- );
1630
-
1631
- const dropzone = container.querySelector("div");
1632
-
1633
- fireEvent.click(dropzone);
1634
-
1635
- expect(showOpenFilePickerMock).toHaveBeenCalled();
1636
-
1637
- await act(() =>
1638
- thenable.cancel(new DOMException("user aborted request", "AbortError"))
1639
- );
1640
-
1641
- expect(activeRef.current).toBeNull();
1642
- expect(dropzone).not.toContainElement(activeRef.current);
1643
- expect(onFileDialogCancelSpy).toHaveBeenCalled();
1644
- expect(onDropSpy).not.toHaveBeenCalled();
1645
- });
1646
-
1647
- test("window focus evt is not bound if showOpenFilePicker() is supported and {useFsAccessApi} is true", async () => {
1648
- jest.useFakeTimers();
1649
-
1650
- const activeRef = createRef();
1651
- const active = <span ref={activeRef}>I am active</span>;
1652
- const onFileDialogCancelSpy = jest.fn();
1653
-
1654
- const thenable = createThenable();
1655
- const showOpenFilePickerMock = jest
1656
- .fn()
1657
- .mockReturnValue(thenable.promise);
1658
-
1659
- window.showOpenFilePicker = showOpenFilePickerMock;
1660
-
1661
- const { container } = render(
1662
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy} useFsAccessApi>
1663
- {({ getRootProps, isFileDialogActive }) => (
1664
- <div {...getRootProps()}>{isFileDialogActive && active}</div>
1665
- )}
1666
- </Dropzone>
1667
- );
1668
-
1669
- const dropzone = container.querySelector("div");
1670
-
1671
- fireEvent.click(dropzone);
1672
-
1673
- expect(activeRef.current).not.toBeNull();
1674
- expect(dropzone).toContainElement(activeRef.current);
1675
-
1676
- await act(() =>
1677
- thenable.cancel(new DOMException("user aborted request", "AbortError"))
1678
- );
1679
-
1680
- // Try to focus window and run timers
1681
- focusWindow();
1682
- drainPendingTimers();
1683
-
1684
- expect(activeRef.current).toBeNull();
1685
- expect(dropzone).not.toContainElement(activeRef.current);
1686
- expect(onFileDialogCancelSpy).toHaveBeenCalledTimes(1);
1687
-
1688
- jest.useRealTimers();
1689
- });
1690
-
1691
- it("should try to use showOpenFilePicker() and fallback to input in case of a security error", async () => {
1692
- const activeRef = createRef();
1693
- const active = <span ref={activeRef}>I am active</span>;
1694
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1695
-
1696
- const thenable = createThenable();
1697
- const showOpenFilePickerMock = jest
1698
- .fn()
1699
- .mockReturnValue(thenable.promise);
1700
-
1701
- window.showOpenFilePicker = showOpenFilePickerMock;
1702
-
1703
- const onDropSpy = jest.fn();
1704
- const onFileDialogOpenSpy = jest.fn();
1705
-
1706
- const { container } = render(
1707
- <Dropzone
1708
- onDrop={onDropSpy}
1709
- onFileDialogOpen={onFileDialogOpenSpy}
1710
- accept={{
1711
- "application/pdf": [],
1712
- }}
1713
- multiple
1714
- useFsAccessApi
1715
- >
1716
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1717
- <div {...getRootProps()}>
1718
- <input {...getInputProps()} />
1719
- {isFileDialogActive && active}
1720
- </div>
1721
- )}
1722
- </Dropzone>
1723
- );
1724
-
1725
- const dropzone = container.querySelector("div");
1726
-
1727
- fireEvent.click(dropzone);
1728
-
1729
- expect(activeRef.current).not.toBeNull();
1730
- expect(dropzone).toContainElement(activeRef.current);
1731
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1732
-
1733
- await act(() =>
1734
- thenable.cancel(
1735
- new DOMException("Cannot use this API cross-origin", "SecurityError")
1736
- )
1737
- );
1738
-
1739
- expect(activeRef.current).not.toBeNull();
1740
- expect(dropzone).toContainElement(activeRef.current);
1741
- expect(onClickSpy).toHaveBeenCalled();
1742
- });
1743
-
1744
- test("window focus evt is bound if showOpenFilePicker() is supported but errors due to a security error", async () => {
1745
- jest.useFakeTimers();
1746
-
1747
- const activeRef = createRef();
1748
- const active = <span ref={activeRef}>I am active</span>;
1749
- const onFileDialogCancelSpy = jest.fn();
1750
-
1751
- const thenable = createThenable();
1752
- const showOpenFilePickerMock = jest
1753
- .fn()
1754
- .mockReturnValue(thenable.promise);
1755
-
1756
- window.showOpenFilePicker = showOpenFilePickerMock;
1757
-
1758
- const { container } = render(
1759
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy} useFsAccessApi>
1760
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1761
- <div {...getRootProps()}>
1762
- <input {...getInputProps()} />
1763
- {isFileDialogActive && active}
1764
- </div>
1765
- )}
1766
- </Dropzone>
1767
- );
1768
-
1769
- const dropzone = container.querySelector("div");
1770
-
1771
- fireEvent.click(dropzone);
1772
-
1773
- expect(activeRef.current).not.toBeNull();
1774
- expect(dropzone).toContainElement(activeRef.current);
1775
-
1776
- await act(() =>
1777
- thenable.cancel(
1778
- new DOMException("Cannot use this API cross-origin", "SecurityError")
1779
- )
1780
- );
1781
-
1782
- focusWindow();
1783
- drainPendingTimers();
1784
-
1785
- expect(onFileDialogCancelSpy).toHaveBeenCalled();
1786
- expect(dropzone).not.toContainElement(activeRef.current);
1787
-
1788
- jest.useRealTimers();
1789
- });
1790
-
1791
- test("showOpenFilePicker() should call {onError} when an unexpected error occurs", async () => {
1792
- const activeRef = createRef();
1793
- const active = <span ref={activeRef}>I am active</span>;
1794
-
1795
- const thenable = createThenable();
1796
- const showOpenFilePickerMock = jest
1797
- .fn()
1798
- .mockReturnValue(thenable.promise);
1799
-
1800
- window.showOpenFilePicker = showOpenFilePickerMock;
1801
-
1802
- const onErrorSpy = jest.fn();
1803
- const onDropSpy = jest.fn();
1804
- const onFileDialogOpenSpy = jest.fn();
1805
-
1806
- const ui = (
1807
- <Dropzone
1808
- onError={onErrorSpy}
1809
- onDrop={onDropSpy}
1810
- onFileDialogOpen={onFileDialogOpenSpy}
1811
- accept={{
1812
- "application/pdf": [],
1813
- }}
1814
- multiple
1815
- useFsAccessApi
1816
- >
1817
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1818
- <div {...getRootProps()}>
1819
- <input {...getInputProps()} />
1820
- {isFileDialogActive && active}
1821
- </div>
1822
- )}
1823
- </Dropzone>
1824
- );
1825
-
1826
- const { container } = render(ui);
1827
-
1828
- const dropzone = container.querySelector("div");
1829
-
1830
- fireEvent.click(dropzone);
1831
-
1832
- expect(activeRef.current).not.toBeNull();
1833
- expect(dropzone).toContainElement(activeRef.current);
1834
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1835
-
1836
- const err = new Error("oops :(");
1837
- await act(() => thenable.cancel(err));
1838
- expect(activeRef.current).not.toBeNull();
1839
- expect(dropzone).toContainElement(activeRef.current);
1840
- expect(onErrorSpy).toHaveBeenCalledWith(err);
1841
- });
1842
-
1843
- test("showOpenFilePicker() should call {onError} when a security error occurs and no <input> was provided", async () => {
1844
- const activeRef = createRef();
1845
- const active = <span ref={activeRef}>I am active</span>;
1846
-
1847
- const thenable = createThenable();
1848
- const showOpenFilePickerMock = jest
1849
- .fn()
1850
- .mockReturnValue(thenable.promise);
1851
-
1852
- window.showOpenFilePicker = showOpenFilePickerMock;
1853
-
1854
- const onErrorSpy = jest.fn();
1855
- const onDropSpy = jest.fn();
1856
- const onFileDialogOpenSpy = jest.fn();
1857
-
1858
- const ui = (
1859
- <Dropzone
1860
- onError={onErrorSpy}
1861
- onDrop={onDropSpy}
1862
- onFileDialogOpen={onFileDialogOpenSpy}
1863
- accept={{
1864
- "application/pdf": [],
1865
- }}
1866
- multiple
1867
- useFsAccessApi
1868
- >
1869
- {({ getRootProps, isFileDialogActive }) => (
1870
- <div {...getRootProps()}>{isFileDialogActive && active}</div>
1871
- )}
1872
- </Dropzone>
1873
- );
1874
-
1875
- const { container } = render(ui);
1876
-
1877
- const dropzone = container.querySelector("div");
1878
-
1879
- fireEvent.click(dropzone);
1880
-
1881
- expect(activeRef.current).not.toBeNull();
1882
- expect(dropzone).toContainElement(activeRef.current);
1883
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1884
-
1885
- const err = new DOMException("oops :(", "SecurityError");
1886
- await act(() => thenable.cancel(err));
1887
- expect(activeRef.current).not.toBeNull();
1888
- expect(dropzone).toContainElement(activeRef.current);
1889
- expect(onErrorSpy).toHaveBeenCalled();
1890
- });
1891
- });
1892
-
1893
- describe("onKeyDown", () => {
1894
- it("triggers the click event on the input if the SPACE/ENTER keys are pressed", () => {
1895
- const activeRef = createRef();
1896
- const active = <span ref={activeRef}>I am active</span>;
1897
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1898
- const { container } = render(
1899
- <Dropzone>
1900
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
1901
- <div {...getRootProps()}>
1902
- <input {...getInputProps()} />
1903
- {isFileDialogActive && active}
1904
- </div>
1905
- )}
1906
- </Dropzone>
1907
- );
1908
-
1909
- const dropzone = container.querySelector("div");
1910
-
1911
- fireEvent.keyDown(dropzone, {
1912
- keyCode: 32, // Space
1913
- });
1914
-
1915
- fireEvent.keyDown(dropzone, {
1916
- keyCode: 13, // Enter
1917
- });
1918
-
1919
- fireEvent.keyDown(dropzone, {
1920
- key: " ", // Space
1921
- });
1922
-
1923
- fireEvent.keyDown(dropzone, {
1924
- key: "Enter",
1925
- });
1926
-
1927
- const ref = activeRef.current;
1928
- expect(ref).not.toBeNull();
1929
- expect(dropzone).toContainElement(ref);
1930
- expect(onClickSpy).toHaveBeenCalledTimes(4);
1931
- });
1932
-
1933
- it("does not trigger the click event on the input if the dropzone is not in focus", () => {
1934
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1935
- const { container } = render(
1936
- <Dropzone>
1937
- {({ getRootProps, getInputProps }) => (
1938
- <div {...getRootProps()}>
1939
- <input {...getInputProps()} />
1940
- </div>
1941
- )}
1942
- </Dropzone>
1943
- );
1944
-
1945
- const input = container.querySelector("input");
1946
-
1947
- fireEvent.keyDown(input, {
1948
- keyCode: 32, // Space
1949
- });
1950
-
1951
- fireEvent.keyDown(input, {
1952
- key: " ", // Space
1953
- });
1954
-
1955
- expect(onClickSpy).not.toHaveBeenCalled();
1956
- });
1957
-
1958
- it("does not trigger the click event on the input if event propagation was stopped", () => {
1959
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1960
- const { container } = render(
1961
- <Dropzone>
1962
- {({ getRootProps, getInputProps }) => (
1963
- <div
1964
- {...getRootProps({
1965
- onKeyDown: (event) => event.stopPropagation(),
1966
- })}
1967
- >
1968
- <input {...getInputProps()} />
1969
- </div>
1970
- )}
1971
- </Dropzone>
1972
- );
1973
-
1974
- const dropzone = container.querySelector("div");
1975
-
1976
- fireEvent.keyDown(dropzone, {
1977
- keyCode: 32, // Space
1978
- });
1979
- fireEvent.keyDown(dropzone, {
1980
- key: " ", // Space
1981
- });
1982
- expect(onClickSpy).not.toHaveBeenCalled();
1983
- });
1984
-
1985
- it("does not trigger the click event on the input if {noKeyboard} is true", () => {
1986
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1987
- const { container } = render(
1988
- <Dropzone noKeyboard>
1989
- {({ getRootProps, getInputProps }) => (
1990
- <div {...getRootProps()}>
1991
- <input {...getInputProps()} />
1992
- </div>
1993
- )}
1994
- </Dropzone>
1995
- );
1996
-
1997
- const dropzone = container.querySelector("div");
1998
-
1999
- fireEvent.keyDown(dropzone, {
2000
- keyCode: 32, // Space
2001
- });
2002
- fireEvent.keyDown(dropzone, {
2003
- key: " ", // Space
2004
- });
2005
- expect(onClickSpy).not.toHaveBeenCalled();
2006
- });
2007
-
2008
- it("restores the keydown behavior when {noKeyboard} is set back to false", () => {
2009
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
2010
- const { container, rerender } = render(
2011
- <Dropzone noKeyboard>
2012
- {({ getRootProps, getInputProps }) => (
2013
- <div {...getRootProps()}>
2014
- <input {...getInputProps()} />
2015
- </div>
2016
- )}
2017
- </Dropzone>
2018
- );
2019
-
2020
- const dropzone = container.querySelector("div");
2021
-
2022
- fireEvent.keyDown(dropzone, {
2023
- keyCode: 32, // Space
2024
- });
2025
- fireEvent.keyDown(dropzone, {
2026
- key: " ", // Space
2027
- });
2028
- expect(onClickSpy).not.toHaveBeenCalled();
2029
-
2030
- rerender(
2031
- <Dropzone noKeyboard={false}>
2032
- {({ getRootProps, getInputProps }) => (
2033
- <div {...getRootProps()}>
2034
- <input {...getInputProps()} />
2035
- </div>
2036
- )}
2037
- </Dropzone>
2038
- );
2039
-
2040
- fireEvent.keyDown(dropzone, {
2041
- keyCode: 32, // Space
2042
- });
2043
- fireEvent.keyDown(dropzone, {
2044
- key: " ", // Space
2045
- });
2046
- expect(onClickSpy).toHaveBeenCalledTimes(2);
2047
- });
2048
-
2049
- it("does not trigger the click event on the input for other keys", () => {
2050
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
2051
- const { container } = render(
2052
- <Dropzone>
2053
- {({ getRootProps, getInputProps }) => (
2054
- <div {...getRootProps()}>
2055
- <input {...getInputProps()} />
2056
- </div>
2057
- )}
2058
- </Dropzone>
2059
- );
2060
-
2061
- const dropzone = container.querySelector("div");
2062
-
2063
- fireEvent.keyDown(dropzone, {
2064
- keyCode: 97, // Numpad1
2065
- });
2066
- fireEvent.keyDown(dropzone, {
2067
- key: "1",
2068
- });
2069
- expect(onClickSpy).not.toHaveBeenCalled();
2070
- });
2071
- });
2072
-
2073
- describe("onDrag*", () => {
2074
- it("invokes callbacks for the appropriate events", async () => {
2075
- const data = createDtWithFiles(files);
2076
-
2077
- const props = {
2078
- onDragEnter: jest.fn(),
2079
- onDragOver: jest.fn(),
2080
- onDragLeave: jest.fn(),
2081
- onDrop: jest.fn(),
2082
- };
2083
-
2084
- const { container } = render(
2085
- <Dropzone {...props}>
2086
- {({ getRootProps, getInputProps }) => (
2087
- <div {...getRootProps()}>
2088
- <input {...getInputProps()} />
2089
- </div>
2090
- )}
2091
- </Dropzone>
2092
- );
2093
- const dropzone = container.querySelector("div");
2094
-
2095
- await act(() => fireEvent.dragEnter(dropzone, data));
2096
- expect(props.onDragEnter).toHaveBeenCalled();
2097
-
2098
- fireEvent.dragOver(dropzone, data);
2099
- expect(props.onDragOver).toHaveBeenCalled();
2100
-
2101
- fireEvent.dragLeave(dropzone, data);
2102
- expect(props.onDragLeave).toHaveBeenCalled();
2103
-
2104
- await act(() => fireEvent.drop(dropzone, data));
2105
- expect(props.onDrop).toHaveBeenCalled();
2106
- });
2107
-
2108
- it("invokes callbacks for the appropriate events even if it cannot access the data", async () => {
2109
- const emptyData = createDtWithFiles([]);
2110
-
2111
- const props = {
2112
- onDragEnter: jest.fn(),
2113
- onDragOver: jest.fn(),
2114
- onDragLeave: jest.fn(),
2115
- onDrop: jest.fn(),
2116
- onDropAccepted: jest.fn(),
2117
- onDropRejected: jest.fn(),
2118
- };
2119
-
2120
- const { container } = render(
2121
- <Dropzone {...props}>
2122
- {({ getRootProps, getInputProps }) => (
2123
- <div {...getRootProps()}>
2124
- <input {...getInputProps()} />
2125
- </div>
2126
- )}
2127
- </Dropzone>
2128
- );
2129
- const dropzone = container.querySelector("div");
2130
-
2131
- await act(() => fireEvent.dragEnter(dropzone, emptyData));
2132
- expect(props.onDragEnter).toHaveBeenCalled();
2133
-
2134
- fireEvent.dragOver(dropzone, emptyData);
2135
- expect(props.onDragOver).toHaveBeenCalled();
2136
-
2137
- fireEvent.dragLeave(dropzone, emptyData);
2138
- expect(props.onDragLeave).toHaveBeenCalled();
2139
-
2140
- const data = createDtWithFiles(files);
2141
- await act(() => fireEvent.drop(dropzone, data));
2142
- expect(props.onDrop).toHaveBeenCalled();
2143
- expect(props.onDropAccepted).toHaveBeenCalledWith(
2144
- files,
2145
- expect.any(Object)
2146
- );
2147
- expect(props.onDropRejected).not.toHaveBeenCalled();
2148
- });
2149
-
2150
- it("does not invoke callbacks if no files are detected", async () => {
2151
- const data = {
2152
- dataTransfer: {
2153
- items: [],
2154
- types: ["text/html", "text/plain"],
2155
- },
2156
- };
2157
-
2158
- const props = {
2159
- onDragEnter: jest.fn(),
2160
- onDragOver: jest.fn(),
2161
- onDragLeave: jest.fn(),
2162
- onDrop: jest.fn(),
2163
- onDropAccepted: jest.fn(),
2164
- onDropRejected: jest.fn(),
2165
- };
2166
-
2167
- const { container } = render(
2168
- <Dropzone {...props}>
2169
- {({ getRootProps, getInputProps }) => (
2170
- <div {...getRootProps()}>
2171
- <input {...getInputProps()} />
2172
- </div>
2173
- )}
2174
- </Dropzone>
2175
- );
2176
- const dropzone = container.querySelector("div");
2177
-
2178
- await act(() => fireEvent.dragEnter(dropzone, data));
2179
- expect(props.onDragEnter).not.toHaveBeenCalled();
2180
-
2181
- fireEvent.dragOver(dropzone, data);
2182
- expect(props.onDragOver).not.toHaveBeenCalled();
2183
-
2184
- fireEvent.dragLeave(dropzone, data);
2185
- expect(props.onDragLeave).not.toHaveBeenCalled();
2186
-
2187
- await act(() => fireEvent.drop(dropzone, data));
2188
- expect(props.onDrop).not.toHaveBeenCalled();
2189
- expect(props.onDropAccepted).not.toHaveBeenCalled();
2190
- expect(props.onDropRejected).not.toHaveBeenCalled();
2191
- });
2192
-
2193
- it("does not invoke callbacks if {noDrag} is true", async () => {
2194
- const data = createDtWithFiles(files);
2195
-
2196
- const props = {
2197
- onDragEnter: jest.fn(),
2198
- onDragOver: jest.fn(),
2199
- onDragLeave: jest.fn(),
2200
- onDrop: jest.fn(),
2201
- onDropAccepted: jest.fn(),
2202
- onDropRejected: jest.fn(),
2203
- };
2204
-
2205
- const { container } = render(
2206
- <Dropzone {...props} noDrag>
2207
- {({ getRootProps, getInputProps }) => (
2208
- <div {...getRootProps()}>
2209
- <input {...getInputProps()} />
2210
- </div>
2211
- )}
2212
- </Dropzone>
2213
- );
2214
- const dropzone = container.querySelector("div");
2215
-
2216
- await act(() => fireEvent.dragEnter(dropzone, data));
2217
- expect(props.onDragEnter).not.toHaveBeenCalled();
2218
-
2219
- fireEvent.dragOver(dropzone, data);
2220
- expect(props.onDragOver).not.toHaveBeenCalled();
2221
-
2222
- fireEvent.dragLeave(dropzone, data);
2223
- expect(props.onDragLeave).not.toHaveBeenCalled();
2224
-
2225
- await act(() => fireEvent.drop(dropzone, data));
2226
- expect(props.onDrop).not.toHaveBeenCalled();
2227
- expect(props.onDropAccepted).not.toHaveBeenCalled();
2228
- expect(props.onDropRejected).not.toHaveBeenCalled();
2229
- });
2230
-
2231
- it("restores drag behavior if {noDrag} is set back to false", async () => {
2232
- const data = createDtWithFiles(files);
2233
-
2234
- const props = {
2235
- onDragEnter: jest.fn(),
2236
- onDragOver: jest.fn(),
2237
- onDragLeave: jest.fn(),
2238
- onDrop: jest.fn(),
2239
- };
2240
-
2241
- const { container, rerender } = render(
2242
- <Dropzone {...props} noDrag>
2243
- {({ getRootProps, getInputProps }) => (
2244
- <div {...getRootProps()}>
2245
- <input {...getInputProps()} />
2246
- </div>
2247
- )}
2248
- </Dropzone>
2249
- );
2250
- const dropzone = container.querySelector("div");
2251
-
2252
- await act(() => fireEvent.dragEnter(dropzone, data));
2253
- expect(props.onDragEnter).not.toHaveBeenCalled();
2254
-
2255
- fireEvent.dragOver(dropzone, data);
2256
- expect(props.onDragOver).not.toHaveBeenCalled();
2257
-
2258
- fireEvent.dragLeave(dropzone, data);
2259
- expect(props.onDragLeave).not.toHaveBeenCalled();
2260
-
2261
- await act(() => fireEvent.drop(dropzone, data));
2262
- expect(props.onDrop).not.toHaveBeenCalled();
2263
-
2264
- rerender(
2265
- <Dropzone {...props} noDrag={false}>
2266
- {({ getRootProps, getInputProps }) => (
2267
- <div {...getRootProps()}>
2268
- <input {...getInputProps()} />
2269
- </div>
2270
- )}
2271
- </Dropzone>
2272
- );
2273
-
2274
- await act(() => fireEvent.dragEnter(dropzone, data));
2275
- expect(props.onDragEnter).toHaveBeenCalled();
2276
-
2277
- fireEvent.dragOver(dropzone, data);
2278
- expect(props.onDragOver).toHaveBeenCalled();
2279
-
2280
- fireEvent.dragLeave(dropzone, data);
2281
- expect(props.onDragLeave).toHaveBeenCalled();
2282
-
2283
- await act(() => fireEvent.drop(dropzone, data));
2284
- expect(props.onDrop).toHaveBeenCalled();
2285
- });
2286
-
2287
- it("sets {isDragActive} and {isDragAccept} if some files are accepted on dragenter", async () => {
2288
- const { container } = render(
2289
- <Dropzone>
2290
- {({
2291
- getRootProps,
2292
- getInputProps,
2293
- isDragActive,
2294
- isDragAccept,
2295
- isDragReject,
2296
- }) => (
2297
- <div {...getRootProps()}>
2298
- <input {...getInputProps()} />
2299
- {isDragActive && "dragActive"}
2300
- {isDragAccept && "dragAccept"}
2301
- {isDragReject && "dragReject"}
2302
- </div>
2303
- )}
2304
- </Dropzone>
2305
- );
2306
- const dropzone = container.querySelector("div");
2307
-
2308
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2309
-
2310
- expect(dropzone).toHaveTextContent("dragActive");
2311
- expect(dropzone).toHaveTextContent("dragAccept");
2312
- expect(dropzone).not.toHaveTextContent("dragReject");
2313
- });
2314
-
2315
- it("sets {isDragActive} and {isDragReject} of some files are not accepted on dragenter", async () => {
2316
- const { container } = render(
2317
- <Dropzone
2318
- accept={{
2319
- "image/*": [],
2320
- }}
2321
- >
2322
- {({
2323
- getRootProps,
2324
- getInputProps,
2325
- isDragActive,
2326
- isDragAccept,
2327
- isDragReject,
2328
- }) => (
2329
- <div {...getRootProps()}>
2330
- <input {...getInputProps()} />
2331
- {isDragActive && "dragActive"}
2332
- {isDragAccept && "dragAccept"}
2333
- {isDragReject && "dragReject"}
2334
- </div>
2335
- )}
2336
- </Dropzone>
2337
- );
2338
- const dropzone = container.querySelector("div");
2339
-
2340
- await act(() =>
2341
- fireEvent.dragEnter(dropzone, createDtWithFiles([...files, ...images]))
2342
- );
2343
-
2344
- expect(dropzone).toHaveTextContent("dragActive");
2345
- expect(dropzone).not.toHaveTextContent("dragAccept");
2346
- expect(dropzone).toHaveTextContent("dragReject");
2347
- });
2348
-
2349
- it("sets {isDragReject} if some files are too large", async () => {
2350
- const { container } = render(
2351
- <Dropzone maxSize={0}>
2352
- {({ getRootProps, getInputProps, isDragAccept, isDragReject }) => (
2353
- <div {...getRootProps()}>
2354
- <input {...getInputProps()} />
2355
- {isDragAccept && "dragAccept"}
2356
- {isDragReject && "dragReject"}
2357
- </div>
2358
- )}
2359
- </Dropzone>
2360
- );
2361
- const dropzone = container.querySelector("div");
2362
-
2363
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2364
-
2365
- expect(dropzone).not.toHaveTextContent("dragAccept");
2366
- expect(dropzone).toHaveTextContent("dragReject");
2367
- });
2368
-
2369
- it("accepts files with empty type during dragenter (Chrome .md file issue)", async () => {
2370
- const markdownFiles = [createFile("README.md", 1234, "text/markdown")];
2371
-
2372
- const { container } = render(
2373
- <Dropzone
2374
- accept={{
2375
- "text/markdown": [".md"],
2376
- }}
2377
- >
2378
- {({
2379
- getRootProps,
2380
- getInputProps,
2381
- isDragActive,
2382
- isDragAccept,
2383
- isDragReject,
2384
- }) => (
2385
- <div {...getRootProps()}>
2386
- <input {...getInputProps()} />
2387
- {isDragActive && "dragActive"}
2388
- {isDragAccept && "dragAccept"}
2389
- {isDragReject && "dragReject"}
2390
- </div>
2391
- )}
2392
- </Dropzone>
2393
- );
2394
- const dropzone = container.querySelector("div");
2395
-
2396
- // Simulate Chrome's behavior: during drag, .md files have empty type
2397
- await act(() =>
2398
- fireEvent.dragEnter(
2399
- dropzone,
2400
- createDtWithFiles(markdownFiles, { emptyTypes: true })
2401
- )
2402
- );
2403
-
2404
- expect(dropzone).toHaveTextContent("dragActive");
2405
- expect(dropzone).toHaveTextContent("dragAccept");
2406
- expect(dropzone).not.toHaveTextContent("dragReject");
2407
- });
2408
-
2409
- it("sets {isDragActive, isDragAccept, isDragReject} if any files are rejected and {multiple} is false on dragenter", async () => {
2410
- const { container } = render(
2411
- <Dropzone
2412
- accept={{
2413
- "image/*": [],
2414
- }}
2415
- multiple={false}
2416
- >
2417
- {({
2418
- getRootProps,
2419
- getInputProps,
2420
- isDragActive,
2421
- isDragAccept,
2422
- isDragReject,
2423
- }) => (
2424
- <div {...getRootProps()}>
2425
- <input {...getInputProps()} />
2426
- {isDragActive && "dragActive"}
2427
- {isDragAccept && "dragAccept"}
2428
- {isDragReject && "dragReject"}
2429
- </div>
2430
- )}
2431
- </Dropzone>
2432
- );
2433
- const dropzone = container.querySelector("div");
2434
-
2435
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(images)));
2436
-
2437
- expect(dropzone).toHaveTextContent("dragActive");
2438
- expect(dropzone).not.toHaveTextContent("dragAccept");
2439
- expect(dropzone).toHaveTextContent("dragReject");
2440
- });
2441
-
2442
- it("keeps {isDragActive} if dragleave is triggered for some arbitrary node", async () => {
2443
- const { container: overlayContainer } = render(<div />);
2444
-
2445
- const { container } = render(
2446
- <Dropzone>
2447
- {({
2448
- getRootProps,
2449
- getInputProps,
2450
- isDragActive,
2451
- isDragAccept,
2452
- isDragReject,
2453
- }) => (
2454
- <div {...getRootProps()}>
2455
- <input {...getInputProps()} />
2456
- {isDragActive && "dragActive"}
2457
- {isDragAccept && "dragAccept"}
2458
- {isDragReject && "dragReject"}
2459
- </div>
2460
- )}
2461
- </Dropzone>
2462
- );
2463
- const dropzone = container.querySelector("div");
2464
-
2465
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2466
-
2467
- fireEvent.dragLeave(dropzone, {
2468
- bubbles: true,
2469
- target: overlayContainer.querySelector("div"),
2470
- });
2471
-
2472
- expect(dropzone).toHaveTextContent("dragActive");
2473
- });
2474
-
2475
- it("resets {isDragActive, isDragAccept, isDragReject} on dragleave", async () => {
2476
- const { container } = render(
2477
- <Dropzone
2478
- accept={{
2479
- "image/*": [],
2480
- }}
2481
- >
2482
- {({
2483
- getRootProps,
2484
- getInputProps,
2485
- isDragActive,
2486
- isDragAccept,
2487
- isDragReject,
2488
- }) => (
2489
- <div {...getRootProps()}>
2490
- <input {...getInputProps()} />
2491
- {isDragActive && "dragActive"}
2492
- {isDragAccept && "dragAccept"}
2493
- {isDragReject && "dragReject"}
2494
- {!isDragActive && (
2495
- <span
2496
- id="child"
2497
- data-accept={isDragAccept}
2498
- data-reject={isDragReject}
2499
- />
2500
- )}
2501
- </div>
2502
- )}
2503
- </Dropzone>
2504
- );
2505
- const dropzone = container.querySelector("div");
2506
-
2507
- const data = createDtWithFiles(images);
2508
-
2509
- await act(() =>
2510
- fireEvent.dragEnter(container.querySelector("#child"), data)
2511
- );
2512
- await act(() => fireEvent.dragEnter(dropzone, data));
2513
-
2514
- await act(() => fireEvent.dragEnter(dropzone, data));
2515
-
2516
- expect(dropzone).toHaveTextContent("dragActive");
2517
- expect(dropzone).toHaveTextContent("dragAccept");
2518
- expect(dropzone).not.toHaveTextContent("dragReject");
2519
-
2520
- fireEvent.dragLeave(dropzone, data);
2521
- expect(dropzone).toHaveTextContent("dragActive");
2522
- expect(dropzone).toHaveTextContent("dragAccept");
2523
- expect(dropzone).not.toHaveTextContent("dragReject");
2524
-
2525
- fireEvent.dragLeave(dropzone, data);
2526
- expect(dropzone).not.toHaveTextContent("dragActive");
2527
- expect(dropzone).not.toHaveTextContent("dragAccept");
2528
- expect(dropzone).not.toHaveTextContent("dragReject");
2529
-
2530
- const child = container.querySelector("#child");
2531
- expect(child).toHaveAttribute("data-accept", "false");
2532
- expect(child).toHaveAttribute("data-reject", "false");
2533
- });
2534
- });
2535
-
2536
- describe("onDrop", () => {
2537
- test("callback is invoked when <input> change event occurs", async () => {
2538
- const onDropSpy = jest.fn();
2539
-
2540
- const { container } = render(
2541
- <Dropzone onDrop={onDropSpy}>
2542
- {({ getRootProps, getInputProps }) => (
2543
- <div {...getRootProps()}>
2544
- <input {...getInputProps()} />
2545
- </div>
2546
- )}
2547
- </Dropzone>
2548
- );
2549
-
2550
- await act(async () =>
2551
- fireEvent.change(container.querySelector("input"), {
2552
- target: { files },
2553
- })
2554
- );
2555
-
2556
- expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything());
2557
- });
2558
-
2559
- it("sets {acceptedFiles, fileRejections, isDragReject}", async () => {
2560
- const FileList = ({ files = [] }) => (
2561
- <ul>
2562
- {files.map((file) => (
2563
- <li key={file.name} data-type={"accepted"}>
2564
- {file.name}
2565
- </li>
2566
- ))}
2567
- </ul>
2568
- );
2569
-
2570
- const RejectedFileList = ({ fileRejections = [] }) => (
2571
- <ul>
2572
- {fileRejections.map(({ file, errors }) => (
2573
- <li key={file.name}>
2574
- <span data-type={"rejected"}>{file.name}</span>
2575
- <ul>
2576
- {errors.map((e) => (
2577
- <li key={e.code} data-type={"error"}>
2578
- {e.code}
2579
- </li>
2580
- ))}
2581
- </ul>
2582
- </li>
2583
- ))}
2584
- </ul>
2585
- );
2586
-
2587
- const getAcceptedFiles = (node) =>
2588
- node.querySelectorAll(`[data-type="accepted"]`);
2589
- const getRejectedFiles = (node) =>
2590
- node.querySelectorAll(`[data-type="rejected"]`);
2591
- const getRejectedFilesErrors = (node) =>
2592
- node.querySelectorAll(`[data-type="error"]`);
2593
-
2594
- const matchToFiles = (fileList, files) =>
2595
- Array.from(fileList).every(
2596
- (item) => !!files.find((file) => file.name === item.textContent)
2597
- );
2598
- const matchToErrorCode = (errorList, code) =>
2599
- Array.from(errorList).every((item) => item.textContent === code);
2600
-
2601
- const { container } = render(
2602
- <Dropzone
2603
- accept={{
2604
- "image/*": [],
2605
- }}
2606
- >
2607
- {({
2608
- getRootProps,
2609
- getInputProps,
2610
- acceptedFiles,
2611
- fileRejections,
2612
- isDragReject,
2613
- }) => (
2614
- <div {...getRootProps()}>
2615
- <input {...getInputProps()} />
2616
- <FileList files={acceptedFiles} />
2617
- <RejectedFileList fileRejections={fileRejections} />
2618
- {isDragReject && "dragReject"}
2619
- </div>
2620
- )}
2621
- </Dropzone>
2622
- );
2623
- const dropzone = container.querySelector("div");
2624
-
2625
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2626
-
2627
- const acceptedFileList = getAcceptedFiles(dropzone);
2628
- expect(acceptedFileList).toHaveLength(images.length);
2629
- expect(matchToFiles(acceptedFileList, images)).toBe(true);
2630
- expect(dropzone).not.toHaveTextContent("dragReject");
2631
-
2632
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2633
-
2634
- const rejectedFileList = getRejectedFiles(dropzone);
2635
- expect(rejectedFileList).toHaveLength(files.length);
2636
- expect(matchToFiles(rejectedFileList, files)).toBe(true);
2637
- const rejectedFileErrorList = getRejectedFilesErrors(dropzone);
2638
- expect(rejectedFileErrorList).toHaveLength(files.length);
2639
- expect(matchToErrorCode(rejectedFileErrorList, "file-invalid-type")).toBe(
2640
- true
2641
- );
2642
- // After drop, isDragReject should be false even if files were rejected
2643
- expect(dropzone).not.toHaveTextContent("dragReject");
2644
- });
2645
-
2646
- it("resets {isDragActive, isDragAccept, isDragReject}", async () => {
2647
- const { container } = render(
2648
- <Dropzone>
2649
- {({
2650
- getRootProps,
2651
- getInputProps,
2652
- isDragActive,
2653
- isDragAccept,
2654
- isDragReject,
2655
- }) => (
2656
- <div {...getRootProps()}>
2657
- <input {...getInputProps()} />
2658
- {isDragActive && "dragActive"}
2659
- {isDragAccept && "dragAccept"}
2660
- {isDragReject && "dragReject"}
2661
- </div>
2662
- )}
2663
- </Dropzone>
2664
- );
2665
- const dropzone = container.querySelector("div");
2666
-
2667
- const data = createDtWithFiles(files);
2668
-
2669
- await act(() => fireEvent.dragEnter(dropzone, data));
2670
-
2671
- expect(dropzone).toHaveTextContent("dragActive");
2672
- expect(dropzone).toHaveTextContent("dragAccept");
2673
- expect(dropzone).not.toHaveTextContent("dragReject");
2674
-
2675
- await act(() => fireEvent.drop(dropzone, data));
2676
-
2677
- expect(dropzone).not.toHaveTextContent("dragActive");
2678
- expect(dropzone).not.toHaveTextContent("dragAccept");
2679
- expect(dropzone).not.toHaveTextContent("dragReject");
2680
- });
2681
-
2682
- it("isDragReject is false after rejected drop and updates correctly on new drag", async () => {
2683
- const { container } = render(
2684
- <Dropzone
2685
- accept={{
2686
- "image/*": [],
2687
- }}
2688
- >
2689
- {({
2690
- getRootProps,
2691
- getInputProps,
2692
- isDragActive,
2693
- isDragAccept,
2694
- isDragReject,
2695
- fileRejections,
2696
- }) => (
2697
- <div {...getRootProps()}>
2698
- <input {...getInputProps()} />
2699
- {isDragActive && "dragActive"}
2700
- {isDragAccept && "dragAccept"}
2701
- {isDragReject && "dragReject"}
2702
- {fileRejections.length > 0 && "hasRejections"}
2703
- </div>
2704
- )}
2705
- </Dropzone>
2706
- );
2707
- const dropzone = container.querySelector("div");
2708
-
2709
- // Drop rejected files
2710
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2711
-
2712
- // After drop, isDragReject should be false but fileRejections should be populated
2713
- expect(dropzone).not.toHaveTextContent("dragActive");
2714
- expect(dropzone).not.toHaveTextContent("dragAccept");
2715
- expect(dropzone).not.toHaveTextContent("dragReject");
2716
- expect(dropzone).toHaveTextContent("hasRejections");
2717
-
2718
- // New drag with rejected files should set isDragReject to true
2719
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2720
-
2721
- expect(dropzone).toHaveTextContent("dragActive");
2722
- expect(dropzone).not.toHaveTextContent("dragAccept");
2723
- expect(dropzone).toHaveTextContent("dragReject");
2724
-
2725
- // Drag leave should reset drag state
2726
- await act(() => fireEvent.dragLeave(dropzone, createDtWithFiles(files)));
2727
-
2728
- expect(dropzone).not.toHaveTextContent("dragActive");
2729
- expect(dropzone).not.toHaveTextContent("dragAccept");
2730
- expect(dropzone).not.toHaveTextContent("dragReject");
2731
- // But fileRejections should still be there from the previous drop
2732
- expect(dropzone).toHaveTextContent("hasRejections");
2733
-
2734
- // New drag with accepted files should set isDragAccept
2735
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(images)));
2736
-
2737
- expect(dropzone).toHaveTextContent("dragActive");
2738
- expect(dropzone).toHaveTextContent("dragAccept");
2739
- expect(dropzone).not.toHaveTextContent("dragReject");
2740
- });
2741
-
2742
- it("sets {isDragGlobal} to true when drag event is detected on document", async () => {
2743
- const { container } = render(
2744
- <Dropzone>
2745
- {({ getRootProps, getInputProps, isDragGlobal }) => (
2746
- <div {...getRootProps()}>
2747
- <input {...getInputProps()} />
2748
- {isDragGlobal && "dragGlobal"}
2749
- </div>
2750
- )}
2751
- </Dropzone>
2752
- );
2753
- const dropzone = container.querySelector("div");
2754
-
2755
- await act(() =>
2756
- fireEvent.dragEnter(document.body, createDtWithFiles(files))
2757
- );
2758
-
2759
- expect(dropzone).toHaveTextContent("dragGlobal");
2760
- });
2761
-
2762
- it("sets {isDragGlobal} to false when drag leaves document", async () => {
2763
- const { container } = render(
2764
- <Dropzone>
2765
- {({ getRootProps, getInputProps, isDragGlobal }) => (
2766
- <div {...getRootProps()}>
2767
- <input {...getInputProps()} />
2768
- {isDragGlobal && "dragGlobal"}
2769
- </div>
2770
- )}
2771
- </Dropzone>
2772
- );
2773
- const dropzone = container.querySelector("div");
2774
-
2775
- await act(() =>
2776
- fireEvent.dragEnter(document.body, createDtWithFiles(files))
2777
- );
2778
- expect(dropzone).toHaveTextContent("dragGlobal");
2779
-
2780
- await act(() =>
2781
- fireEvent.dragLeave(document.body, createDtWithFiles(files))
2782
- );
2783
- expect(dropzone).not.toHaveTextContent("dragGlobal");
2784
- });
2785
-
2786
- it("sets {isDragGlobal} to false when drop occurs on document", async () => {
2787
- const { container } = render(
2788
- <Dropzone>
2789
- {({ getRootProps, getInputProps, isDragGlobal }) => (
2790
- <div {...getRootProps()}>
2791
- <input {...getInputProps()} />
2792
- {isDragGlobal && "dragGlobal"}
2793
- </div>
2794
- )}
2795
- </Dropzone>
2796
- );
2797
- const dropzone = container.querySelector("div");
2798
-
2799
- await act(() =>
2800
- fireEvent.dragEnter(document.body, createDtWithFiles(files))
2801
- );
2802
- expect(dropzone).toHaveTextContent("dragGlobal");
2803
-
2804
- await act(() => fireEvent.drop(document.body, createDtWithFiles(files)));
2805
- expect(dropzone).not.toHaveTextContent("dragGlobal");
2806
- });
2807
-
2808
- it("sets {isDragGlobal} to false when dragend occurs on document", async () => {
2809
- const { container } = render(
2810
- <Dropzone>
2811
- {({ getRootProps, getInputProps, isDragGlobal }) => (
2812
- <div {...getRootProps()}>
2813
- <input {...getInputProps()} />
2814
- {isDragGlobal && "dragGlobal"}
2815
- </div>
2816
- )}
2817
- </Dropzone>
2818
- );
2819
- const dropzone = container.querySelector("div");
2820
-
2821
- await act(() =>
2822
- fireEvent.dragEnter(document.body, createDtWithFiles(files))
2823
- );
2824
- expect(dropzone).toHaveTextContent("dragGlobal");
2825
-
2826
- fireEvent.dragEnd(document.body, createDtWithFiles(files));
2827
- expect(dropzone).not.toHaveTextContent("dragGlobal");
2828
- });
2829
-
2830
- it("rejects all files if {multiple} is false and {accept} criteria is not met", async () => {
2831
- const onDropSpy = jest.fn();
2832
-
2833
- const { container } = render(
2834
- <Dropzone
2835
- accept={{
2836
- "image/*": [],
2837
- }}
2838
- onDrop={onDropSpy}
2839
- multiple={false}
2840
- >
2841
- {({ getRootProps, getInputProps }) => (
2842
- <div {...getRootProps()}>
2843
- <input {...getInputProps()} />
2844
- </div>
2845
- )}
2846
- </Dropzone>
2847
- );
2848
- const dropzone = container.querySelector("div");
2849
-
2850
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2851
-
2852
- expect(onDropSpy).toHaveBeenCalledWith(
2853
- [],
2854
- [
2855
- {
2856
- file: files[0],
2857
- errors: [
2858
- {
2859
- code: "file-invalid-type",
2860
- message: "File type must be image/*",
2861
- },
2862
- ],
2863
- },
2864
- ],
2865
- expect.anything()
2866
- );
2867
- });
2868
-
2869
- it("rejects all files if {multiple} is false and {accept} criteria is met", async () => {
2870
- const onDropSpy = jest.fn();
2871
-
2872
- const { container } = render(
2873
- <Dropzone
2874
- accept={{
2875
- "image/*": [],
2876
- }}
2877
- onDrop={onDropSpy}
2878
- multiple={false}
2879
- >
2880
- {({ getRootProps, getInputProps }) => (
2881
- <div {...getRootProps()}>
2882
- <input {...getInputProps()} />
2883
- </div>
2884
- )}
2885
- </Dropzone>
2886
- );
2887
- const dropzone = container.querySelector("div");
2888
-
2889
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2890
-
2891
- expect(onDropSpy).toHaveBeenCalledWith(
2892
- [],
2893
- [
2894
- {
2895
- file: images[0],
2896
- errors: [
2897
- {
2898
- code: "too-many-files",
2899
- message: "Too many files",
2900
- },
2901
- ],
2902
- },
2903
- {
2904
- file: images[1],
2905
- errors: [
2906
- {
2907
- code: "too-many-files",
2908
- message: "Too many files",
2909
- },
2910
- ],
2911
- },
2912
- ],
2913
- expect.anything()
2914
- );
2915
- });
2916
-
2917
- it("rejects all files if {multiple} is true and maxFiles is less than files and {accept} criteria is met", async () => {
2918
- const onDropSpy = jest.fn();
2919
- const onDropRejectedSpy = jest.fn();
2920
-
2921
- const { container } = render(
2922
- <Dropzone
2923
- accept={{
2924
- "image/*": [],
2925
- }}
2926
- onDrop={onDropSpy}
2927
- onDropRejected={onDropRejectedSpy}
2928
- multiple={true}
2929
- maxFiles={1}
2930
- >
2931
- {({ getRootProps, getInputProps, isDragReject, isDragAccept }) => (
2932
- <div {...getRootProps()}>
2933
- <input {...getInputProps()} />
2934
- {isDragReject && "dragReject"}
2935
- {isDragAccept && "dragAccept"}
2936
- </div>
2937
- )}
2938
- </Dropzone>
2939
- );
2940
- const dropzone = container.querySelector("div");
2941
-
2942
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2943
-
2944
- expect(onDropRejectedSpy).toHaveBeenCalled();
2945
-
2946
- await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(images)));
2947
-
2948
- expect(dropzone).toHaveTextContent("dragReject");
2949
- expect(dropzone).not.toHaveTextContent("dragAccept");
2950
- expect(onDropSpy).toHaveBeenCalledWith(
2951
- [],
2952
- [
2953
- {
2954
- file: images[0],
2955
- errors: [
2956
- {
2957
- code: "too-many-files",
2958
- message: "Too many files",
2959
- },
2960
- ],
2961
- },
2962
- {
2963
- file: images[1],
2964
- errors: [
2965
- {
2966
- code: "too-many-files",
2967
- message: "Too many files",
2968
- },
2969
- ],
2970
- },
2971
- ],
2972
- expect.anything()
2973
- );
2974
- });
2975
-
2976
- it("rejects all files if {multiple} is true and maxFiles has been updated so that it is less than files", async () => {
2977
- const onDropSpy = jest.fn();
2978
- const onDropRejectedSpy = jest.fn();
2979
- const ui = (maxFiles) => (
2980
- <Dropzone
2981
- accept={{
2982
- "image/*": [],
2983
- }}
2984
- onDrop={onDropSpy}
2985
- multiple={true}
2986
- maxFiles={maxFiles}
2987
- onDropRejected={onDropRejectedSpy}
2988
- >
2989
- {({ getRootProps, getInputProps }) => (
2990
- <div {...getRootProps()}>
2991
- <input {...getInputProps()} />
2992
- </div>
2993
- )}
2994
- </Dropzone>
2995
- );
2996
- const { container, rerender } = render(ui(3));
2997
- const dropzone = container.querySelector("div");
2998
-
2999
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
3000
- rerender(ui(3));
3001
-
3002
- expect(onDropRejectedSpy).not.toHaveBeenCalled();
3003
- expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3004
-
3005
- rerender(ui(1));
3006
-
3007
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
3008
- rerender(ui(1));
3009
-
3010
- expect(onDropRejectedSpy).toHaveBeenCalledWith(
3011
- expect.arrayContaining(
3012
- images.map((image) =>
3013
- expect.objectContaining({ errors: expect.any(Array), file: image })
3014
- )
3015
- ),
3016
- expect.anything()
3017
- );
3018
- });
3019
-
3020
- it("accepts multiple files if {multiple} is true and {accept} criteria is met", async () => {
3021
- const onDropSpy = jest.fn();
3022
- const onDropRejectedSpy = jest.fn();
3023
-
3024
- const { container } = render(
3025
- <Dropzone
3026
- accept={{
3027
- "image/*": [],
3028
- }}
3029
- onDrop={onDropSpy}
3030
- multiple={true}
3031
- maxFiles={3}
3032
- onDropRejected={onDropRejectedSpy}
3033
- >
3034
- {({ getRootProps, getInputProps }) => (
3035
- <div {...getRootProps()}>
3036
- <input {...getInputProps()} />
3037
- </div>
3038
- )}
3039
- </Dropzone>
3040
- );
3041
-
3042
- await act(() =>
3043
- fireEvent.drop(
3044
- container.querySelector("div"),
3045
- createDtWithFiles(images)
3046
- )
3047
- );
3048
-
3049
- expect(onDropRejectedSpy).not.toHaveBeenCalled();
3050
- expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3051
- });
3052
-
3053
- it("accepts a single files if {multiple} is false and {accept} criteria is met", async () => {
3054
- const onDropSpy = jest.fn();
3055
-
3056
- const { container } = render(
3057
- <Dropzone
3058
- accept={{
3059
- "image/*": [],
3060
- }}
3061
- onDrop={onDropSpy}
3062
- multiple={false}
3063
- >
3064
- {({ getRootProps, getInputProps }) => (
3065
- <div {...getRootProps()}>
3066
- <input {...getInputProps()} />
3067
- </div>
3068
- )}
3069
- </Dropzone>
3070
- );
3071
-
3072
- const [image] = images;
3073
- await act(() =>
3074
- fireEvent.drop(
3075
- container.querySelector("div"),
3076
- createDtWithFiles([image])
3077
- )
3078
- );
3079
-
3080
- expect(onDropSpy).toHaveBeenCalledWith([image], [], expect.anything());
3081
- });
3082
-
3083
- it("accepts all files if {multiple} is true", async () => {
3084
- const onDropSpy = jest.fn();
3085
-
3086
- const { container } = render(
3087
- <Dropzone onDrop={onDropSpy}>
3088
- {({ getRootProps, getInputProps }) => (
3089
- <div {...getRootProps()}>
3090
- <input {...getInputProps()} />
3091
- </div>
3092
- )}
3093
- </Dropzone>
3094
- );
3095
-
3096
- await act(() =>
3097
- fireEvent.drop(container.querySelector("div"), createDtWithFiles(files))
3098
- );
3099
-
3100
- expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything());
3101
- });
3102
-
3103
- it("resets {isFileDialogActive} state", async () => {
3104
- const onDropSpy = jest.fn();
3105
- const activeRef = createRef();
3106
- const active = <span ref={activeRef}>I am active</span>;
3107
-
3108
- const { container } = render(
3109
- <Dropzone onDrop={onDropSpy}>
3110
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
3111
- <div {...getRootProps()}>
3112
- <input {...getInputProps()} />
3113
- {isFileDialogActive && active}
3114
- </div>
3115
- )}
3116
- </Dropzone>
3117
- );
3118
-
3119
- const dropzone = container.querySelector("div");
3120
-
3121
- fireEvent.click(dropzone);
3122
-
3123
- expect(activeRef.current).not.toBeNull();
3124
- expect(dropzone).toContainElement(activeRef.current);
3125
-
3126
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3127
-
3128
- expect(activeRef.current).toBeNull();
3129
- expect(dropzone).not.toContainElement(activeRef.current);
3130
- });
3131
-
3132
- it("gets invoked with both accepted/rejected files", async () => {
3133
- const onDropSpy = jest.fn();
3134
-
3135
- const { container } = render(
3136
- <Dropzone
3137
- accept={{
3138
- "image/*": [],
3139
- }}
3140
- onDrop={onDropSpy}
3141
- >
3142
- {({ getRootProps, getInputProps }) => (
3143
- <div {...getRootProps()}>
3144
- <input {...getInputProps()} />
3145
- </div>
3146
- )}
3147
- </Dropzone>
3148
- );
3149
- const dropzone = container.querySelector("div");
3150
-
3151
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3152
-
3153
- expect(onDropSpy).toHaveBeenCalledWith(
3154
- [],
3155
- [
3156
- {
3157
- file: files[0],
3158
- errors: [
3159
- {
3160
- code: "file-invalid-type",
3161
- message: "File type must be image/*",
3162
- },
3163
- ],
3164
- },
3165
- ],
3166
- expect.anything()
3167
- );
3168
- onDropSpy.mockClear();
3169
-
3170
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
3171
-
3172
- expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3173
- onDropSpy.mockClear();
3174
-
3175
- await act(() =>
3176
- fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
3177
- );
3178
-
3179
- expect(onDropSpy).toHaveBeenCalledWith(
3180
- images,
3181
- [
3182
- {
3183
- file: files[0],
3184
- errors: [
3185
- {
3186
- code: "file-invalid-type",
3187
- message: "File type must be image/*",
3188
- },
3189
- ],
3190
- },
3191
- ],
3192
- expect.anything()
3193
- );
3194
- });
3195
-
3196
- test("onDropAccepted callback is invoked if some files are accepted", async () => {
3197
- const onDropAcceptedSpy = jest.fn();
3198
-
3199
- const { container } = render(
3200
- <Dropzone
3201
- accept={{
3202
- "image/*": [],
3203
- }}
3204
- onDropAccepted={onDropAcceptedSpy}
3205
- >
3206
- {({ getRootProps, getInputProps }) => (
3207
- <div {...getRootProps()}>
3208
- <input {...getInputProps()} />
3209
- </div>
3210
- )}
3211
- </Dropzone>
3212
- );
3213
- const dropzone = container.querySelector("div");
3214
-
3215
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3216
- expect(onDropAcceptedSpy).not.toHaveBeenCalled();
3217
- onDropAcceptedSpy.mockClear();
3218
-
3219
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
3220
-
3221
- expect(onDropAcceptedSpy).toHaveBeenCalledWith(images, expect.anything());
3222
- onDropAcceptedSpy.mockClear();
3223
-
3224
- await act(() =>
3225
- fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
3226
- );
3227
-
3228
- expect(onDropAcceptedSpy).toHaveBeenCalledWith(images, expect.anything());
3229
- });
3230
-
3231
- test("onDropRejected callback is invoked if some files are rejected", async () => {
3232
- const onDropRejectedSpy = jest.fn();
3233
-
3234
- const { container } = render(
3235
- <Dropzone
3236
- accept={{
3237
- "image/*": [],
3238
- }}
3239
- onDropRejected={onDropRejectedSpy}
3240
- >
3241
- {({ getRootProps, getInputProps }) => (
3242
- <div {...getRootProps()}>
3243
- <input {...getInputProps()} />
3244
- </div>
3245
- )}
3246
- </Dropzone>
3247
- );
3248
- const dropzone = container.querySelector("div");
3249
-
3250
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3251
-
3252
- expect(onDropRejectedSpy).toHaveBeenCalledWith(
3253
- [
3254
- {
3255
- file: files[0],
3256
- errors: [
3257
- {
3258
- code: "file-invalid-type",
3259
- message: "File type must be image/*",
3260
- },
3261
- ],
3262
- },
3263
- ],
3264
- expect.anything()
3265
- );
3266
- onDropRejectedSpy.mockClear();
3267
-
3268
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
3269
-
3270
- expect(onDropRejectedSpy).not.toHaveBeenCalled();
3271
- onDropRejectedSpy.mockClear();
3272
-
3273
- await act(() =>
3274
- fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
3275
- );
3276
-
3277
- expect(onDropRejectedSpy).toHaveBeenCalledWith(
3278
- [
3279
- {
3280
- file: files[0],
3281
- errors: [
3282
- {
3283
- code: "file-invalid-type",
3284
- message: "File type must be image/*",
3285
- },
3286
- ],
3287
- },
3288
- ],
3289
- expect.anything()
3290
- );
3291
- });
3292
-
3293
- it("accepts a dropped image when Firefox provides a bogus file type", async () => {
3294
- const onDropSpy = jest.fn();
3295
-
3296
- const { container } = render(
3297
- <Dropzone
3298
- accept={{
3299
- "image/*": [],
3300
- }}
3301
- onDrop={onDropSpy}
3302
- >
3303
- {({ getRootProps, getInputProps }) => (
3304
- <div {...getRootProps()}>
3305
- <input {...getInputProps()} />
3306
- </div>
3307
- )}
3308
- </Dropzone>
3309
- );
3310
-
3311
- const images = [createFile("bogus.gif", 1234, "application/x-moz-file")];
3312
- await act(() =>
3313
- fireEvent.drop(
3314
- container.querySelector("div"),
3315
- createDtWithFiles(images)
3316
- )
3317
- );
3318
-
3319
- expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3320
- });
3321
-
3322
- it("filters files according to {maxSize}", async () => {
3323
- const onDropSpy = jest.fn();
3324
-
3325
- const { container } = render(
3326
- <Dropzone onDrop={onDropSpy} maxSize={1111}>
3327
- {({ getRootProps, getInputProps }) => (
3328
- <div {...getRootProps()}>
3329
- <input {...getInputProps()} />
3330
- </div>
3331
- )}
3332
- </Dropzone>
3333
- );
3334
-
3335
- await act(() =>
3336
- fireEvent.drop(
3337
- container.querySelector("div"),
3338
- createDtWithFiles(images)
3339
- )
3340
- );
3341
-
3342
- expect(onDropSpy).toHaveBeenCalledWith(
3343
- [],
3344
- [
3345
- {
3346
- file: images[0],
3347
- errors: [
3348
- {
3349
- code: "file-too-large",
3350
- message: "File is larger than 1111 bytes",
3351
- },
3352
- ],
3353
- },
3354
- {
3355
- file: images[1],
3356
- errors: [
3357
- {
3358
- code: "file-too-large",
3359
- message: "File is larger than 1111 bytes",
3360
- },
3361
- ],
3362
- },
3363
- ],
3364
- expect.anything()
3365
- );
3366
- });
3367
-
3368
- it("filters files according to {minSize}", async () => {
3369
- const onDropSpy = jest.fn();
3370
-
3371
- const { container } = render(
3372
- <Dropzone onDrop={onDropSpy} minSize={1112}>
3373
- {({ getRootProps, getInputProps }) => (
3374
- <div {...getRootProps()}>
3375
- <input {...getInputProps()} />
3376
- </div>
3377
- )}
3378
- </Dropzone>
3379
- );
3380
- const dropzone = container.querySelector("div");
3381
-
3382
- await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3383
-
3384
- expect(onDropSpy).toHaveBeenCalledWith(
3385
- [],
3386
- [
3387
- {
3388
- file: files[0],
3389
- errors: [
3390
- {
3391
- code: "file-too-small",
3392
- message: "File is smaller than 1112 bytes",
3393
- },
3394
- ],
3395
- },
3396
- ],
3397
- expect.anything()
3398
- );
3399
- });
3400
- });
3401
-
3402
- describe("onFileDialogCancel", () => {
3403
- beforeEach(() => {
3404
- jest.useFakeTimers();
3405
- });
3406
-
3407
- afterEach(() => {
3408
- jest.useRealTimers();
3409
- });
3410
-
3411
- it("is not invoked every time window receives focus", () => {
3412
- const onFileDialogCancelSpy = jest.fn();
3413
-
3414
- render(
3415
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3416
- {({ getRootProps, getInputProps }) => (
3417
- <div {...getRootProps()}>
3418
- <input {...getInputProps()} />
3419
- </div>
3420
- )}
3421
- </Dropzone>
3422
- );
3423
-
3424
- focusWindow();
3425
- drainPendingTimers();
3426
-
3427
- expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3428
- });
3429
-
3430
- it("resets {isFileDialogActive}", () => {
3431
- const activeRef = createRef();
3432
- const active = <span ref={activeRef}>I am active</span>;
3433
- const onFileDialogCancelSpy = jest.fn();
3434
-
3435
- const { container } = render(
3436
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3437
- {({ getRootProps, getInputProps, isFileDialogActive }) => (
3438
- <div {...getRootProps()}>
3439
- <input {...getInputProps()} />
3440
- {isFileDialogActive && active}
3441
- </div>
3442
- )}
3443
- </Dropzone>
3444
- );
3445
-
3446
- const dropzone = container.querySelector("div");
3447
-
3448
- fireEvent.click(dropzone);
3449
-
3450
- expect(activeRef.current).not.toBeNull();
3451
- expect(dropzone).toContainElement(activeRef.current);
3452
-
3453
- focusWindow();
3454
- drainPendingTimers();
3455
-
3456
- expect(onFileDialogCancelSpy).toHaveBeenCalled();
3457
- expect(dropzone).not.toContainElement(activeRef.current);
3458
- });
3459
-
3460
- it("is not invoked if <input> is not rendered", () => {
3461
- const onFileDialogCancelSpy = jest.fn();
3462
- const { container, rerender } = render(
3463
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3464
- {({ getRootProps, getInputProps }) => (
3465
- <div {...getRootProps()}>
3466
- <input {...getInputProps()} />
3467
- </div>
3468
- )}
3469
- </Dropzone>
3470
- );
3471
-
3472
- fireEvent.click(container.querySelector("div"));
3473
-
3474
- // Remove the input, then on window focus nothing should happen because we check if the input ref is set
3475
- rerender(
3476
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3477
- {({ getRootProps }) => <div {...getRootProps()} />}
3478
- </Dropzone>
3479
- );
3480
-
3481
- focusWindow();
3482
- drainPendingTimers();
3483
-
3484
- expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3485
- });
3486
-
3487
- it("is not invoked if files were selected", async () => {
3488
- const onFileDialogCancelSpy = jest.fn();
3489
-
3490
- const { container } = render(
3491
- <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3492
- {({ getRootProps, getInputProps }) => (
3493
- <div {...getRootProps()}>
3494
- <input {...getInputProps()} />
3495
- </div>
3496
- )}
3497
- </Dropzone>
3498
- );
3499
-
3500
- await act(async () =>
3501
- fireEvent.change(container.querySelector("input"), {
3502
- target: { files },
3503
- })
3504
- );
3505
- fireEvent.click(container.querySelector("div"));
3506
-
3507
- focusWindow();
3508
- drainPendingTimers();
3509
-
3510
- expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3511
- });
3512
-
3513
- it("does not throw if callback is not provided", () => {
3514
- const { container } = render(
3515
- <Dropzone onFileDialogCancel={null}>
3516
- {({ getRootProps, getInputProps }) => (
3517
- <div {...getRootProps()}>
3518
- <input {...getInputProps()} />
3519
- </div>
3520
- )}
3521
- </Dropzone>
3522
- );
3523
-
3524
- fireEvent.click(container.querySelector("div"));
3525
-
3526
- const fn = () => {
3527
- focusWindow();
3528
- drainPendingTimers();
3529
- };
3530
- expect(fn).not.toThrow();
3531
- });
3532
- });
3533
-
3534
- describe("onFileDialogOpen", () => {
3535
- it("is invoked when opening the file dialog", () => {
3536
- const onFileDialogOpenSpy = jest.fn();
3537
- const { container } = render(
3538
- <Dropzone onFileDialogOpen={onFileDialogOpenSpy}>
3539
- {({ getRootProps, getInputProps }) => (
3540
- <div {...getRootProps()}>
3541
- <input {...getInputProps()} />
3542
- </div>
3543
- )}
3544
- </Dropzone>
3545
- );
3546
-
3547
- fireEvent.click(container.querySelector("div"));
3548
-
3549
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
3550
- });
3551
-
3552
- it("is invoked when opening the file dialog programmatically", () => {
3553
- const onFileDialogOpenSpy = jest.fn();
3554
- const { container } = render(
3555
- <Dropzone onFileDialogOpen={onFileDialogOpenSpy}>
3556
- {({ getRootProps, getInputProps, open }) => (
3557
- <div {...getRootProps()}>
3558
- <input {...getInputProps()} />
3559
- <button type="button" onClick={open}>
3560
- Open
3561
- </button>
3562
- </div>
3563
- )}
3564
- </Dropzone>
3565
- );
3566
-
3567
- fireEvent.click(container.querySelector("button"));
3568
-
3569
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
3570
- });
3571
- });
3572
-
3573
- describe("{open}", () => {
3574
- it("can open file dialog programmatically", () => {
3575
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
3576
- const { container } = render(
3577
- <Dropzone>
3578
- {({ getRootProps, getInputProps, open }) => (
3579
- <div {...getRootProps()}>
3580
- <input {...getInputProps()} />
3581
- <button type="button" onClick={open}>
3582
- Open
3583
- </button>
3584
- </div>
3585
- )}
3586
- </Dropzone>
3587
- );
3588
-
3589
- fireEvent.click(container.querySelector("button"));
3590
-
3591
- expect(onClickSpy).toHaveBeenCalled();
3592
- });
3593
-
3594
- it("sets {isFileDialogActive} state", () => {
3595
- const activeRef = createRef();
3596
- const active = <span ref={activeRef}>I am active</span>;
3597
- const { container } = render(
3598
- <Dropzone>
3599
- {({ getRootProps, getInputProps, isFileDialogActive, open }) => (
3600
- <div {...getRootProps()}>
3601
- <input {...getInputProps()} />
3602
- {isFileDialogActive && active}
3603
- <button type="button" onClick={open}>
3604
- Open
3605
- </button>
3606
- </div>
3607
- )}
3608
- </Dropzone>
3609
- );
3610
-
3611
- fireEvent.click(container.querySelector("button"));
3612
-
3613
- expect(activeRef.current).not.toBeNull();
3614
- expect(container.querySelector("div")).toContainElement(
3615
- activeRef.current
3616
- );
3617
- });
3618
-
3619
- it("does nothing if {disabled} is true", () => {
3620
- const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
3621
- const { container } = render(
3622
- <Dropzone disabled>
3623
- {({ getRootProps, getInputProps, open }) => (
3624
- <div {...getRootProps()}>
3625
- <input {...getInputProps()} />
3626
- <button type="button" onClick={open}>
3627
- Open
3628
- </button>
3629
- </div>
3630
- )}
3631
- </Dropzone>
3632
- );
3633
-
3634
- fireEvent.click(container.querySelector("button"));
3635
-
3636
- expect(onClickSpy).not.toHaveBeenCalled();
3637
- });
3638
-
3639
- it("does not throw if <input> is missing", () => {
3640
- const { container } = render(
3641
- <Dropzone>
3642
- {({ getRootProps, open }) => (
3643
- <div {...getRootProps()}>
3644
- <button type="button" onClick={open}>
3645
- Open
3646
- </button>
3647
- </div>
3648
- )}
3649
- </Dropzone>
3650
- );
3651
-
3652
- const fn = () => fireEvent.click(container.querySelector("button"));
3653
- expect(fn).not.toThrow();
3654
- });
3655
- });
3656
-
3657
- describe("validator", () => {
3658
- it("rejects with custom error", async () => {
3659
- const validator = (file) => {
3660
- if (/dogs/i.test(file.name))
3661
- return { code: "dogs-not-allowed", message: "Dogs not allowed" };
3662
-
3663
- return null;
3664
- };
3665
-
3666
- const onDropSpy = jest.fn();
3667
-
3668
- const { container } = render(
3669
- <Dropzone validator={validator} onDrop={onDropSpy} multiple={true}>
3670
- {({ getRootProps }) => <div {...getRootProps()} />}
3671
- </Dropzone>
3672
- );
3673
-
3674
- await act(() =>
3675
- fireEvent.drop(
3676
- container.querySelector("div"),
3677
- createDtWithFiles(images)
3678
- )
3679
- );
3680
-
3681
- expect(onDropSpy).toHaveBeenCalledWith(
3682
- [images[0]],
3683
- [
3684
- {
3685
- file: images[1],
3686
- errors: [
3687
- {
3688
- code: "dogs-not-allowed",
3689
- message: "Dogs not allowed",
3690
- },
3691
- ],
3692
- },
3693
- ],
3694
- expect.anything()
3695
- );
3696
- });
3697
-
3698
- it("sets {isDragAccept, isDragReject}", async () => {
3699
- const data = createDtWithFiles(images);
3700
- const validator = () => ({
3701
- code: "not-allowed",
3702
- message: "Cannot do this!",
3703
- });
3704
-
3705
- const ui = (
3706
- <Dropzone validator={validator} multiple={true}>
3707
- {({ getRootProps, getInputProps, isDragAccept, isDragReject }) => (
3708
- <div {...getRootProps()}>
3709
- <input {...getInputProps()} />
3710
- {isDragAccept && "dragAccept"}
3711
- {isDragReject && "dragReject"}
3712
- </div>
3713
- )}
3714
- </Dropzone>
3715
- );
3716
-
3717
- const { container } = render(ui);
3718
- const dropzone = container.querySelector("div");
3719
-
3720
- await act(() => fireEvent.dragEnter(dropzone, data));
3721
-
3722
- expect(dropzone).not.toHaveTextContent("dragAccept");
3723
- expect(dropzone).toHaveTextContent("dragReject");
3724
- });
3725
- });
3726
-
3727
- describe("accessibility", () => {
3728
- it("sets the role attribute to button by default on the root", () => {
3729
- const { container } = render(
3730
- <Dropzone>
3731
- {({ getRootProps }) => <div id="root" {...getRootProps()} />}
3732
- </Dropzone>
3733
- );
3734
-
3735
- expect(container.querySelector("#root")).toHaveAttribute(
3736
- "role",
3737
- "presentation"
3738
- );
3739
- });
3740
-
3741
- test("users can override the default role attribute on the root", () => {
3742
- const { container } = render(
3743
- <Dropzone>
3744
- {({ getRootProps }) => (
3745
- <div id="root" {...getRootProps({ role: "generic" })} />
3746
- )}
3747
- </Dropzone>
3748
- );
3749
-
3750
- expect(container.querySelector("#root")).toHaveAttribute(
3751
- "role",
3752
- "generic"
3753
- );
3754
- });
3755
- });
3756
- });
3757
-
3758
- /**
3759
- * drainPendingTimers just runs pending timers wrapped in act() to avoid
3760
- * getting warnings from react about side effects that happen async.
3761
- *
3762
- * This can be used whenever a setTimeout(), setInterval() or async operation is used
3763
- * which triggers a state update.
3764
- */
3765
- function drainPendingTimers() {
3766
- return act(() => jest.runOnlyPendingTimers());
3767
- }
3768
-
3769
- /**
3770
- * focusWindow triggers focus on the window
3771
- */
3772
- function focusWindow() {
3773
- return fireEvent.focus(document.body, { bubbles: true });
3774
- }
3775
-
3776
- /**
3777
- * createDtWithFiles creates a mock data transfer object that can be used for drop events
3778
- * @param {File[]} files
3779
- * @param {object} options
3780
- * @param {boolean} options.emptyTypes - If true, sets item types to empty string (simulates Chrome drag behavior)
3781
- */
3782
- function createDtWithFiles(files = [], options = {}) {
3783
- const { emptyTypes = false } = options;
3784
- return {
3785
- dataTransfer: {
3786
- files,
3787
- items: files.map((file) => ({
3788
- kind: "file",
3789
- size: file.size,
3790
- type: emptyTypes ? "" : file.type,
3791
- getAsFile: () => file,
3792
- })),
3793
- types: ["Files"],
3794
- },
3795
- };
3796
- }
3797
-
3798
- /**
3799
- * createFileSystemFileHandle creates a mock [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle)
3800
- * @param {File} file
3801
- */
3802
- function createFileSystemFileHandle(file) {
3803
- return { getFile: () => Promise.resolve(file) };
3804
- }
3805
-
3806
- /**
3807
- * createFile creates a mock File object
3808
- * @param {string} name
3809
- * @param {number} size
3810
- * @param {string} type
3811
- */
3812
- function createFile(name, size, type) {
3813
- const file = new File([], name, { type });
3814
- Object.defineProperty(file, "size", {
3815
- get() {
3816
- return size;
3817
- },
3818
- });
3819
- return file;
3820
- }
3821
-
3822
- /**
3823
- * createThenable creates a Promise that can be controlled from outside its inner scope
3824
- */
3825
- function createThenable() {
3826
- let done, cancel;
3827
-
3828
- const promise = new Promise((resolve, reject) => {
3829
- done = resolve;
3830
- cancel = reject;
3831
- });
3832
-
3833
- return {
3834
- promise,
3835
- done,
3836
- cancel,
3837
- };
3838
- }