react-dropzone 13.0.0 → 14.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.
package/src/index.spec.js CHANGED
@@ -1,12 +1,6 @@
1
1
  /* eslint react/prop-types: 0, jsx-a11y/label-has-for: 0 */
2
2
  import React, { createRef } from "react";
3
- import {
4
- act,
5
- cleanup,
6
- fireEvent,
7
- render,
8
- waitFor,
9
- } from "@testing-library/react";
3
+ import { act, cleanup, fireEvent, render } from "@testing-library/react";
10
4
  import { renderHook } from "@testing-library/react-hooks";
11
5
  import { fromEvent } from "file-selector";
12
6
  import * as utils from "./utils";
@@ -54,9 +48,10 @@ describe("useDropzone() hook", () => {
54
48
  </Dropzone>
55
49
  );
56
50
 
57
- const input = container.querySelector("input");
58
-
59
- expect(input).toHaveAttribute("accept", "image/jpeg");
51
+ expect(container.querySelector("input")).toHaveAttribute(
52
+ "accept",
53
+ "image/jpeg"
54
+ );
60
55
  });
61
56
 
62
57
  it("updates {multiple} prop on the <input> when it changes", () => {
@@ -110,8 +105,7 @@ describe("useDropzone() hook", () => {
110
105
  </Dropzone>
111
106
  );
112
107
 
113
- const input = container.querySelector("input");
114
- expect(input).toHaveAttribute("multiple");
108
+ expect(container.querySelector("input")).toHaveAttribute("multiple");
115
109
  });
116
110
 
117
111
  it("updates {multiple} prop on the <input> when it changes", () => {
@@ -152,8 +146,7 @@ describe("useDropzone() hook", () => {
152
146
  </Dropzone>
153
147
  );
154
148
 
155
- const input = container.querySelector("input");
156
- expect(input).toHaveAttribute("name", name);
149
+ expect(container.querySelector("input")).toHaveAttribute("name", name);
157
150
  });
158
151
 
159
152
  it("sets any props passed to the root props getter on the root node", () => {
@@ -168,9 +161,10 @@ describe("useDropzone() hook", () => {
168
161
  </Dropzone>
169
162
  );
170
163
 
171
- const dropzone = container.querySelector("div");
172
-
173
- expect(dropzone).toHaveAttribute("aria-label", ariaLabel);
164
+ expect(container.querySelector("div")).toHaveAttribute(
165
+ "aria-label",
166
+ ariaLabel
167
+ );
174
168
  });
175
169
 
176
170
  it("runs the custom callback handlers provided to the root props getter", async () => {
@@ -187,7 +181,7 @@ describe("useDropzone() hook", () => {
187
181
  onDrop: jest.fn(),
188
182
  };
189
183
 
190
- const ui = (
184
+ const { container } = render(
191
185
  <Dropzone>
192
186
  {({ getRootProps, getInputProps }) => (
193
187
  <div {...getRootProps(rootProps)}>
@@ -197,8 +191,6 @@ describe("useDropzone() hook", () => {
197
191
  </Dropzone>
198
192
  );
199
193
 
200
- const { container, rerender } = render(ui);
201
-
202
194
  const dropzone = container.querySelector("div");
203
195
 
204
196
  fireEvent.click(dropzone);
@@ -212,8 +204,7 @@ describe("useDropzone() hook", () => {
212
204
  fireEvent.blur(dropzone);
213
205
  expect(rootProps.onBlur).toHaveBeenCalled();
214
206
 
215
- fireEvent.dragEnter(dropzone, event);
216
- await flushPromises(rerender, ui);
207
+ await act(() => fireEvent.dragEnter(dropzone, event));
217
208
  expect(rootProps.onDragEnter).toHaveBeenCalled();
218
209
 
219
210
  fireEvent.dragOver(dropzone, event);
@@ -222,8 +213,7 @@ describe("useDropzone() hook", () => {
222
213
  fireEvent.dragLeave(dropzone, event);
223
214
  expect(rootProps.onDragLeave).toHaveBeenCalled();
224
215
 
225
- fireEvent.drop(dropzone, event);
226
- await flushPromises(rerender, ui);
216
+ await act(() => fireEvent.drop(dropzone, event));
227
217
  expect(rootProps.onDrop).toHaveBeenCalled();
228
218
  });
229
219
 
@@ -233,7 +223,7 @@ describe("useDropzone() hook", () => {
233
223
  onChange: jest.fn(),
234
224
  };
235
225
 
236
- const ui = (
226
+ const { container } = render(
237
227
  <Dropzone>
238
228
  {({ getRootProps, getInputProps }) => (
239
229
  <div {...getRootProps()}>
@@ -243,20 +233,16 @@ describe("useDropzone() hook", () => {
243
233
  </Dropzone>
244
234
  );
245
235
 
246
- const { container, rerender } = render(ui);
247
-
248
236
  const input = container.querySelector("input");
249
237
 
250
238
  fireEvent.click(input);
251
- await flushPromises(rerender, ui);
252
239
  expect(inputProps.onClick).toHaveBeenCalled();
253
240
 
254
- fireEvent.change(input);
255
- await flushPromises(rerender, ui);
241
+ await act(async () => fireEvent.change(input, { target: { files: [] } }));
256
242
  expect(inputProps.onChange).toHaveBeenCalled();
257
243
  });
258
244
 
259
- it("runs no callback handlers if {disabled} is true", () => {
245
+ it("runs no callback handlers if {disabled} is true", async () => {
260
246
  const event = createDtWithFiles(files);
261
247
 
262
248
  const rootProps = {
@@ -298,7 +284,7 @@ describe("useDropzone() hook", () => {
298
284
  fireEvent.blur(dropzone);
299
285
  expect(rootProps.onBlur).not.toHaveBeenCalled();
300
286
 
301
- fireEvent.dragEnter(dropzone, event);
287
+ await act(() => fireEvent.dragEnter(dropzone, event));
302
288
  expect(rootProps.onDragEnter).not.toHaveBeenCalled();
303
289
 
304
290
  fireEvent.dragOver(dropzone, event);
@@ -307,7 +293,7 @@ describe("useDropzone() hook", () => {
307
293
  fireEvent.dragLeave(dropzone, event);
308
294
  expect(rootProps.onDragLeave).not.toHaveBeenCalled();
309
295
 
310
- fireEvent.drop(dropzone, event);
296
+ await act(() => fireEvent.drop(dropzone, event));
311
297
  expect(rootProps.onDrop).not.toHaveBeenCalled();
312
298
 
313
299
  const input = container.querySelector("input");
@@ -315,7 +301,7 @@ describe("useDropzone() hook", () => {
315
301
  fireEvent.click(input);
316
302
  expect(inputProps.onClick).not.toHaveBeenCalled();
317
303
 
318
- fireEvent.change(input);
304
+ await act(() => fireEvent.change(input));
319
305
  expect(inputProps.onChange).not.toHaveBeenCalled();
320
306
  });
321
307
 
@@ -535,8 +521,8 @@ describe("useDropzone() hook", () => {
535
521
  const dropzone = container.querySelector("#dropzone");
536
522
 
537
523
  const fn = async () => {
538
- fireDrop(dropzone, data);
539
- await flushPromises(rerender, ui);
524
+ await act(() => fireEvent.drop(dropzone, data));
525
+ rerender(ui);
540
526
  done();
541
527
  };
542
528
 
@@ -561,13 +547,10 @@ describe("useDropzone() hook", () => {
561
547
 
562
548
  const dropzone = container.querySelector("label");
563
549
 
564
- const event = new Event("click", { bubbles: true, cancelable: true });
550
+ fireEvent.click(dropzone, { bubbles: true, cancelable: true });
565
551
 
566
- fireEvent(dropzone, event);
567
-
568
- const ref = activeRef.current;
569
- expect(ref).not.toBeNull();
570
- expect(dropzone).toContainElement(ref);
552
+ expect(activeRef.current).not.toBeNull();
553
+ expect(dropzone).toContainElement(activeRef.current);
571
554
  expect(onClickSpy).toHaveBeenCalledTimes(1);
572
555
  });
573
556
  });
@@ -674,12 +657,11 @@ describe("useDropzone() hook", () => {
674
657
  )}
675
658
  </Dropzone>
676
659
  );
677
- const dropzone = container.querySelector("div");
678
660
 
679
661
  const dropEvt = new Event("drop", { bubbles: true });
680
662
  const dropEvtPreventDefaultSpy = jest.spyOn(dropEvt, "preventDefault");
681
663
 
682
- fireEvent(dropzone, dropEvt);
664
+ fireEvent(container.querySelector("div"), dropEvt);
683
665
  // A call is from the onDrop handler for the dropzone,
684
666
  // but there should be no more than 1
685
667
  expect(dropEvtPreventDefaultSpy).toHaveBeenCalled();
@@ -731,7 +713,7 @@ describe("useDropzone() hook", () => {
731
713
  onDrop: jest.fn(),
732
714
  };
733
715
 
734
- const ui = (
716
+ const { container } = render(
735
717
  <Dropzone {...parentProps}>
736
718
  {({ getRootProps, getInputProps }) => (
737
719
  <div {...getRootProps()}>
@@ -742,25 +724,21 @@ describe("useDropzone() hook", () => {
742
724
  </Dropzone>
743
725
  );
744
726
 
745
- const { container, rerender } = render(ui);
746
-
747
727
  const innerDropzone = container.querySelector("#inner-dropzone");
748
728
 
749
- fireDragEnter(innerDropzone, data);
750
- await flushPromises(rerender, ui);
729
+ await act(() => fireEvent.dragEnter(innerDropzone, data));
751
730
  expect(innerProps.onDragEnter).toHaveBeenCalled();
752
731
  expect(parentProps.onDragEnter).toHaveBeenCalled();
753
732
 
754
- fireDragOver(innerDropzone, data);
733
+ fireEvent.dragOver(innerDropzone, data);
755
734
  expect(innerProps.onDragOver).toHaveBeenCalled();
756
735
  expect(parentProps.onDragOver).toHaveBeenCalled();
757
736
 
758
- fireDragLeave(innerDropzone, data);
737
+ fireEvent.dragLeave(innerDropzone, data);
759
738
  expect(innerProps.onDragLeave).toHaveBeenCalled();
760
739
  expect(parentProps.onDragLeave).toHaveBeenCalled();
761
740
 
762
- fireDrop(innerDropzone, data);
763
- await flushPromises(rerender, ui);
741
+ await act(() => fireEvent.drop(innerDropzone, data));
764
742
  expect(innerProps.onDrop).toHaveBeenCalled();
765
743
  expect(parentProps.onDrop).toHaveBeenCalled();
766
744
  });
@@ -797,7 +775,7 @@ describe("useDropzone() hook", () => {
797
775
  onDrop: jest.fn(),
798
776
  };
799
777
 
800
- const ui = (
778
+ const { container } = render(
801
779
  <Dropzone {...parentProps}>
802
780
  {({ getRootProps, getInputProps }) => (
803
781
  <div {...getRootProps()}>
@@ -808,25 +786,21 @@ describe("useDropzone() hook", () => {
808
786
  </Dropzone>
809
787
  );
810
788
 
811
- const { container, rerender } = render(ui);
812
-
813
789
  const innerDropzone = container.querySelector("#inner-dropzone");
814
790
 
815
- fireDragEnter(innerDropzone, data);
816
- await flushPromises(rerender, ui);
791
+ await act(() => fireEvent.dragEnter(innerDropzone, data));
817
792
  expect(innerProps.onDragEnter).toHaveBeenCalled();
818
793
  expect(parentProps.onDragEnter).not.toHaveBeenCalled();
819
794
 
820
- fireDragOver(innerDropzone, data);
795
+ fireEvent.dragOver(innerDropzone, data);
821
796
  expect(innerProps.onDragOver).toHaveBeenCalled();
822
797
  expect(parentProps.onDragOver).not.toHaveBeenCalled();
823
798
 
824
- fireDragLeave(innerDropzone, data);
799
+ fireEvent.dragLeave(innerDropzone, data);
825
800
  expect(innerProps.onDragLeave).toHaveBeenCalled();
826
801
  expect(parentProps.onDragLeave).not.toHaveBeenCalled();
827
802
 
828
- fireDrop(innerDropzone, data);
829
- await flushPromises(rerender, ui);
803
+ await act(() => fireEvent.drop(innerDropzone, data));
830
804
  expect(innerProps.onDrop).toHaveBeenCalled();
831
805
  expect(parentProps.onDrop).not.toHaveBeenCalled();
832
806
  });
@@ -856,7 +830,7 @@ describe("useDropzone() hook", () => {
856
830
  onDrop: jest.fn(),
857
831
  };
858
832
 
859
- const ui = (
833
+ const { container } = render(
860
834
  <Dropzone {...parentProps}>
861
835
  {({ getRootProps, getInputProps }) => (
862
836
  <div id="outer-dropzone" {...getRootProps()}>
@@ -867,30 +841,25 @@ describe("useDropzone() hook", () => {
867
841
  </Dropzone>
868
842
  );
869
843
 
870
- const { container, rerender } = render(ui);
871
-
872
844
  const outerDropzone = container.querySelector("#outer-dropzone");
873
845
  const innerDropzone = container.querySelector("#inner-dropzone");
874
846
 
875
847
  // Sets drag targets on the outer dropzone
876
- fireDragEnter(outerDropzone, data);
877
- await flushPromises(rerender, ui);
848
+ await act(() => fireEvent.dragEnter(outerDropzone, data));
878
849
 
879
- fireDragEnter(innerDropzone, data);
880
- await flushPromises(rerender, ui);
850
+ await act(() => fireEvent.dragEnter(innerDropzone, data));
881
851
  expect(innerProps.onDragEnter).toHaveBeenCalled();
882
852
  expect(parentProps.onDragEnter).toHaveBeenCalledTimes(1);
883
853
 
884
- fireDragOver(innerDropzone, data);
854
+ fireEvent.dragOver(innerDropzone, data);
885
855
  expect(innerProps.onDragOver).toHaveBeenCalled();
886
856
  expect(parentProps.onDragOver).not.toHaveBeenCalled();
887
857
 
888
- fireDragLeave(innerDropzone, data);
858
+ fireEvent.dragLeave(innerDropzone, data);
889
859
  expect(innerProps.onDragLeave).toHaveBeenCalled();
890
860
  expect(parentProps.onDragLeave).not.toHaveBeenCalled();
891
861
 
892
- fireDrop(innerDropzone, data);
893
- await flushPromises(rerender, ui);
862
+ await act(() => fireEvent.drop(innerDropzone, data));
894
863
  expect(innerProps.onDrop).toHaveBeenCalled();
895
864
  expect(parentProps.onDrop).not.toHaveBeenCalled();
896
865
  });
@@ -908,7 +877,8 @@ describe("useDropzone() hook", () => {
908
877
  );
909
878
 
910
879
  const parentDragLeave = jest.fn();
911
- const ui = (
880
+
881
+ const { container } = render(
912
882
  <Dropzone onDragLeave={parentDragLeave}>
913
883
  {({ getRootProps, getInputProps }) => (
914
884
  <div id="parent-dropzone" {...getRootProps()}>
@@ -919,18 +889,14 @@ describe("useDropzone() hook", () => {
919
889
  </Dropzone>
920
890
  );
921
891
 
922
- const { container, rerender } = render(ui);
923
-
924
892
  const parentDropzone = container.querySelector("#parent-dropzone");
925
893
 
926
- fireDragEnter(parentDropzone, data);
927
- await flushPromises(rerender, ui);
894
+ await act(() => fireEvent.dragEnter(parentDropzone, data));
928
895
 
929
896
  const innerDropzone = container.querySelector("#inner-dropzone");
930
- fireDragEnter(innerDropzone, data);
931
- await flushPromises(rerender, ui);
897
+ await act(() => fireEvent.dragEnter(innerDropzone, data));
932
898
 
933
- fireDragLeave(innerDropzone, data);
899
+ fireEvent.dragLeave(innerDropzone, data);
934
900
  expect(innerDragLeave).toHaveBeenCalled();
935
901
  expect(parentDragLeave).not.toHaveBeenCalled();
936
902
  });
@@ -950,7 +916,7 @@ describe("useDropzone() hook", () => {
950
916
  onDrop: jest.fn(),
951
917
  };
952
918
 
953
- const ui = (
919
+ const { container } = render(
954
920
  <Dropzone {...props}>
955
921
  {({ getRootProps, getInputProps }) => (
956
922
  <div {...getRootProps()}>
@@ -959,23 +925,19 @@ describe("useDropzone() hook", () => {
959
925
  )}
960
926
  </Dropzone>
961
927
  );
962
- const { container, rerender } = render(ui);
963
928
  const dropzone = container.querySelector("div");
964
929
 
965
- fireDragEnter(dropzone, data);
966
- await flushPromises(rerender, ui);
930
+ await act(() => fireEvent.dragEnter(dropzone, data));
967
931
  expect(props.onDragEnter).toHaveBeenCalled();
968
932
 
969
- fireDragOver(dropzone, data);
933
+ fireEvent.dragOver(dropzone, data);
970
934
  expect(props.onDragOver).toHaveBeenCalled();
971
935
 
972
- fireDragLeave(dropzone, data);
936
+ fireEvent.dragLeave(dropzone, data);
973
937
  expect(props.onDragLeave).toHaveBeenCalled();
974
938
 
975
- fireDrop(dropzone, data);
976
- await flushPromises(rerender, ui);
939
+ await act(() => fireEvent.drop(dropzone, data));
977
940
  expect(props.onDrop).toHaveBeenCalled();
978
-
979
941
  expect(props.getFilesFromEvent).toHaveBeenCalledTimes(2);
980
942
  });
981
943
 
@@ -1000,15 +962,13 @@ describe("useDropzone() hook", () => {
1000
962
  )}
1001
963
  </Dropzone>
1002
964
  );
1003
- const { container, rerender } = render(ui);
965
+ const { container } = render(ui);
1004
966
  const dropzone = container.querySelector("div");
1005
967
 
1006
- fireDragEnter(dropzone, data);
1007
- await flushPromises(rerender, ui);
968
+ await act(() => fireEvent.dragEnter(dropzone, data));
1008
969
  expect(props.onDragEnter).not.toHaveBeenCalled();
1009
970
 
1010
- fireDrop(dropzone, data);
1011
- await flushPromises(rerender, ui);
971
+ await act(() => fireEvent.drop(dropzone, data));
1012
972
  expect(props.onDrop).not.toHaveBeenCalled();
1013
973
 
1014
974
  expect(props.getFilesFromEvent).toHaveBeenCalledTimes(2);
@@ -1263,9 +1223,8 @@ describe("useDropzone() hook", () => {
1263
1223
  const dropzone = container.querySelector("div");
1264
1224
 
1265
1225
  fireEvent.click(dropzone);
1266
- const ref = activeRef.current;
1267
- expect(ref).not.toBeNull();
1268
- expect(dropzone).toContainElement(ref);
1226
+ expect(activeRef.current).not.toBeNull();
1227
+ expect(dropzone).toContainElement(activeRef.current);
1269
1228
  expect(onClickSpy).toHaveBeenCalled();
1270
1229
  });
1271
1230
 
@@ -1283,9 +1242,7 @@ describe("useDropzone() hook", () => {
1283
1242
  </Dropzone>
1284
1243
  );
1285
1244
 
1286
- const dropzone = container.querySelector("div");
1287
-
1288
- fireEvent.click(dropzone);
1245
+ fireEvent.click(container.querySelector("div"));
1289
1246
  expect(onClickSpy).not.toHaveBeenCalled();
1290
1247
  });
1291
1248
 
@@ -1301,9 +1258,7 @@ describe("useDropzone() hook", () => {
1301
1258
  </Dropzone>
1302
1259
  );
1303
1260
 
1304
- const dropzone = container.querySelector("div");
1305
-
1306
- fireEvent.click(dropzone);
1261
+ fireEvent.click(container.querySelector("div"));
1307
1262
  expect(onClickSpy).not.toHaveBeenCalled();
1308
1263
  });
1309
1264
 
@@ -1353,13 +1308,10 @@ describe("useDropzone() hook", () => {
1353
1308
  </Dropzone>
1354
1309
  );
1355
1310
 
1356
- const dropzone = container.querySelector("div");
1357
- const btn = container.querySelector("button");
1358
-
1359
- fireEvent.click(dropzone);
1311
+ fireEvent.click(container.querySelector("div"));
1360
1312
  expect(inputClickSpy).not.toHaveBeenCalled();
1361
1313
 
1362
- fireEvent.click(btn);
1314
+ fireEvent.click(container.querySelector("button"));
1363
1315
  expect(btnClickSpy).toHaveBeenCalled();
1364
1316
  });
1365
1317
 
@@ -1371,7 +1323,7 @@ describe("useDropzone() hook", () => {
1371
1323
  .mockReturnValueOnce(true);
1372
1324
  const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1373
1325
 
1374
- const ui = (
1326
+ const { container } = render(
1375
1327
  <Dropzone>
1376
1328
  {({ getRootProps, getInputProps }) => (
1377
1329
  <div {...getRootProps()}>
@@ -1381,36 +1333,29 @@ describe("useDropzone() hook", () => {
1381
1333
  </Dropzone>
1382
1334
  );
1383
1335
 
1384
- const { container } = render(ui);
1385
-
1386
- const dropzone = container.querySelector("div");
1387
-
1388
- fireEvent.click(dropzone);
1389
- drainTimers();
1336
+ fireEvent.click(container.querySelector("div"));
1337
+ drainPendingTimers();
1390
1338
 
1391
1339
  expect(onClickSpy).toHaveBeenCalled();
1392
1340
  jest.useRealTimers();
1393
1341
  isIeOrEdgeSpy.mockClear();
1394
1342
  });
1395
1343
 
1396
- it("should not use showOpenFilePicker() if supported and {useFsAccessApi} is not true", async () => {
1344
+ it("should not use showOpenFilePicker() if supported and {useFsAccessApi} is not true", () => {
1397
1345
  jest.useFakeTimers();
1398
1346
 
1399
1347
  const activeRef = createRef();
1400
1348
  const active = <span ref={activeRef}>I am active</span>;
1401
1349
  const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1402
1350
 
1403
- const handlers = files.map((f) => createFileSystemFileHandle(f));
1404
- const showOpenFilePickerMock = jest
1405
- .fn()
1406
- .mockReturnValue(Promise.resolve(handlers));
1351
+ const showOpenFilePickerMock = jest.fn();
1407
1352
 
1408
1353
  window.showOpenFilePicker = showOpenFilePickerMock;
1409
1354
 
1410
1355
  const onDropSpy = jest.fn();
1411
1356
  const onFileDialogOpenSpy = jest.fn();
1412
1357
 
1413
- const ui = (
1358
+ const { container } = render(
1414
1359
  <Dropzone
1415
1360
  onDrop={onDropSpy}
1416
1361
  onFileDialogOpen={onFileDialogOpenSpy}
@@ -1429,39 +1374,34 @@ describe("useDropzone() hook", () => {
1429
1374
  </Dropzone>
1430
1375
  );
1431
1376
 
1432
- const { container, rerender } = render(ui);
1433
-
1434
1377
  const dropzone = container.querySelector("div");
1435
1378
 
1436
1379
  fireEvent.click(dropzone);
1437
1380
 
1438
- await flushPromises(rerender, ui);
1439
-
1440
- dispatchEvt(document.body, "focus");
1441
- drainTimers();
1442
-
1381
+ expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1382
+ expect(onClickSpy).toHaveBeenCalled();
1443
1383
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
1384
+ expect(activeRef.current).not.toBeNull();
1385
+ expect(dropzone).toContainElement(activeRef.current);
1386
+
1387
+ focusWindow();
1388
+ drainPendingTimers();
1444
1389
 
1445
1390
  expect(activeRef.current).toBeNull();
1446
1391
  expect(dropzone).not.toContainElement(activeRef.current);
1447
- expect(onClickSpy).toHaveBeenCalled();
1448
- expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1449
1392
  expect(onDropSpy).not.toHaveBeenCalled();
1450
1393
 
1451
1394
  jest.useRealTimers();
1452
1395
  });
1453
1396
 
1454
- it("should not use showOpenFilePicker() if supported and {isSecureContext} is not true", async () => {
1397
+ it("should not use showOpenFilePicker() if supported and {isSecureContext} is not true", () => {
1455
1398
  jest.useFakeTimers();
1456
1399
 
1457
1400
  const activeRef = createRef();
1458
1401
  const active = <span ref={activeRef}>I am active</span>;
1459
1402
  const onClickSpy = jest.spyOn(HTMLInputElement.prototype, "click");
1460
1403
 
1461
- const handlers = files.map((f) => createFileSystemFileHandle(f));
1462
- const showOpenFilePickerMock = jest
1463
- .fn()
1464
- .mockReturnValue(Promise.resolve(handlers));
1404
+ const showOpenFilePickerMock = jest.fn();
1465
1405
 
1466
1406
  window.showOpenFilePicker = showOpenFilePickerMock;
1467
1407
  window.isSecureContext = false;
@@ -1469,7 +1409,7 @@ describe("useDropzone() hook", () => {
1469
1409
  const onDropSpy = jest.fn();
1470
1410
  const onFileDialogOpenSpy = jest.fn();
1471
1411
 
1472
- const ui = (
1412
+ const { container } = render(
1473
1413
  <Dropzone
1474
1414
  onDrop={onDropSpy}
1475
1415
  onFileDialogOpen={onFileDialogOpenSpy}
@@ -1487,23 +1427,21 @@ describe("useDropzone() hook", () => {
1487
1427
  </Dropzone>
1488
1428
  );
1489
1429
 
1490
- const { container, rerender } = render(ui);
1491
-
1492
1430
  const dropzone = container.querySelector("div");
1493
1431
 
1494
1432
  fireEvent.click(dropzone);
1495
1433
 
1496
- await flushPromises(rerender, ui);
1497
-
1498
- dispatchEvt(document.body, "focus");
1499
- drainTimers();
1500
-
1434
+ expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1435
+ expect(onClickSpy).toHaveBeenCalled();
1501
1436
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
1437
+ expect(activeRef.current).not.toBeNull();
1438
+ expect(dropzone).toContainElement(activeRef.current);
1439
+
1440
+ focusWindow();
1441
+ drainPendingTimers();
1502
1442
 
1503
1443
  expect(activeRef.current).toBeNull();
1504
1444
  expect(dropzone).not.toContainElement(activeRef.current);
1505
- expect(onClickSpy).toHaveBeenCalled();
1506
- expect(showOpenFilePickerMock).not.toHaveBeenCalled();
1507
1445
  expect(onDropSpy).not.toHaveBeenCalled();
1508
1446
 
1509
1447
  jest.useRealTimers();
@@ -1527,7 +1465,7 @@ describe("useDropzone() hook", () => {
1527
1465
  const onDropSpy = jest.fn();
1528
1466
  const onFileDialogOpenSpy = jest.fn();
1529
1467
 
1530
- const ui = (
1468
+ const { container } = render(
1531
1469
  <Dropzone
1532
1470
  onDrop={onDropSpy}
1533
1471
  onFileDialogOpen={onFileDialogOpenSpy}
@@ -1546,24 +1484,10 @@ describe("useDropzone() hook", () => {
1546
1484
  </Dropzone>
1547
1485
  );
1548
1486
 
1549
- const { container, rerender } = render(ui);
1550
-
1551
1487
  const dropzone = container.querySelector("div");
1552
1488
 
1553
1489
  fireEvent.click(dropzone);
1554
1490
 
1555
- await flushPromises(rerender, ui);
1556
-
1557
- expect(activeRef.current).not.toBeNull();
1558
- expect(dropzone).toContainElement(activeRef.current);
1559
- expect(onFileDialogOpenSpy).toHaveBeenCalled();
1560
-
1561
- thenable.done(handlers);
1562
- await flushPromises(rerender, ui);
1563
-
1564
- expect(activeRef.current).toBeNull(); // We expect the dialog to be closed at this point
1565
- expect(dropzone).not.toContainElement(activeRef.current);
1566
- expect(onClickSpy).not.toHaveBeenCalled();
1567
1491
  expect(showOpenFilePickerMock).toHaveBeenCalledWith({
1568
1492
  multiple: true,
1569
1493
  types: [
@@ -1572,6 +1496,17 @@ describe("useDropzone() hook", () => {
1572
1496
  },
1573
1497
  ],
1574
1498
  });
1499
+ expect(onClickSpy).not.toHaveBeenCalled();
1500
+ expect(onFileDialogOpenSpy).toHaveBeenCalled();
1501
+
1502
+ expect(activeRef.current).not.toBeNull();
1503
+ expect(dropzone).toContainElement(activeRef.current);
1504
+
1505
+ await act(() => thenable.done(handlers));
1506
+
1507
+ expect(activeRef.current).toBeNull();
1508
+ expect(dropzone).not.toContainElement(activeRef.current);
1509
+
1575
1510
  expect(onDropSpy).toHaveBeenCalledWith(files, [], null);
1576
1511
  });
1577
1512
 
@@ -1580,16 +1515,17 @@ describe("useDropzone() hook", () => {
1580
1515
  const active = <span ref={activeRef}>I am active</span>;
1581
1516
 
1582
1517
  const handlers = files.map((f) => createFileSystemFileHandle(f));
1518
+ const thenable = createThenable();
1583
1519
  const showOpenFilePickerMock = jest
1584
1520
  .fn()
1585
- .mockReturnValue(Promise.resolve(handlers));
1521
+ .mockReturnValue(thenable.promise);
1586
1522
 
1587
1523
  window.showOpenFilePicker = showOpenFilePickerMock;
1588
1524
 
1589
1525
  const onDropSpy = jest.fn();
1590
1526
  const onFileDialogOpenSpy = jest.fn();
1591
1527
 
1592
- const ui = (
1528
+ const { container } = render(
1593
1529
  <Dropzone
1594
1530
  onDrop={onDropSpy}
1595
1531
  onFileDialogOpen={onFileDialogOpenSpy}
@@ -1601,36 +1537,35 @@ describe("useDropzone() hook", () => {
1601
1537
  </Dropzone>
1602
1538
  );
1603
1539
 
1604
- const { container, rerender } = render(ui);
1605
-
1606
1540
  const dropzone = container.querySelector("div");
1607
1541
 
1608
1542
  fireEvent.click(dropzone);
1609
- await flushPromises(rerender, ui);
1610
- const ref = activeRef.current;
1611
- expect(ref).toBeNull(); // We expect the dialog to be closed at this point
1612
- expect(dropzone).not.toContainElement(ref);
1543
+
1613
1544
  expect(showOpenFilePickerMock).toHaveBeenCalled();
1614
- expect(onDropSpy).toHaveBeenCalledWith(files, [], null);
1615
1545
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
1546
+
1547
+ await act(() => thenable.done(handlers));
1548
+
1549
+ expect(activeRef.current).toBeNull();
1550
+ expect(dropzone).not.toContainElement(activeRef.current);
1551
+ expect(onDropSpy).toHaveBeenCalledWith(files, [], null);
1616
1552
  });
1617
1553
 
1618
1554
  test("if showOpenFilePicker() is supported and {useFsAccessApi} is true, and the user cancels it should call onFileDialogCancel", async () => {
1619
1555
  const activeRef = createRef();
1620
1556
  const active = <span ref={activeRef}>I am active</span>;
1621
1557
 
1558
+ const thenable = createThenable();
1622
1559
  const showOpenFilePickerMock = jest
1623
1560
  .fn()
1624
- .mockReturnValue(
1625
- Promise.reject(new DOMException("user aborted request", "AbortError"))
1626
- );
1561
+ .mockReturnValue(thenable.promise);
1627
1562
 
1628
1563
  window.showOpenFilePicker = showOpenFilePickerMock;
1629
1564
 
1630
1565
  const onDropSpy = jest.fn();
1631
1566
  const onFileDialogCancelSpy = jest.fn();
1632
1567
 
1633
- const ui = (
1568
+ const { container } = render(
1634
1569
  <Dropzone
1635
1570
  onDrop={onDropSpy}
1636
1571
  onFileDialogCancel={onFileDialogCancelSpy}
@@ -1642,18 +1577,20 @@ describe("useDropzone() hook", () => {
1642
1577
  </Dropzone>
1643
1578
  );
1644
1579
 
1645
- const { container, rerender } = render(ui);
1646
-
1647
1580
  const dropzone = container.querySelector("div");
1648
1581
 
1649
1582
  fireEvent.click(dropzone);
1650
- await flushPromises(rerender, ui);
1651
- const ref = activeRef.current;
1652
- expect(ref).toBeNull(); // We expect the dialog to be closed at this point
1653
- expect(dropzone).not.toContainElement(ref);
1583
+
1654
1584
  expect(showOpenFilePickerMock).toHaveBeenCalled();
1655
- expect(onDropSpy).not.toHaveBeenCalled();
1585
+
1586
+ await act(() =>
1587
+ thenable.cancel(new DOMException("user aborted request", "AbortError"))
1588
+ );
1589
+
1590
+ expect(activeRef.current).toBeNull();
1591
+ expect(dropzone).not.toContainElement(activeRef.current);
1656
1592
  expect(onFileDialogCancelSpy).toHaveBeenCalled();
1593
+ expect(onDropSpy).not.toHaveBeenCalled();
1657
1594
  });
1658
1595
 
1659
1596
  test("window focus evt is not bound if showOpenFilePicker() is supported and {useFsAccessApi} is true", async () => {
@@ -1670,44 +1607,32 @@ describe("useDropzone() hook", () => {
1670
1607
 
1671
1608
  window.showOpenFilePicker = showOpenFilePickerMock;
1672
1609
 
1673
- const ui = (
1674
- <Dropzone
1675
- onFileDialogCancel={onFileDialogCancelSpy}
1676
- noClick
1677
- useFsAccessApi
1678
- >
1679
- {({ getRootProps, isFileDialogActive, open }) => (
1680
- <div {...getRootProps()}>
1681
- {isFileDialogActive && active}
1682
- <button type="button" onClick={open}>
1683
- Open
1684
- </button>
1685
- </div>
1610
+ const { container } = render(
1611
+ <Dropzone onFileDialogCancel={onFileDialogCancelSpy} useFsAccessApi>
1612
+ {({ getRootProps, isFileDialogActive }) => (
1613
+ <div {...getRootProps()}>{isFileDialogActive && active}</div>
1686
1614
  )}
1687
1615
  </Dropzone>
1688
1616
  );
1689
1617
 
1690
- const { container, rerender } = render(ui);
1691
-
1692
1618
  const dropzone = container.querySelector("div");
1693
- const btn = container.querySelector("button");
1694
1619
 
1695
- btn.click();
1696
- drainTimers();
1697
- await flushPromises(rerender, ui);
1620
+ fireEvent.click(dropzone);
1698
1621
 
1699
- const ref = activeRef.current;
1700
- expect(ref).not.toBeNull();
1701
- expect(dropzone).toContainElement(ref);
1622
+ expect(activeRef.current).not.toBeNull();
1623
+ expect(dropzone).toContainElement(activeRef.current);
1702
1624
 
1703
- thenable.cancel(new DOMException("user aborted request", "AbortError"));
1625
+ await act(() =>
1626
+ thenable.cancel(new DOMException("user aborted request", "AbortError"))
1627
+ );
1704
1628
 
1705
- dispatchEvt(document.body, "focus");
1706
- drainTimers();
1707
- await flushPromises(rerender, ui);
1629
+ // Try to focus window and run timers
1630
+ focusWindow();
1631
+ drainPendingTimers();
1708
1632
 
1633
+ expect(activeRef.current).toBeNull();
1634
+ expect(dropzone).not.toContainElement(activeRef.current);
1709
1635
  expect(onFileDialogCancelSpy).toHaveBeenCalledTimes(1);
1710
- expect(dropzone).not.toContainElement(ref);
1711
1636
 
1712
1637
  jest.useRealTimers();
1713
1638
  });
@@ -1727,7 +1652,7 @@ describe("useDropzone() hook", () => {
1727
1652
  const onDropSpy = jest.fn();
1728
1653
  const onFileDialogOpenSpy = jest.fn();
1729
1654
 
1730
- const ui = (
1655
+ const { container } = render(
1731
1656
  <Dropzone
1732
1657
  onDrop={onDropSpy}
1733
1658
  onFileDialogOpen={onFileDialogOpenSpy}
@@ -1746,22 +1671,19 @@ describe("useDropzone() hook", () => {
1746
1671
  </Dropzone>
1747
1672
  );
1748
1673
 
1749
- const { container, rerender } = render(ui);
1750
-
1751
1674
  const dropzone = container.querySelector("div");
1752
1675
 
1753
1676
  fireEvent.click(dropzone);
1754
1677
 
1755
- await flushPromises(rerender, ui);
1756
-
1757
1678
  expect(activeRef.current).not.toBeNull();
1758
1679
  expect(dropzone).toContainElement(activeRef.current);
1759
1680
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
1760
1681
 
1761
- thenable.cancel(
1762
- new DOMException("Cannot use this API cross-origin", "SecurityError")
1682
+ await act(() =>
1683
+ thenable.cancel(
1684
+ new DOMException("Cannot use this API cross-origin", "SecurityError")
1685
+ )
1763
1686
  );
1764
- await flushPromises(rerender, ui);
1765
1687
 
1766
1688
  expect(activeRef.current).not.toBeNull();
1767
1689
  expect(dropzone).toContainElement(activeRef.current);
@@ -1782,7 +1704,7 @@ describe("useDropzone() hook", () => {
1782
1704
 
1783
1705
  window.showOpenFilePicker = showOpenFilePickerMock;
1784
1706
 
1785
- const ui = (
1707
+ const { container } = render(
1786
1708
  <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
1787
1709
  {({ getRootProps, getInputProps, isFileDialogActive }) => (
1788
1710
  <div {...getRootProps()}>
@@ -1793,31 +1715,24 @@ describe("useDropzone() hook", () => {
1793
1715
  </Dropzone>
1794
1716
  );
1795
1717
 
1796
- const { container, rerender } = render(ui);
1797
-
1798
1718
  const dropzone = container.querySelector("div");
1799
1719
 
1800
1720
  fireEvent.click(dropzone);
1801
1721
 
1802
- drainTimers();
1803
- await flushPromises(rerender, ui);
1804
-
1805
- const ref = activeRef.current;
1806
- expect(ref).not.toBeNull();
1807
- expect(dropzone).toContainElement(ref);
1722
+ expect(activeRef.current).not.toBeNull();
1723
+ expect(dropzone).toContainElement(activeRef.current);
1808
1724
 
1809
- thenable.cancel(
1810
- new DOMException("Cannot use this API cross-origin", "SecurityError")
1725
+ await act(() =>
1726
+ thenable.cancel(
1727
+ new DOMException("Cannot use this API cross-origin", "SecurityError")
1728
+ )
1811
1729
  );
1812
1730
 
1813
- await flushPromises(rerender, ui);
1814
-
1815
- dispatchEvt(document.body, "focus");
1816
- drainTimers();
1817
- await flushPromises(rerender, ui);
1731
+ focusWindow();
1732
+ drainPendingTimers();
1818
1733
 
1819
1734
  expect(onFileDialogCancelSpy).toHaveBeenCalled();
1820
- expect(dropzone).not.toContainElement(ref);
1735
+ expect(dropzone).not.toContainElement(activeRef.current);
1821
1736
 
1822
1737
  jest.useRealTimers();
1823
1738
  });
@@ -1857,22 +1772,18 @@ describe("useDropzone() hook", () => {
1857
1772
  </Dropzone>
1858
1773
  );
1859
1774
 
1860
- const { container, rerender } = render(ui);
1775
+ const { container } = render(ui);
1861
1776
 
1862
1777
  const dropzone = container.querySelector("div");
1863
1778
 
1864
1779
  fireEvent.click(dropzone);
1865
1780
 
1866
- await flushPromises(rerender, ui);
1867
-
1868
1781
  expect(activeRef.current).not.toBeNull();
1869
1782
  expect(dropzone).toContainElement(activeRef.current);
1870
1783
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
1871
1784
 
1872
1785
  const err = new Error("oops :(");
1873
- thenable.cancel(err);
1874
- await flushPromises(rerender, ui);
1875
-
1786
+ await act(() => thenable.cancel(err));
1876
1787
  expect(activeRef.current).not.toBeNull();
1877
1788
  expect(dropzone).toContainElement(activeRef.current);
1878
1789
  expect(onErrorSpy).toHaveBeenCalledWith(err);
@@ -2070,7 +1981,7 @@ describe("useDropzone() hook", () => {
2070
1981
  onDrop: jest.fn(),
2071
1982
  };
2072
1983
 
2073
- const ui = (
1984
+ const { container } = render(
2074
1985
  <Dropzone {...props}>
2075
1986
  {({ getRootProps, getInputProps }) => (
2076
1987
  <div {...getRootProps()}>
@@ -2079,21 +1990,18 @@ describe("useDropzone() hook", () => {
2079
1990
  )}
2080
1991
  </Dropzone>
2081
1992
  );
2082
- const { container, rerender } = render(ui);
2083
1993
  const dropzone = container.querySelector("div");
2084
1994
 
2085
- fireDragEnter(dropzone, data);
2086
- await flushPromises(rerender, ui);
1995
+ await act(() => fireEvent.dragEnter(dropzone, data));
2087
1996
  expect(props.onDragEnter).toHaveBeenCalled();
2088
1997
 
2089
- fireDragOver(dropzone, data);
1998
+ fireEvent.dragOver(dropzone, data);
2090
1999
  expect(props.onDragOver).toHaveBeenCalled();
2091
2000
 
2092
- fireDragLeave(dropzone, data);
2001
+ fireEvent.dragLeave(dropzone, data);
2093
2002
  expect(props.onDragLeave).toHaveBeenCalled();
2094
2003
 
2095
- fireDrop(dropzone, data);
2096
- await flushPromises(rerender, ui);
2004
+ await act(() => fireEvent.drop(dropzone, data));
2097
2005
  expect(props.onDrop).toHaveBeenCalled();
2098
2006
  });
2099
2007
 
@@ -2109,7 +2017,7 @@ describe("useDropzone() hook", () => {
2109
2017
  onDropRejected: jest.fn(),
2110
2018
  };
2111
2019
 
2112
- const ui = (
2020
+ const { container } = render(
2113
2021
  <Dropzone {...props}>
2114
2022
  {({ getRootProps, getInputProps }) => (
2115
2023
  <div {...getRootProps()}>
@@ -2118,22 +2026,19 @@ describe("useDropzone() hook", () => {
2118
2026
  )}
2119
2027
  </Dropzone>
2120
2028
  );
2121
- const { container, rerender } = render(ui);
2122
2029
  const dropzone = container.querySelector("div");
2123
2030
 
2124
- fireDragEnter(dropzone, emptyData);
2125
- await flushPromises(rerender, ui);
2031
+ await act(() => fireEvent.dragEnter(dropzone, emptyData));
2126
2032
  expect(props.onDragEnter).toHaveBeenCalled();
2127
2033
 
2128
- fireDragOver(dropzone, emptyData);
2034
+ fireEvent.dragOver(dropzone, emptyData);
2129
2035
  expect(props.onDragOver).toHaveBeenCalled();
2130
2036
 
2131
- fireDragLeave(dropzone, emptyData);
2037
+ fireEvent.dragLeave(dropzone, emptyData);
2132
2038
  expect(props.onDragLeave).toHaveBeenCalled();
2133
2039
 
2134
2040
  const data = createDtWithFiles(files);
2135
- fireDrop(dropzone, data);
2136
- await flushPromises(rerender, ui);
2041
+ await act(() => fireEvent.drop(dropzone, data));
2137
2042
  expect(props.onDrop).toHaveBeenCalled();
2138
2043
  expect(props.onDropAccepted).toHaveBeenCalledWith(
2139
2044
  files,
@@ -2159,7 +2064,7 @@ describe("useDropzone() hook", () => {
2159
2064
  onDropRejected: jest.fn(),
2160
2065
  };
2161
2066
 
2162
- const ui = (
2067
+ const { container } = render(
2163
2068
  <Dropzone {...props}>
2164
2069
  {({ getRootProps, getInputProps }) => (
2165
2070
  <div {...getRootProps()}>
@@ -2168,21 +2073,18 @@ describe("useDropzone() hook", () => {
2168
2073
  )}
2169
2074
  </Dropzone>
2170
2075
  );
2171
- const { container, rerender } = render(ui);
2172
2076
  const dropzone = container.querySelector("div");
2173
2077
 
2174
- fireDragEnter(dropzone, data);
2175
- await flushPromises(rerender, ui);
2078
+ await act(() => fireEvent.dragEnter(dropzone, data));
2176
2079
  expect(props.onDragEnter).not.toHaveBeenCalled();
2177
2080
 
2178
- fireDragOver(dropzone, data);
2081
+ fireEvent.dragOver(dropzone, data);
2179
2082
  expect(props.onDragOver).not.toHaveBeenCalled();
2180
2083
 
2181
- fireDragLeave(dropzone, data);
2084
+ fireEvent.dragLeave(dropzone, data);
2182
2085
  expect(props.onDragLeave).not.toHaveBeenCalled();
2183
2086
 
2184
- fireDrop(dropzone, data);
2185
- await flushPromises(rerender, ui);
2087
+ await act(() => fireEvent.drop(dropzone, data));
2186
2088
  expect(props.onDrop).not.toHaveBeenCalled();
2187
2089
  expect(props.onDropAccepted).not.toHaveBeenCalled();
2188
2090
  expect(props.onDropRejected).not.toHaveBeenCalled();
@@ -2200,7 +2102,7 @@ describe("useDropzone() hook", () => {
2200
2102
  onDropRejected: jest.fn(),
2201
2103
  };
2202
2104
 
2203
- const ui = (
2105
+ const { container } = render(
2204
2106
  <Dropzone {...props} noDrag>
2205
2107
  {({ getRootProps, getInputProps }) => (
2206
2108
  <div {...getRootProps()}>
@@ -2209,21 +2111,18 @@ describe("useDropzone() hook", () => {
2209
2111
  )}
2210
2112
  </Dropzone>
2211
2113
  );
2212
- const { container, rerender } = render(ui);
2213
2114
  const dropzone = container.querySelector("div");
2214
2115
 
2215
- fireDragEnter(dropzone, data);
2216
- await flushPromises(rerender, ui);
2116
+ await act(() => fireEvent.dragEnter(dropzone, data));
2217
2117
  expect(props.onDragEnter).not.toHaveBeenCalled();
2218
2118
 
2219
- fireDragOver(dropzone, data);
2119
+ fireEvent.dragOver(dropzone, data);
2220
2120
  expect(props.onDragOver).not.toHaveBeenCalled();
2221
2121
 
2222
- fireDragLeave(dropzone, data);
2122
+ fireEvent.dragLeave(dropzone, data);
2223
2123
  expect(props.onDragLeave).not.toHaveBeenCalled();
2224
2124
 
2225
- fireDrop(dropzone, data);
2226
- await flushPromises(rerender, ui);
2125
+ await act(() => fireEvent.drop(dropzone, data));
2227
2126
  expect(props.onDrop).not.toHaveBeenCalled();
2228
2127
  expect(props.onDropAccepted).not.toHaveBeenCalled();
2229
2128
  expect(props.onDropRejected).not.toHaveBeenCalled();
@@ -2239,7 +2138,7 @@ describe("useDropzone() hook", () => {
2239
2138
  onDrop: jest.fn(),
2240
2139
  };
2241
2140
 
2242
- const noDragUi = (
2141
+ const { container, rerender } = render(
2243
2142
  <Dropzone {...props} noDrag>
2244
2143
  {({ getRootProps, getInputProps }) => (
2245
2144
  <div {...getRootProps()}>
@@ -2248,24 +2147,21 @@ describe("useDropzone() hook", () => {
2248
2147
  )}
2249
2148
  </Dropzone>
2250
2149
  );
2251
- const { container, rerender } = render(noDragUi);
2252
2150
  const dropzone = container.querySelector("div");
2253
2151
 
2254
- fireDragEnter(dropzone, data);
2255
- await flushPromises(rerender, noDragUi);
2152
+ await act(() => fireEvent.dragEnter(dropzone, data));
2256
2153
  expect(props.onDragEnter).not.toHaveBeenCalled();
2257
2154
 
2258
- fireDragOver(dropzone, data);
2155
+ fireEvent.dragOver(dropzone, data);
2259
2156
  expect(props.onDragOver).not.toHaveBeenCalled();
2260
2157
 
2261
- fireDragLeave(dropzone, data);
2158
+ fireEvent.dragLeave(dropzone, data);
2262
2159
  expect(props.onDragLeave).not.toHaveBeenCalled();
2263
2160
 
2264
- fireDrop(dropzone, data);
2265
- await flushPromises(rerender, noDragUi);
2161
+ await act(() => fireEvent.drop(dropzone, data));
2266
2162
  expect(props.onDrop).not.toHaveBeenCalled();
2267
2163
 
2268
- const ui = (
2164
+ rerender(
2269
2165
  <Dropzone {...props} noDrag={false}>
2270
2166
  {({ getRootProps, getInputProps }) => (
2271
2167
  <div {...getRootProps()}>
@@ -2274,25 +2170,22 @@ describe("useDropzone() hook", () => {
2274
2170
  )}
2275
2171
  </Dropzone>
2276
2172
  );
2277
- rerender(ui);
2278
2173
 
2279
- fireDragEnter(dropzone, data);
2280
- await flushPromises(rerender, ui);
2174
+ await act(() => fireEvent.dragEnter(dropzone, data));
2281
2175
  expect(props.onDragEnter).toHaveBeenCalled();
2282
2176
 
2283
- fireDragOver(dropzone, data);
2177
+ fireEvent.dragOver(dropzone, data);
2284
2178
  expect(props.onDragOver).toHaveBeenCalled();
2285
2179
 
2286
- fireDragLeave(dropzone, data);
2180
+ fireEvent.dragLeave(dropzone, data);
2287
2181
  expect(props.onDragLeave).toHaveBeenCalled();
2288
2182
 
2289
- fireDrop(dropzone, data);
2290
- await flushPromises(rerender, ui);
2183
+ await act(() => fireEvent.drop(dropzone, data));
2291
2184
  expect(props.onDrop).toHaveBeenCalled();
2292
2185
  });
2293
2186
 
2294
2187
  it("sets {isDragActive} and {isDragAccept} if some files are accepted on dragenter", async () => {
2295
- const ui = (
2188
+ const { container } = render(
2296
2189
  <Dropzone>
2297
2190
  {({
2298
2191
  getRootProps,
@@ -2310,12 +2203,9 @@ describe("useDropzone() hook", () => {
2310
2203
  )}
2311
2204
  </Dropzone>
2312
2205
  );
2313
- const { container, rerender } = render(ui);
2314
2206
  const dropzone = container.querySelector("div");
2315
2207
 
2316
- const data = createDtWithFiles(files);
2317
- fireDragEnter(dropzone, data);
2318
- await flushPromises(rerender, ui);
2208
+ await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2319
2209
 
2320
2210
  expect(dropzone).toHaveTextContent("dragActive");
2321
2211
  expect(dropzone).toHaveTextContent("dragAccept");
@@ -2323,7 +2213,7 @@ describe("useDropzone() hook", () => {
2323
2213
  });
2324
2214
 
2325
2215
  it("sets {isDragActive} and {isDragReject} of some files are not accepted on dragenter", async () => {
2326
- const ui = (
2216
+ const { container } = render(
2327
2217
  <Dropzone
2328
2218
  accept={{
2329
2219
  "image/*": [],
@@ -2345,12 +2235,11 @@ describe("useDropzone() hook", () => {
2345
2235
  )}
2346
2236
  </Dropzone>
2347
2237
  );
2348
- const { container, rerender } = render(ui);
2349
2238
  const dropzone = container.querySelector("div");
2350
2239
 
2351
- const data = createDtWithFiles([...files, ...images]);
2352
- fireDragEnter(dropzone, data);
2353
- await flushPromises(rerender, ui);
2240
+ await act(() =>
2241
+ fireEvent.dragEnter(dropzone, createDtWithFiles([...files, ...images]))
2242
+ );
2354
2243
 
2355
2244
  expect(dropzone).toHaveTextContent("dragActive");
2356
2245
  expect(dropzone).not.toHaveTextContent("dragAccept");
@@ -2358,7 +2247,7 @@ describe("useDropzone() hook", () => {
2358
2247
  });
2359
2248
 
2360
2249
  it("sets {isDragReject} if some files are too large", async () => {
2361
- const ui = (
2250
+ const { container } = render(
2362
2251
  <Dropzone maxSize={0}>
2363
2252
  {({ getRootProps, getInputProps, isDragAccept, isDragReject }) => (
2364
2253
  <div {...getRootProps()}>
@@ -2369,19 +2258,16 @@ describe("useDropzone() hook", () => {
2369
2258
  )}
2370
2259
  </Dropzone>
2371
2260
  );
2372
- const { container, rerender } = render(ui);
2373
2261
  const dropzone = container.querySelector("div");
2374
2262
 
2375
- const data = createDtWithFiles(files);
2376
- fireDragEnter(dropzone, data);
2377
- await flushPromises(rerender, ui);
2263
+ await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2378
2264
 
2379
2265
  expect(dropzone).not.toHaveTextContent("dragAccept");
2380
2266
  expect(dropzone).toHaveTextContent("dragReject");
2381
2267
  });
2382
2268
 
2383
2269
  it("sets {isDragActive, isDragAccept, isDragReject} if any files are rejected and {multiple} is false on dragenter", async () => {
2384
- const ui = (
2270
+ const { container } = render(
2385
2271
  <Dropzone
2386
2272
  accept={{
2387
2273
  "image/*": [],
@@ -2404,12 +2290,9 @@ describe("useDropzone() hook", () => {
2404
2290
  )}
2405
2291
  </Dropzone>
2406
2292
  );
2407
- const { container, rerender } = render(ui);
2408
2293
  const dropzone = container.querySelector("div");
2409
2294
 
2410
- const data = createDtWithFiles(images);
2411
- fireDragEnter(dropzone, data);
2412
- await flushPromises(rerender, ui);
2295
+ await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(images)));
2413
2296
 
2414
2297
  expect(dropzone).toHaveTextContent("dragActive");
2415
2298
  expect(dropzone).not.toHaveTextContent("dragAccept");
@@ -2418,7 +2301,8 @@ describe("useDropzone() hook", () => {
2418
2301
 
2419
2302
  it("keeps {isDragActive} if dragleave is triggered for some arbitrary node", async () => {
2420
2303
  const { container: overlayContainer } = render(<div />);
2421
- const ui = (
2304
+
2305
+ const { container } = render(
2422
2306
  <Dropzone>
2423
2307
  {({
2424
2308
  getRootProps,
@@ -2436,87 +2320,20 @@ describe("useDropzone() hook", () => {
2436
2320
  )}
2437
2321
  </Dropzone>
2438
2322
  );
2439
- const { container, rerender } = render(ui);
2440
2323
  const dropzone = container.querySelector("div");
2441
2324
 
2442
- const data = createDtWithFiles(files);
2443
- fireDragEnter(dropzone, data);
2444
- await flushPromises(rerender, ui);
2325
+ await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(files)));
2445
2326
 
2446
- const event = new Event("dragleave", { bubbles: true });
2447
- Object.defineProperty(event, "target", {
2448
- value: overlayContainer.querySelector("div"),
2449
- writable: false,
2327
+ fireEvent.dragLeave(dropzone, {
2328
+ bubbles: true,
2329
+ target: overlayContainer.querySelector("div"),
2450
2330
  });
2451
- fireEvent(dropzone, event);
2452
-
2453
- expect(dropzone).toHaveTextContent("dragActive");
2454
- });
2455
-
2456
- it("updates {isDragActive} if {accept} changes mid-drag", async () => {
2457
- const ui = (
2458
- <Dropzone
2459
- accept={{
2460
- "image/*": [],
2461
- }}
2462
- >
2463
- {({
2464
- getRootProps,
2465
- getInputProps,
2466
- isDragActive,
2467
- isDragAccept,
2468
- isDragReject,
2469
- }) => (
2470
- <div {...getRootProps()}>
2471
- <input {...getInputProps()} />
2472
- {isDragActive && "dragActive"}
2473
- {isDragAccept && "dragAccept"}
2474
- {isDragReject && "dragReject"}
2475
- </div>
2476
- )}
2477
- </Dropzone>
2478
- );
2479
- const { container, rerender } = render(ui);
2480
- const dropzone = container.querySelector("div");
2481
-
2482
- const data = createDtWithFiles(images);
2483
- fireDragEnter(dropzone, data);
2484
- await flushPromises(rerender, ui);
2485
2331
 
2486
2332
  expect(dropzone).toHaveTextContent("dragActive");
2487
- expect(dropzone).toHaveTextContent("dragAccept");
2488
- expect(dropzone).not.toHaveTextContent("dragReject");
2489
-
2490
- rerender(
2491
- <Dropzone
2492
- accept={{
2493
- "text/*": [],
2494
- }}
2495
- >
2496
- {({
2497
- getRootProps,
2498
- getInputProps,
2499
- isDragActive,
2500
- isDragAccept,
2501
- isDragReject,
2502
- }) => (
2503
- <div {...getRootProps()}>
2504
- <input {...getInputProps()} />
2505
- {isDragActive && "dragActive"}
2506
- {isDragAccept && "dragAccept"}
2507
- {isDragReject && "dragReject"}
2508
- </div>
2509
- )}
2510
- </Dropzone>
2511
- );
2512
-
2513
- expect(dropzone).toHaveTextContent("dragActive");
2514
- expect(dropzone).not.toHaveTextContent("dragAccept");
2515
- expect(dropzone).toHaveTextContent("dragReject");
2516
2333
  });
2517
2334
 
2518
2335
  it("resets {isDragActive, isDragAccept, isDragReject} on dragleave", async () => {
2519
- const ui = (
2336
+ const { container } = render(
2520
2337
  <Dropzone
2521
2338
  accept={{
2522
2339
  "image/*": [],
@@ -2545,29 +2362,27 @@ describe("useDropzone() hook", () => {
2545
2362
  )}
2546
2363
  </Dropzone>
2547
2364
  );
2548
- const { container, rerender } = render(ui);
2549
2365
  const dropzone = container.querySelector("div");
2550
2366
 
2551
2367
  const data = createDtWithFiles(images);
2552
2368
 
2553
- fireDragEnter(container.querySelector("#child"), data);
2554
- fireDragEnter(dropzone, data);
2555
- await flushPromises(rerender, ui);
2556
- fireDragEnter(dropzone, data);
2557
- await flushPromises(rerender, ui);
2369
+ await act(() =>
2370
+ fireEvent.dragEnter(container.querySelector("#child"), data)
2371
+ );
2372
+ await act(() => fireEvent.dragEnter(dropzone, data));
2373
+
2374
+ await act(() => fireEvent.dragEnter(dropzone, data));
2558
2375
 
2559
2376
  expect(dropzone).toHaveTextContent("dragActive");
2560
2377
  expect(dropzone).toHaveTextContent("dragAccept");
2561
2378
  expect(dropzone).not.toHaveTextContent("dragReject");
2562
2379
 
2563
- fireDragLeave(dropzone, data);
2564
- await flushPromises(rerender, ui);
2380
+ fireEvent.dragLeave(dropzone, data);
2565
2381
  expect(dropzone).toHaveTextContent("dragActive");
2566
2382
  expect(dropzone).toHaveTextContent("dragAccept");
2567
2383
  expect(dropzone).not.toHaveTextContent("dragReject");
2568
2384
 
2569
- fireDragLeave(dropzone, data);
2570
- await flushPromises(rerender, ui);
2385
+ fireEvent.dragLeave(dropzone, data);
2571
2386
  expect(dropzone).not.toHaveTextContent("dragActive");
2572
2387
  expect(dropzone).not.toHaveTextContent("dragAccept");
2573
2388
  expect(dropzone).not.toHaveTextContent("dragReject");
@@ -2582,7 +2397,7 @@ describe("useDropzone() hook", () => {
2582
2397
  test("callback is invoked when <input> change event occurs", async () => {
2583
2398
  const onDropSpy = jest.fn();
2584
2399
 
2585
- const ui = (
2400
+ const { container } = render(
2586
2401
  <Dropzone onDrop={onDropSpy}>
2587
2402
  {({ getRootProps, getInputProps }) => (
2588
2403
  <div {...getRootProps()}>
@@ -2591,13 +2406,12 @@ describe("useDropzone() hook", () => {
2591
2406
  )}
2592
2407
  </Dropzone>
2593
2408
  );
2594
- const { container, rerender } = render(ui);
2595
- const input = container.querySelector("input");
2596
2409
 
2597
- Object.defineProperty(input, "files", { value: files });
2598
-
2599
- dispatchEvt(input, "change");
2600
- await flushPromises(rerender, ui);
2410
+ await act(async () =>
2411
+ fireEvent.change(container.querySelector("input"), {
2412
+ target: { files },
2413
+ })
2414
+ );
2601
2415
 
2602
2416
  expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything());
2603
2417
  });
@@ -2636,6 +2450,7 @@ describe("useDropzone() hook", () => {
2636
2450
  node.querySelectorAll(`[data-type="rejected"]`);
2637
2451
  const getRejectedFilesErrors = (node) =>
2638
2452
  node.querySelectorAll(`[data-type="error"]`);
2453
+
2639
2454
  const matchToFiles = (fileList, files) =>
2640
2455
  Array.from(fileList).every(
2641
2456
  (item) => !!files.find((file) => file.name === item.textContent)
@@ -2643,7 +2458,7 @@ describe("useDropzone() hook", () => {
2643
2458
  const matchToErrorCode = (errorList, code) =>
2644
2459
  Array.from(errorList).every((item) => item.textContent === code);
2645
2460
 
2646
- const ui = (
2461
+ const { container } = render(
2647
2462
  <Dropzone
2648
2463
  accept={{
2649
2464
  "image/*": [],
@@ -2658,17 +2473,16 @@ describe("useDropzone() hook", () => {
2658
2473
  )}
2659
2474
  </Dropzone>
2660
2475
  );
2661
- const { container, rerender } = render(ui);
2662
2476
  const dropzone = container.querySelector("div");
2663
2477
 
2664
- fireDrop(dropzone, createDtWithFiles(images));
2665
- await flushPromises(rerender, ui);
2478
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2479
+
2666
2480
  const acceptedFileList = getAcceptedFiles(dropzone);
2667
2481
  expect(acceptedFileList).toHaveLength(images.length);
2668
2482
  expect(matchToFiles(acceptedFileList, images)).toBe(true);
2669
2483
 
2670
- fireDrop(dropzone, createDtWithFiles(files));
2671
- await flushPromises(rerender, ui);
2484
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2485
+
2672
2486
  const rejectedFileList = getRejectedFiles(dropzone);
2673
2487
  expect(rejectedFileList).toHaveLength(files.length);
2674
2488
  expect(matchToFiles(rejectedFileList, files)).toBe(true);
@@ -2680,7 +2494,7 @@ describe("useDropzone() hook", () => {
2680
2494
  });
2681
2495
 
2682
2496
  it("resets {isDragActive, isDragAccept, isDragReject}", async () => {
2683
- const ui = (
2497
+ const { container } = render(
2684
2498
  <Dropzone>
2685
2499
  {({
2686
2500
  getRootProps,
@@ -2698,20 +2512,18 @@ describe("useDropzone() hook", () => {
2698
2512
  )}
2699
2513
  </Dropzone>
2700
2514
  );
2701
- const { container, rerender } = render(ui);
2702
2515
  const dropzone = container.querySelector("div");
2703
2516
 
2704
2517
  const data = createDtWithFiles(files);
2705
2518
 
2706
- fireDragEnter(dropzone, data);
2707
- await flushPromises(rerender, ui);
2519
+ await act(() => fireEvent.dragEnter(dropzone, data));
2708
2520
 
2709
2521
  expect(dropzone).toHaveTextContent("dragActive");
2710
2522
  expect(dropzone).toHaveTextContent("dragAccept");
2711
2523
  expect(dropzone).not.toHaveTextContent("dragReject");
2712
2524
 
2713
- fireDrop(dropzone, data);
2714
- await flushPromises(rerender, ui);
2525
+ await act(() => fireEvent.drop(dropzone, data));
2526
+
2715
2527
  expect(dropzone).not.toHaveTextContent("dragActive");
2716
2528
  expect(dropzone).not.toHaveTextContent("dragAccept");
2717
2529
  expect(dropzone).not.toHaveTextContent("dragReject");
@@ -2719,7 +2531,8 @@ describe("useDropzone() hook", () => {
2719
2531
 
2720
2532
  it("rejects all files if {multiple} is false and {accept} criteria is not met", async () => {
2721
2533
  const onDropSpy = jest.fn();
2722
- const ui = (
2534
+
2535
+ const { container } = render(
2723
2536
  <Dropzone
2724
2537
  accept={{
2725
2538
  "image/*": [],
@@ -2734,11 +2547,10 @@ describe("useDropzone() hook", () => {
2734
2547
  )}
2735
2548
  </Dropzone>
2736
2549
  );
2737
- const { container, rerender } = render(ui);
2738
2550
  const dropzone = container.querySelector("div");
2739
2551
 
2740
- fireDrop(dropzone, createDtWithFiles(files));
2741
- await flushPromises(rerender, ui);
2552
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2553
+
2742
2554
  expect(onDropSpy).toHaveBeenCalledWith(
2743
2555
  [],
2744
2556
  [
@@ -2758,7 +2570,8 @@ describe("useDropzone() hook", () => {
2758
2570
 
2759
2571
  it("rejects all files if {multiple} is false and {accept} criteria is met", async () => {
2760
2572
  const onDropSpy = jest.fn();
2761
- const ui = (
2573
+
2574
+ const { container } = render(
2762
2575
  <Dropzone
2763
2576
  accept={{
2764
2577
  "image/*": [],
@@ -2773,11 +2586,10 @@ describe("useDropzone() hook", () => {
2773
2586
  )}
2774
2587
  </Dropzone>
2775
2588
  );
2776
- const { container, rerender } = render(ui);
2777
2589
  const dropzone = container.querySelector("div");
2778
2590
 
2779
- fireDrop(dropzone, createDtWithFiles(images));
2780
- await flushPromises(rerender, ui);
2591
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2592
+
2781
2593
  expect(onDropSpy).toHaveBeenCalledWith(
2782
2594
  [],
2783
2595
  [
@@ -2807,7 +2619,8 @@ describe("useDropzone() hook", () => {
2807
2619
  it("rejects all files if {multiple} is true and maxFiles is less than files and {accept} criteria is met", async () => {
2808
2620
  const onDropSpy = jest.fn();
2809
2621
  const onDropRejectedSpy = jest.fn();
2810
- const ui = (
2622
+
2623
+ const { container } = render(
2811
2624
  <Dropzone
2812
2625
  accept={{
2813
2626
  "image/*": [],
@@ -2826,13 +2639,14 @@ describe("useDropzone() hook", () => {
2826
2639
  )}
2827
2640
  </Dropzone>
2828
2641
  );
2829
- const { container, rerender } = render(ui);
2830
2642
  const dropzone = container.querySelector("div");
2831
- fireDrop(dropzone, createDtWithFiles(images));
2832
- await flushPromises(rerender, ui);
2643
+
2644
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2645
+
2833
2646
  expect(onDropRejectedSpy).toHaveBeenCalled();
2834
- fireDragEnter(dropzone, createDtWithFiles(images));
2835
- await flushPromises(rerender, ui);
2647
+
2648
+ await act(() => fireEvent.dragEnter(dropzone, createDtWithFiles(images)));
2649
+
2836
2650
  expect(dropzone).toHaveTextContent("dragReject");
2837
2651
  expect(dropzone).not.toHaveTextContent("dragAccept");
2838
2652
  expect(onDropSpy).toHaveBeenCalledWith(
@@ -2884,15 +2698,17 @@ describe("useDropzone() hook", () => {
2884
2698
  const { container, rerender } = render(ui(3));
2885
2699
  const dropzone = container.querySelector("div");
2886
2700
 
2887
- fireDrop(dropzone, createDtWithFiles(images));
2888
- await flushPromises(rerender, ui(3));
2701
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2702
+ rerender(ui(3));
2703
+
2889
2704
  expect(onDropRejectedSpy).not.toHaveBeenCalled();
2890
2705
  expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
2891
2706
 
2892
2707
  rerender(ui(1));
2893
2708
 
2894
- fireDrop(dropzone, createDtWithFiles(images));
2895
- await flushPromises(rerender, ui(1));
2709
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2710
+ rerender(ui(1));
2711
+
2896
2712
  expect(onDropRejectedSpy).toHaveBeenCalledWith(
2897
2713
  expect.arrayContaining(
2898
2714
  images.map((image) =>
@@ -2906,7 +2722,8 @@ describe("useDropzone() hook", () => {
2906
2722
  it("accepts multiple files if {multiple} is true and {accept} criteria is met", async () => {
2907
2723
  const onDropSpy = jest.fn();
2908
2724
  const onDropRejectedSpy = jest.fn();
2909
- const ui = (
2725
+
2726
+ const { container } = render(
2910
2727
  <Dropzone
2911
2728
  accept={{
2912
2729
  "image/*": [],
@@ -2923,18 +2740,22 @@ describe("useDropzone() hook", () => {
2923
2740
  )}
2924
2741
  </Dropzone>
2925
2742
  );
2926
- const { container, rerender } = render(ui);
2927
- const dropzone = container.querySelector("div");
2928
2743
 
2929
- fireDrop(dropzone, createDtWithFiles(images));
2930
- await flushPromises(rerender, ui);
2744
+ await act(() =>
2745
+ fireEvent.drop(
2746
+ container.querySelector("div"),
2747
+ createDtWithFiles(images)
2748
+ )
2749
+ );
2750
+
2931
2751
  expect(onDropRejectedSpy).not.toHaveBeenCalled();
2932
2752
  expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
2933
2753
  });
2934
2754
 
2935
2755
  it("accepts a single files if {multiple} is false and {accept} criteria is met", async () => {
2936
2756
  const onDropSpy = jest.fn();
2937
- const ui = (
2757
+
2758
+ const { container } = render(
2938
2759
  <Dropzone
2939
2760
  accept={{
2940
2761
  "image/*": [],
@@ -2949,18 +2770,22 @@ describe("useDropzone() hook", () => {
2949
2770
  )}
2950
2771
  </Dropzone>
2951
2772
  );
2952
- const { container, rerender } = render(ui);
2953
- const dropzone = container.querySelector("div");
2954
2773
 
2955
2774
  const [image] = images;
2956
- fireDrop(dropzone, createDtWithFiles([image]));
2957
- await flushPromises(rerender, ui);
2775
+ await act(() =>
2776
+ fireEvent.drop(
2777
+ container.querySelector("div"),
2778
+ createDtWithFiles([image])
2779
+ )
2780
+ );
2781
+
2958
2782
  expect(onDropSpy).toHaveBeenCalledWith([image], [], expect.anything());
2959
2783
  });
2960
2784
 
2961
2785
  it("accepts all files if {multiple} is true", async () => {
2962
2786
  const onDropSpy = jest.fn();
2963
- const ui = (
2787
+
2788
+ const { container } = render(
2964
2789
  <Dropzone onDrop={onDropSpy}>
2965
2790
  {({ getRootProps, getInputProps }) => (
2966
2791
  <div {...getRootProps()}>
@@ -2969,11 +2794,11 @@ describe("useDropzone() hook", () => {
2969
2794
  )}
2970
2795
  </Dropzone>
2971
2796
  );
2972
- const { container, rerender } = render(ui);
2973
- const dropzone = container.querySelector("div");
2974
2797
 
2975
- fireDrop(dropzone, createDtWithFiles(files));
2976
- await flushPromises(rerender, ui);
2798
+ await act(() =>
2799
+ fireEvent.drop(container.querySelector("div"), createDtWithFiles(files))
2800
+ );
2801
+
2977
2802
  expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything());
2978
2803
  });
2979
2804
 
@@ -2982,37 +2807,34 @@ describe("useDropzone() hook", () => {
2982
2807
  const activeRef = createRef();
2983
2808
  const active = <span ref={activeRef}>I am active</span>;
2984
2809
 
2985
- const ui = (
2810
+ const { container } = render(
2986
2811
  <Dropzone onDrop={onDropSpy}>
2987
- {({ getRootProps, getInputProps, isFileDialogActive, open }) => (
2812
+ {({ getRootProps, getInputProps, isFileDialogActive }) => (
2988
2813
  <div {...getRootProps()}>
2989
2814
  <input {...getInputProps()} />
2990
2815
  {isFileDialogActive && active}
2991
- <button type="button" onClick={open}>
2992
- Open
2993
- </button>
2994
2816
  </div>
2995
2817
  )}
2996
2818
  </Dropzone>
2997
2819
  );
2998
- const { container, rerender } = render(ui);
2820
+
2999
2821
  const dropzone = container.querySelector("div");
3000
- const btn = container.querySelector("button");
3001
2822
 
3002
- btn.click();
2823
+ fireEvent.click(dropzone);
3003
2824
 
3004
- const ref = activeRef.current;
3005
- expect(dropzone).toContainElement(ref);
2825
+ expect(activeRef.current).not.toBeNull();
2826
+ expect(dropzone).toContainElement(activeRef.current);
3006
2827
 
3007
- fireDrop(dropzone, createDtWithFiles(files));
3008
- await flushPromises(rerender, ui);
2828
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3009
2829
 
3010
- expect(dropzone).not.toContainElement(ref);
2830
+ expect(activeRef.current).toBeNull();
2831
+ expect(dropzone).not.toContainElement(activeRef.current);
3011
2832
  });
3012
2833
 
3013
2834
  it("gets invoked with both accepted/rejected files", async () => {
3014
2835
  const onDropSpy = jest.fn();
3015
- const ui = (
2836
+
2837
+ const { container } = render(
3016
2838
  <Dropzone
3017
2839
  accept={{
3018
2840
  "image/*": [],
@@ -3026,11 +2848,10 @@ describe("useDropzone() hook", () => {
3026
2848
  )}
3027
2849
  </Dropzone>
3028
2850
  );
3029
- const { container, rerender } = render(ui);
3030
2851
  const dropzone = container.querySelector("div");
3031
2852
 
3032
- fireDrop(dropzone, createDtWithFiles(files));
3033
- await flushPromises(rerender, ui);
2853
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2854
+
3034
2855
  expect(onDropSpy).toHaveBeenCalledWith(
3035
2856
  [],
3036
2857
  [
@@ -3048,13 +2869,15 @@ describe("useDropzone() hook", () => {
3048
2869
  );
3049
2870
  onDropSpy.mockClear();
3050
2871
 
3051
- fireDrop(dropzone, createDtWithFiles(images));
3052
- await flushPromises(rerender, ui);
2872
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2873
+
3053
2874
  expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3054
2875
  onDropSpy.mockClear();
3055
2876
 
3056
- fireDrop(dropzone, createDtWithFiles([...files, ...images]));
3057
- await flushPromises(rerender, ui);
2877
+ await act(() =>
2878
+ fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
2879
+ );
2880
+
3058
2881
  expect(onDropSpy).toHaveBeenCalledWith(
3059
2882
  images,
3060
2883
  [
@@ -3074,7 +2897,8 @@ describe("useDropzone() hook", () => {
3074
2897
 
3075
2898
  test("onDropAccepted callback is invoked if some files are accepted", async () => {
3076
2899
  const onDropAcceptedSpy = jest.fn();
3077
- const ui = (
2900
+
2901
+ const { container } = render(
3078
2902
  <Dropzone
3079
2903
  accept={{
3080
2904
  "image/*": [],
@@ -3088,27 +2912,28 @@ describe("useDropzone() hook", () => {
3088
2912
  )}
3089
2913
  </Dropzone>
3090
2914
  );
3091
- const { container, rerender } = render(ui);
3092
2915
  const dropzone = container.querySelector("div");
3093
2916
 
3094
- fireDrop(dropzone, createDtWithFiles(files));
3095
- await flushPromises(rerender, ui);
2917
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3096
2918
  expect(onDropAcceptedSpy).not.toHaveBeenCalled();
3097
2919
  onDropAcceptedSpy.mockClear();
3098
2920
 
3099
- fireDrop(dropzone, createDtWithFiles(images));
3100
- await flushPromises(rerender, ui);
2921
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2922
+
3101
2923
  expect(onDropAcceptedSpy).toHaveBeenCalledWith(images, expect.anything());
3102
2924
  onDropAcceptedSpy.mockClear();
3103
2925
 
3104
- fireDrop(dropzone, createDtWithFiles([...files, ...images]));
3105
- await flushPromises(rerender, ui);
2926
+ await act(() =>
2927
+ fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
2928
+ );
2929
+
3106
2930
  expect(onDropAcceptedSpy).toHaveBeenCalledWith(images, expect.anything());
3107
2931
  });
3108
2932
 
3109
2933
  test("onDropRejected callback is invoked if some files are rejected", async () => {
3110
2934
  const onDropRejectedSpy = jest.fn();
3111
- const ui = (
2935
+
2936
+ const { container } = render(
3112
2937
  <Dropzone
3113
2938
  accept={{
3114
2939
  "image/*": [],
@@ -3122,11 +2947,10 @@ describe("useDropzone() hook", () => {
3122
2947
  )}
3123
2948
  </Dropzone>
3124
2949
  );
3125
- const { container, rerender } = render(ui);
3126
2950
  const dropzone = container.querySelector("div");
3127
2951
 
3128
- fireDrop(dropzone, createDtWithFiles(files));
3129
- await flushPromises(rerender, ui);
2952
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
2953
+
3130
2954
  expect(onDropRejectedSpy).toHaveBeenCalledWith(
3131
2955
  [
3132
2956
  {
@@ -3143,13 +2967,15 @@ describe("useDropzone() hook", () => {
3143
2967
  );
3144
2968
  onDropRejectedSpy.mockClear();
3145
2969
 
3146
- fireDrop(dropzone, createDtWithFiles(images));
3147
- await flushPromises(rerender, ui);
2970
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(images)));
2971
+
3148
2972
  expect(onDropRejectedSpy).not.toHaveBeenCalled();
3149
2973
  onDropRejectedSpy.mockClear();
3150
2974
 
3151
- fireDrop(dropzone, createDtWithFiles([...files, ...images]));
3152
- await flushPromises(rerender, ui);
2975
+ await act(() =>
2976
+ fireEvent.drop(dropzone, createDtWithFiles([...files, ...images]))
2977
+ );
2978
+
3153
2979
  expect(onDropRejectedSpy).toHaveBeenCalledWith(
3154
2980
  [
3155
2981
  {
@@ -3168,7 +2994,8 @@ describe("useDropzone() hook", () => {
3168
2994
 
3169
2995
  it("accepts a dropped image when Firefox provides a bogus file type", async () => {
3170
2996
  const onDropSpy = jest.fn();
3171
- const ui = (
2997
+
2998
+ const { container } = render(
3172
2999
  <Dropzone
3173
3000
  accept={{
3174
3001
  "image/*": [],
@@ -3182,19 +3009,22 @@ describe("useDropzone() hook", () => {
3182
3009
  )}
3183
3010
  </Dropzone>
3184
3011
  );
3185
- const { container, rerender } = render(ui);
3186
- const dropzone = container.querySelector("div");
3187
3012
 
3188
3013
  const images = [createFile("bogus.gif", 1234, "application/x-moz-file")];
3189
- fireDrop(dropzone, createDtWithFiles(images));
3190
- await flushPromises(rerender, ui);
3014
+ await act(() =>
3015
+ fireEvent.drop(
3016
+ container.querySelector("div"),
3017
+ createDtWithFiles(images)
3018
+ )
3019
+ );
3191
3020
 
3192
3021
  expect(onDropSpy).toHaveBeenCalledWith(images, [], expect.anything());
3193
3022
  });
3194
3023
 
3195
3024
  it("filters files according to {maxSize}", async () => {
3196
3025
  const onDropSpy = jest.fn();
3197
- const ui = (
3026
+
3027
+ const { container } = render(
3198
3028
  <Dropzone onDrop={onDropSpy} maxSize={1111}>
3199
3029
  {({ getRootProps, getInputProps }) => (
3200
3030
  <div {...getRootProps()}>
@@ -3203,11 +3033,13 @@ describe("useDropzone() hook", () => {
3203
3033
  )}
3204
3034
  </Dropzone>
3205
3035
  );
3206
- const { container, rerender } = render(ui);
3207
- const dropzone = container.querySelector("div");
3208
3036
 
3209
- fireDrop(dropzone, createDtWithFiles(images));
3210
- await flushPromises(rerender, ui);
3037
+ await act(() =>
3038
+ fireEvent.drop(
3039
+ container.querySelector("div"),
3040
+ createDtWithFiles(images)
3041
+ )
3042
+ );
3211
3043
 
3212
3044
  expect(onDropSpy).toHaveBeenCalledWith(
3213
3045
  [],
@@ -3237,7 +3069,8 @@ describe("useDropzone() hook", () => {
3237
3069
 
3238
3070
  it("filters files according to {minSize}", async () => {
3239
3071
  const onDropSpy = jest.fn();
3240
- const ui = (
3072
+
3073
+ const { container } = render(
3241
3074
  <Dropzone onDrop={onDropSpy} minSize={1112}>
3242
3075
  {({ getRootProps, getInputProps }) => (
3243
3076
  <div {...getRootProps()}>
@@ -3246,11 +3079,9 @@ describe("useDropzone() hook", () => {
3246
3079
  )}
3247
3080
  </Dropzone>
3248
3081
  );
3249
- const { container, rerender } = render(ui);
3250
3082
  const dropzone = container.querySelector("div");
3251
3083
 
3252
- fireDrop(dropzone, createDtWithFiles(files));
3253
- await flushPromises(rerender, ui);
3084
+ await act(() => fireEvent.drop(dropzone, createDtWithFiles(files)));
3254
3085
 
3255
3086
  expect(onDropSpy).toHaveBeenCalledWith(
3256
3087
  [],
@@ -3292,8 +3123,8 @@ describe("useDropzone() hook", () => {
3292
3123
  </Dropzone>
3293
3124
  );
3294
3125
 
3295
- dispatchEvt(document.body, "focus");
3296
- drainTimers();
3126
+ focusWindow();
3127
+ drainPendingTimers();
3297
3128
 
3298
3129
  expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3299
3130
  });
@@ -3305,91 +3136,78 @@ describe("useDropzone() hook", () => {
3305
3136
 
3306
3137
  const { container } = render(
3307
3138
  <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3308
- {({ getRootProps, getInputProps, isFileDialogActive, open }) => (
3139
+ {({ getRootProps, getInputProps, isFileDialogActive }) => (
3309
3140
  <div {...getRootProps()}>
3310
3141
  <input {...getInputProps()} />
3311
3142
  {isFileDialogActive && active}
3312
- <button type="button" onClick={open}>
3313
- Open
3314
- </button>
3315
3143
  </div>
3316
3144
  )}
3317
3145
  </Dropzone>
3318
3146
  );
3319
3147
 
3320
3148
  const dropzone = container.querySelector("div");
3321
- const btn = container.querySelector("button");
3322
3149
 
3323
- btn.click();
3324
- drainTimers();
3325
- const ref = activeRef.current;
3326
- expect(ref).not.toBeNull();
3327
- expect(dropzone).toContainElement(ref);
3150
+ fireEvent.click(dropzone);
3151
+
3152
+ expect(activeRef.current).not.toBeNull();
3153
+ expect(dropzone).toContainElement(activeRef.current);
3328
3154
 
3329
- dispatchEvt(document.body, "focus");
3330
- drainTimers();
3155
+ focusWindow();
3156
+ drainPendingTimers();
3331
3157
 
3332
3158
  expect(onFileDialogCancelSpy).toHaveBeenCalled();
3333
- expect(dropzone).not.toContainElement(ref);
3159
+ expect(dropzone).not.toContainElement(activeRef.current);
3334
3160
  });
3335
3161
 
3336
3162
  it("is not invoked if <input> is not rendered", () => {
3337
3163
  const onFileDialogCancelSpy = jest.fn();
3338
3164
  const { container, rerender } = render(
3339
3165
  <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3340
- {({ getRootProps, getInputProps, open }) => (
3166
+ {({ getRootProps, getInputProps }) => (
3341
3167
  <div {...getRootProps()}>
3342
3168
  <input {...getInputProps()} />
3343
- <button type="button" onClick={open}>
3344
- Open
3345
- </button>
3346
3169
  </div>
3347
3170
  )}
3348
3171
  </Dropzone>
3349
3172
  );
3350
3173
 
3351
- const btn = container.querySelector("button");
3352
- btn.click();
3353
- drainTimers();
3174
+ fireEvent.click(container.querySelector("div"));
3354
3175
 
3176
+ // Remove the input, then on window focus nothing should happen because we check if the input ref is set
3355
3177
  rerender(
3356
3178
  <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3357
3179
  {({ getRootProps }) => <div {...getRootProps()} />}
3358
3180
  </Dropzone>
3359
3181
  );
3360
- drainTimers();
3361
3182
 
3362
- dispatchEvt(document.body, "focus");
3363
- drainTimers();
3183
+ focusWindow();
3184
+ drainPendingTimers();
3364
3185
 
3365
3186
  expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3366
3187
  });
3367
3188
 
3368
- it("is not invoked if files were selected", () => {
3189
+ it("is not invoked if files were selected", async () => {
3369
3190
  const onFileDialogCancelSpy = jest.fn();
3370
3191
 
3371
3192
  const { container } = render(
3372
3193
  <Dropzone onFileDialogCancel={onFileDialogCancelSpy}>
3373
- {({ getRootProps, getInputProps, open }) => (
3194
+ {({ getRootProps, getInputProps }) => (
3374
3195
  <div {...getRootProps()}>
3375
3196
  <input {...getInputProps()} />
3376
- <button type="button" onClick={open}>
3377
- Open
3378
- </button>
3379
3197
  </div>
3380
3198
  )}
3381
3199
  </Dropzone>
3382
3200
  );
3383
3201
 
3384
- const input = container.querySelector("input");
3385
- Object.defineProperty(input, "files", { value: files });
3386
-
3387
- const btn = container.querySelector("button");
3388
- btn.click();
3389
- drainTimers();
3202
+ await act(async () =>
3203
+ fireEvent.change(container.querySelector("input"), {
3204
+ target: { files },
3205
+ })
3206
+ );
3207
+ fireEvent.click(container.querySelector("div"));
3390
3208
 
3391
- dispatchEvt(document.body, "focus");
3392
- drainTimers();
3209
+ focusWindow();
3210
+ drainPendingTimers();
3393
3211
 
3394
3212
  expect(onFileDialogCancelSpy).not.toHaveBeenCalled();
3395
3213
  });
@@ -3397,24 +3215,19 @@ describe("useDropzone() hook", () => {
3397
3215
  it("does not throw if callback is not provided", () => {
3398
3216
  const { container } = render(
3399
3217
  <Dropzone onFileDialogCancel={null}>
3400
- {({ getRootProps, getInputProps, open }) => (
3218
+ {({ getRootProps, getInputProps }) => (
3401
3219
  <div {...getRootProps()}>
3402
3220
  <input {...getInputProps()} />
3403
- <button type="button" onClick={open}>
3404
- Open
3405
- </button>
3406
3221
  </div>
3407
3222
  )}
3408
3223
  </Dropzone>
3409
3224
  );
3410
3225
 
3411
- const btn = container.querySelector("button");
3412
- btn.click();
3413
- drainTimers();
3226
+ fireEvent.click(container.querySelector("div"));
3414
3227
 
3415
3228
  const fn = () => {
3416
- dispatchEvt(document.body, "focus");
3417
- drainTimers();
3229
+ focusWindow();
3230
+ drainPendingTimers();
3418
3231
  };
3419
3232
  expect(fn).not.toThrow();
3420
3233
  });
@@ -3433,8 +3246,7 @@ describe("useDropzone() hook", () => {
3433
3246
  </Dropzone>
3434
3247
  );
3435
3248
 
3436
- const dropzone = container.querySelector("div");
3437
- fireEvent.click(dropzone);
3249
+ fireEvent.click(container.querySelector("div"));
3438
3250
 
3439
3251
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
3440
3252
  });
@@ -3454,8 +3266,7 @@ describe("useDropzone() hook", () => {
3454
3266
  </Dropzone>
3455
3267
  );
3456
3268
 
3457
- const btn = container.querySelector("button");
3458
- btn.click();
3269
+ fireEvent.click(container.querySelector("button"));
3459
3270
 
3460
3271
  expect(onFileDialogOpenSpy).toHaveBeenCalled();
3461
3272
  });
@@ -3477,9 +3288,7 @@ describe("useDropzone() hook", () => {
3477
3288
  </Dropzone>
3478
3289
  );
3479
3290
 
3480
- const btn = container.querySelector("button");
3481
-
3482
- btn.click();
3291
+ fireEvent.click(container.querySelector("button"));
3483
3292
 
3484
3293
  expect(onClickSpy).toHaveBeenCalled();
3485
3294
  });
@@ -3501,14 +3310,12 @@ describe("useDropzone() hook", () => {
3501
3310
  </Dropzone>
3502
3311
  );
3503
3312
 
3504
- const dropzone = container.querySelector("div");
3505
- const btn = container.querySelector("button");
3506
-
3507
- btn.click();
3313
+ fireEvent.click(container.querySelector("button"));
3508
3314
 
3509
- const ref = activeRef.current;
3510
- expect(ref).not.toBeNull();
3511
- expect(dropzone).toContainElement(ref);
3315
+ expect(activeRef.current).not.toBeNull();
3316
+ expect(container.querySelector("div")).toContainElement(
3317
+ activeRef.current
3318
+ );
3512
3319
  });
3513
3320
 
3514
3321
  it("does nothing if {disabled} is true", () => {
@@ -3526,9 +3333,7 @@ describe("useDropzone() hook", () => {
3526
3333
  </Dropzone>
3527
3334
  );
3528
3335
 
3529
- const btn = container.querySelector("button");
3530
-
3531
- btn.click();
3336
+ fireEvent.click(container.querySelector("button"));
3532
3337
 
3533
3338
  expect(onClickSpy).not.toHaveBeenCalled();
3534
3339
  });
@@ -3546,9 +3351,7 @@ describe("useDropzone() hook", () => {
3546
3351
  </Dropzone>
3547
3352
  );
3548
3353
 
3549
- const btn = container.querySelector("button");
3550
-
3551
- const fn = () => btn.click();
3354
+ const fn = () => fireEvent.click(container.querySelector("button"));
3552
3355
  expect(fn).not.toThrow();
3553
3356
  });
3554
3357
  });
@@ -3564,21 +3367,18 @@ describe("useDropzone() hook", () => {
3564
3367
 
3565
3368
  const onDropSpy = jest.fn();
3566
3369
 
3567
- const ui = (
3370
+ const { container } = render(
3568
3371
  <Dropzone validator={validator} onDrop={onDropSpy} multiple={true}>
3569
- {({ getRootProps, getInputProps }) => (
3570
- <div {...getRootProps()}>
3571
- <input {...getInputProps()} />
3572
- </div>
3573
- )}
3372
+ {({ getRootProps }) => <div {...getRootProps()} />}
3574
3373
  </Dropzone>
3575
3374
  );
3576
3375
 
3577
- const { container, rerender } = render(ui);
3578
- const dropzone = container.querySelector("div");
3579
-
3580
- fireDrop(dropzone, createDtWithFiles(images));
3581
- await flushPromises(rerender, ui);
3376
+ await act(() =>
3377
+ fireEvent.drop(
3378
+ container.querySelector("div"),
3379
+ createDtWithFiles(images)
3380
+ )
3381
+ );
3582
3382
 
3583
3383
  expect(onDropSpy).toHaveBeenCalledWith(
3584
3384
  [images[0]],
@@ -3605,9 +3405,11 @@ describe("useDropzone() hook", () => {
3605
3405
  {({ getRootProps }) => <div id="root" {...getRootProps()} />}
3606
3406
  </Dropzone>
3607
3407
  );
3608
- const root = container.querySelector("#root");
3609
3408
 
3610
- expect(root).toHaveAttribute("role", "button");
3409
+ expect(container.querySelector("#root")).toHaveAttribute(
3410
+ "role",
3411
+ "button"
3412
+ );
3611
3413
  });
3612
3414
 
3613
3415
  test("users can override the default role attribute on the root", () => {
@@ -3618,21 +3420,37 @@ describe("useDropzone() hook", () => {
3618
3420
  )}
3619
3421
  </Dropzone>
3620
3422
  );
3621
- const root = container.querySelector("#root");
3622
3423
 
3623
- expect(root).toHaveAttribute("role", "generic");
3424
+ expect(container.querySelector("#root")).toHaveAttribute(
3425
+ "role",
3426
+ "generic"
3427
+ );
3624
3428
  });
3625
3429
  });
3626
3430
  });
3627
3431
 
3628
- async function flushPromises(rerender, ui) {
3629
- await act(() => waitFor(() => rerender(ui)));
3432
+ /**
3433
+ * drainPendingTimers just runs pending timers wrapped in act() to avoid
3434
+ * getting warnings from react about side effects that happen async.
3435
+ *
3436
+ * This can be used whenever a setTimeout(), setInterval() or async operation is used
3437
+ * which triggers a state update.
3438
+ */
3439
+ function drainPendingTimers() {
3440
+ return act(() => jest.runOnlyPendingTimers());
3630
3441
  }
3631
3442
 
3632
- function drainTimers() {
3633
- act(() => jest.runAllTimers());
3443
+ /**
3444
+ * focusWindow triggers focus on the window
3445
+ */
3446
+ function focusWindow() {
3447
+ return fireEvent.focus(document.body, { bubbles: true });
3634
3448
  }
3635
3449
 
3450
+ /**
3451
+ * createDtWithFiles creates a mock data transfer object that can be used for drop events
3452
+ * @param {File[]} files
3453
+ */
3636
3454
  function createDtWithFiles(files = []) {
3637
3455
  return {
3638
3456
  dataTransfer: {
@@ -3648,36 +3466,20 @@ function createDtWithFiles(files = []) {
3648
3466
  };
3649
3467
  }
3650
3468
 
3651
- function fireDragEnter(node, data) {
3652
- dispatchEvt(node, "dragenter", data);
3653
- }
3654
-
3655
- function fireDragOver(node, data) {
3656
- dispatchEvt(node, "dragover", data);
3657
- }
3658
-
3659
- function fireDragLeave(node, data) {
3660
- dispatchEvt(node, "dragleave", data);
3661
- }
3662
-
3663
- function fireDrop(node, data) {
3664
- dispatchEvt(node, "drop", data);
3665
- }
3666
-
3667
- // Using fireEvent.* doesn't work for our use case,
3668
- // we cannot set the event props
3669
- function dispatchEvt(node, type, data) {
3670
- const event = new Event(type, { bubbles: true });
3671
- if (data) {
3672
- Object.assign(event, data);
3673
- }
3674
- fireEvent(node, event);
3675
- }
3676
-
3469
+ /**
3470
+ * createFileSystemFileHandle creates a mock [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle)
3471
+ * @param {File} file
3472
+ */
3677
3473
  function createFileSystemFileHandle(file) {
3678
3474
  return { getFile: () => Promise.resolve(file) };
3679
3475
  }
3680
3476
 
3477
+ /**
3478
+ * createFile creates a mock File object
3479
+ * @param {string} name
3480
+ * @param {number} size
3481
+ * @param {string} type
3482
+ */
3681
3483
  function createFile(name, size, type) {
3682
3484
  const file = new File([], name, { type });
3683
3485
  Object.defineProperty(file, "size", {
@@ -3688,6 +3490,9 @@ function createFile(name, size, type) {
3688
3490
  return file;
3689
3491
  }
3690
3492
 
3493
+ /**
3494
+ * createThenable creates a Promise that can be controlled from outside its inner scope
3495
+ */
3691
3496
  function createThenable() {
3692
3497
  let done, cancel;
3693
3498