@khanacademy/wonder-blocks-data 2.3.2 → 3.0.1

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 (41) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/es/index.js +210 -439
  3. package/dist/index.js +235 -478
  4. package/docs.md +19 -13
  5. package/package.json +6 -7
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +40 -160
  7. package/src/__tests__/generated-snapshot.test.js +15 -195
  8. package/src/components/__tests__/data.test.js +159 -965
  9. package/src/components/__tests__/intercept-data.test.js +9 -66
  10. package/src/components/__tests__/track-data.test.js +6 -5
  11. package/src/components/data.js +9 -119
  12. package/src/components/data.md +38 -60
  13. package/src/components/intercept-context.js +2 -3
  14. package/src/components/intercept-data.js +2 -34
  15. package/src/components/intercept-data.md +7 -105
  16. package/src/hooks/__tests__/use-data.test.js +826 -0
  17. package/src/hooks/use-data.js +143 -0
  18. package/src/index.js +1 -3
  19. package/src/util/__tests__/memory-cache.test.js +134 -35
  20. package/src/util/__tests__/request-fulfillment.test.js +21 -36
  21. package/src/util/__tests__/request-handler.test.js +30 -30
  22. package/src/util/__tests__/request-tracking.test.js +29 -30
  23. package/src/util/__tests__/response-cache.test.js +521 -561
  24. package/src/util/__tests__/result-from-cache-entry.test.js +68 -0
  25. package/src/util/memory-cache.js +20 -15
  26. package/src/util/request-fulfillment.js +4 -0
  27. package/src/util/request-handler.js +4 -28
  28. package/src/util/request-handler.md +0 -32
  29. package/src/util/request-tracking.js +2 -3
  30. package/src/util/response-cache.js +50 -110
  31. package/src/util/result-from-cache-entry.js +38 -0
  32. package/src/util/types.js +14 -35
  33. package/LICENSE +0 -21
  34. package/src/components/__tests__/intercept-cache.test.js +0 -124
  35. package/src/components/__tests__/internal-data.test.js +0 -1030
  36. package/src/components/intercept-cache.js +0 -79
  37. package/src/components/intercept-cache.md +0 -103
  38. package/src/components/internal-data.js +0 -219
  39. package/src/util/__tests__/no-cache.test.js +0 -112
  40. package/src/util/no-cache.js +0 -66
  41. package/src/util/no-cache.md +0 -66
@@ -0,0 +1,826 @@
1
+ // @flow
2
+ import {
3
+ renderHook as clientRenderHook,
4
+ act,
5
+ } from "@testing-library/react-hooks";
6
+ import {renderHook as serverRenderHook} from "@testing-library/react-hooks/server";
7
+
8
+ import {Server} from "@khanacademy/wonder-blocks-core";
9
+
10
+ import * as React from "react";
11
+ import TrackData from "../../components/track-data.js";
12
+ import InterceptData from "../../components/intercept-data.js";
13
+ import {RequestFulfillment} from "../../util/request-fulfillment.js";
14
+ import {ResponseCache} from "../../util/response-cache.js";
15
+ import {RequestTracker} from "../../util/request-tracking.js";
16
+
17
+ import {useData} from "../use-data.js";
18
+
19
+ import type {IRequestHandler} from "../../util/types.js";
20
+
21
+ describe("#useData", () => {
22
+ beforeEach(() => {
23
+ const responseCache = new ResponseCache();
24
+ jest.spyOn(ResponseCache, "Default", "get").mockReturnValue(
25
+ responseCache,
26
+ );
27
+ jest.spyOn(RequestFulfillment, "Default", "get").mockReturnValue(
28
+ new RequestFulfillment(responseCache),
29
+ );
30
+ jest.spyOn(RequestTracker, "Default", "get").mockReturnValue(
31
+ new RequestTracker(responseCache),
32
+ );
33
+ });
34
+
35
+ afterEach(() => {
36
+ jest.resetAllMocks();
37
+ });
38
+
39
+ describe("when server-side", () => {
40
+ beforeEach(() => {
41
+ jest.spyOn(Server, "isServerSide").mockReturnValue(true);
42
+ });
43
+
44
+ it("should return loading if no cached result", () => {
45
+ // Arrange
46
+ const fakeHandler: IRequestHandler<string, string> = {
47
+ fulfillRequest: jest.fn(),
48
+ getKey: (o) => o,
49
+ type: "MY_HANDLER",
50
+ hydrate: true,
51
+ };
52
+
53
+ // Act
54
+ const {
55
+ result: {current: result},
56
+ } = serverRenderHook(() => useData(fakeHandler, "options"));
57
+
58
+ // Assert
59
+ expect(result).toEqual({
60
+ status: "loading",
61
+ });
62
+ });
63
+
64
+ it("should not directly request fulfillment", () => {
65
+ // Arrange
66
+ const fakeHandler: IRequestHandler<string, string> = {
67
+ fulfillRequest: jest.fn(),
68
+ getKey: (o) => o,
69
+ type: "MY_HANDLER",
70
+ hydrate: true,
71
+ };
72
+ const fulfillRequestSpy = jest.spyOn(
73
+ RequestFulfillment.Default,
74
+ "fulfill",
75
+ );
76
+
77
+ // Act
78
+ serverRenderHook(() => useData(fakeHandler, "options"));
79
+
80
+ // Assert
81
+ expect(fulfillRequestSpy).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("should track the request", () => {
85
+ // Arrange
86
+ const fakeHandler: IRequestHandler<string, string> = {
87
+ fulfillRequest: jest.fn(),
88
+ getKey: (o) => o,
89
+ type: "MY_HANDLER",
90
+ hydrate: true,
91
+ };
92
+ const trackDataRequestSpy = jest.spyOn(
93
+ RequestTracker.Default,
94
+ "trackDataRequest",
95
+ );
96
+
97
+ // Act
98
+ serverRenderHook(() => useData(fakeHandler, "options"), {
99
+ wrapper: TrackData,
100
+ });
101
+
102
+ // Assert
103
+ expect(trackDataRequestSpy).toHaveBeenCalledWith(
104
+ fakeHandler,
105
+ "options",
106
+ );
107
+ });
108
+
109
+ it("should return success state if cached result has data", () => {
110
+ // Arrange
111
+ const fakeHandler: IRequestHandler<string, string> = {
112
+ fulfillRequest: jest.fn(),
113
+ getKey: (o) => o,
114
+ type: "MY_HANDLER",
115
+ hydrate: true,
116
+ };
117
+ jest.spyOn(ResponseCache.Default, "getEntry").mockReturnValueOnce({
118
+ data: "DATA",
119
+ error: null,
120
+ });
121
+
122
+ // Act
123
+ const {
124
+ result: {current: result},
125
+ } = serverRenderHook(() => useData(fakeHandler, "options"));
126
+
127
+ // Assert
128
+ expect(result).toEqual({
129
+ status: "success",
130
+ data: "DATA",
131
+ });
132
+ });
133
+
134
+ it("should return error state if cached result has error", () => {
135
+ // Arrange
136
+ const fakeHandler: IRequestHandler<string, string> = {
137
+ fulfillRequest: jest.fn(),
138
+ getKey: (o) => o,
139
+ type: "MY_HANDLER",
140
+ hydrate: true,
141
+ };
142
+ jest.spyOn(ResponseCache.Default, "getEntry").mockReturnValueOnce({
143
+ data: null,
144
+ error: "ERROR",
145
+ });
146
+
147
+ // Act
148
+ const {
149
+ result: {current: result},
150
+ } = serverRenderHook(() => useData(fakeHandler, "options"));
151
+
152
+ // Assert
153
+ expect(result).toEqual({
154
+ status: "error",
155
+ error: "ERROR",
156
+ });
157
+ });
158
+
159
+ it("should track the intercepted request", async () => {
160
+ // Arrange
161
+ const intercepted = Promise.resolve("INTERCEPTED");
162
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
163
+ const fakeHandler: IRequestHandler<string, string> = {
164
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
165
+ getKey: (o) => o,
166
+ type: "MY_HANDLER",
167
+ hydrate: true,
168
+ };
169
+ const trackDataRequestSpy = jest.spyOn(
170
+ RequestTracker.Default,
171
+ "trackDataRequest",
172
+ );
173
+ const wrapper = ({children}) => (
174
+ <TrackData>
175
+ <InterceptData
176
+ fulfillRequest={() => intercepted}
177
+ handler={fakeHandler}
178
+ >
179
+ {children}
180
+ </InterceptData>
181
+ </TrackData>
182
+ );
183
+
184
+ // Act
185
+ serverRenderHook(() => useData(fakeHandler, "options"), {
186
+ wrapper,
187
+ });
188
+ const trackedHandler = trackDataRequestSpy.mock.calls[0][0];
189
+ const result = await trackedHandler.fulfillRequest();
190
+
191
+ // Assert
192
+ expect(result).toBe("INTERCEPTED");
193
+ });
194
+ });
195
+
196
+ describe("when client-side", () => {
197
+ beforeEach(() => {
198
+ jest.spyOn(Server, "isServerSide").mockReturnValue(false);
199
+ });
200
+
201
+ it("should initially return loading state when no cached result", () => {
202
+ // Arrange
203
+ const fakeHandler: IRequestHandler<string, string> = {
204
+ fulfillRequest: jest.fn().mockReturnValue(
205
+ new Promise(() => {
206
+ /*prevent act() warning*/
207
+ }),
208
+ ),
209
+ getKey: (o) => o,
210
+ type: "MY_HANDLER",
211
+ hydrate: true,
212
+ };
213
+
214
+ // Act
215
+ const {
216
+ result: {current: result},
217
+ } = clientRenderHook(() => useData(fakeHandler, "options"));
218
+
219
+ // Assert
220
+ expect(result).toEqual({
221
+ status: "loading",
222
+ });
223
+ });
224
+
225
+ it("should initially return success state when cached result has data", () => {
226
+ // Arrange
227
+ const fakeHandler: IRequestHandler<string, string> = {
228
+ fulfillRequest: jest.fn().mockReturnValue(
229
+ new Promise(() => {
230
+ /*prevent act() warning*/
231
+ }),
232
+ ),
233
+ getKey: (o) => o,
234
+ type: "MY_HANDLER",
235
+ hydrate: true,
236
+ };
237
+ jest.spyOn(ResponseCache.Default, "getEntry").mockReturnValueOnce({
238
+ data: "DATA",
239
+ error: null,
240
+ });
241
+
242
+ // Act
243
+ const {
244
+ result: {current: result},
245
+ } = clientRenderHook(() => useData(fakeHandler, "options"));
246
+
247
+ // Assert
248
+ expect(result).toEqual({
249
+ status: "success",
250
+ data: "DATA",
251
+ });
252
+ });
253
+
254
+ it("should initially return error state when cached result has error", () => {
255
+ // Arrange
256
+ const fakeHandler: IRequestHandler<string, string> = {
257
+ fulfillRequest: jest.fn().mockReturnValue(
258
+ new Promise(() => {
259
+ /*prevent act() warning*/
260
+ }),
261
+ ),
262
+ getKey: (o) => o,
263
+ type: "MY_HANDLER",
264
+ hydrate: true,
265
+ };
266
+ jest.spyOn(ResponseCache.Default, "getEntry").mockReturnValueOnce({
267
+ data: null,
268
+ error: "ERROR",
269
+ });
270
+
271
+ // Act
272
+ const {
273
+ result: {current: result},
274
+ } = clientRenderHook(() => useData(fakeHandler, "options"));
275
+
276
+ // Assert
277
+ expect(result).toEqual({
278
+ status: "error",
279
+ error: "ERROR",
280
+ });
281
+ });
282
+
283
+ it("should not track the request", () => {
284
+ // Arrange
285
+ const fakeHandler: IRequestHandler<string, string> = {
286
+ fulfillRequest: jest.fn().mockReturnValue(
287
+ new Promise(() => {
288
+ /*prevent act() warning*/
289
+ }),
290
+ ),
291
+ getKey: (o) => o,
292
+ type: "MY_HANDLER",
293
+ hydrate: true,
294
+ };
295
+ const trackDataRequestSpy = jest.spyOn(
296
+ RequestTracker.Default,
297
+ "trackDataRequest",
298
+ );
299
+
300
+ // Act
301
+ clientRenderHook(() => useData(fakeHandler, "options"), {
302
+ wrapper: TrackData,
303
+ });
304
+
305
+ // Assert
306
+ expect(trackDataRequestSpy).not.toHaveBeenCalled();
307
+ });
308
+
309
+ it("should request fulfillment", () => {
310
+ // Arrange
311
+ const fakeHandler: IRequestHandler<string, string> = {
312
+ fulfillRequest: jest.fn().mockReturnValue(
313
+ new Promise(() => {
314
+ /*prevent act() warning*/
315
+ }),
316
+ ),
317
+ getKey: (o) => o,
318
+ type: "MY_HANDLER",
319
+ hydrate: true,
320
+ };
321
+ const fulfillRequestSpy = jest.spyOn(
322
+ RequestFulfillment.Default,
323
+ "fulfill",
324
+ );
325
+
326
+ // Act
327
+ clientRenderHook(() => useData(fakeHandler, "options"));
328
+
329
+ // Assert
330
+ expect(fulfillRequestSpy).toHaveBeenCalledWith(
331
+ fakeHandler,
332
+ "options",
333
+ );
334
+ });
335
+
336
+ it("should return success state if fulfillment resolves with data", async () => {
337
+ // Arrange
338
+ const request = Promise.resolve("DATA");
339
+ const fakeHandler: IRequestHandler<string, string> = {
340
+ fulfillRequest: jest.fn().mockReturnValue(request),
341
+ getKey: (o) => o,
342
+ type: "MY_HANDLER",
343
+ hydrate: true,
344
+ };
345
+
346
+ // Act
347
+ const render = clientRenderHook(() =>
348
+ useData(fakeHandler, "options"),
349
+ );
350
+ await act((): Promise<mixed> => request);
351
+ const result = render.result.current;
352
+
353
+ // Assert
354
+ expect(result).toEqual({
355
+ status: "success",
356
+ data: "DATA",
357
+ });
358
+ });
359
+
360
+ it("should return error state if fulfillment resolves with error", async () => {
361
+ // Arrange
362
+ const request = Promise.reject(new Error("ERROR"));
363
+ const fakeHandler: IRequestHandler<string, string> = {
364
+ fulfillRequest: jest.fn().mockReturnValue(request),
365
+ getKey: (o) => o,
366
+ type: "MY_HANDLER",
367
+ hydrate: true,
368
+ };
369
+
370
+ // Act
371
+ const render = clientRenderHook(() =>
372
+ useData(fakeHandler, "options"),
373
+ );
374
+ await act(async (): Promise<mixed> => {
375
+ try {
376
+ await request;
377
+ } catch (e) {
378
+ /* we know, we know */
379
+ }
380
+ });
381
+ const result = render.result.current;
382
+
383
+ // Assert
384
+ expect(result).toEqual({
385
+ status: "error",
386
+ error: "ERROR",
387
+ });
388
+ });
389
+
390
+ it("should return error state if fulfillment rejects", async () => {
391
+ // Arrange
392
+ const fakeHandler: IRequestHandler<string, string> = {
393
+ fulfillRequest: jest.fn().mockReturnValue(
394
+ new Promise(() => {
395
+ /*prevent act() warning*/
396
+ }),
397
+ ),
398
+ getKey: (o) => o,
399
+ type: "MY_HANDLER",
400
+ hydrate: true,
401
+ };
402
+ const request = Promise.reject(new Error("ERROR"));
403
+ jest.spyOn(
404
+ RequestFulfillment.Default,
405
+ "fulfill",
406
+ ).mockReturnValueOnce(request);
407
+
408
+ // Act
409
+ const render = clientRenderHook(() =>
410
+ useData(fakeHandler, "options"),
411
+ );
412
+ await act(async (): Promise<mixed> => {
413
+ try {
414
+ await request;
415
+ } catch (e) {
416
+ /* we know, we know */
417
+ }
418
+ });
419
+ const result = render.result.current;
420
+
421
+ // Assert
422
+ expect(result).toEqual({
423
+ status: "error",
424
+ error: "ERROR",
425
+ });
426
+ });
427
+
428
+ it("should ignore resolution of pending request fulfillment when handler changes", async () => {
429
+ // Arrange
430
+ const oldRequest = Promise.resolve("OLD DATA");
431
+ const oldHandler: IRequestHandler<string, string> = {
432
+ fulfillRequest: jest.fn().mockReturnValue(oldRequest),
433
+ getKey: (o) => o,
434
+ type: "MY_HANDLER",
435
+ hydrate: true,
436
+ };
437
+ const newHandler: IRequestHandler<string, string> = {
438
+ fulfillRequest: jest.fn().mockReturnValue(
439
+ new Promise(() => {
440
+ /*let's have the new request remain pending*/
441
+ }),
442
+ ),
443
+ getKey: (o) => o,
444
+ type: "MY_NEW_HANDLER",
445
+ hydrate: true,
446
+ };
447
+
448
+ // Act
449
+ const render = clientRenderHook(
450
+ ({handler}) => useData(handler, "options"),
451
+ {
452
+ initialProps: {handler: oldHandler},
453
+ },
454
+ );
455
+ render.rerender({handler: newHandler});
456
+ await act((): Promise<mixed> => oldRequest);
457
+ const result = render.result.all;
458
+
459
+ // Assert
460
+ expect(result).not.toIncludeAnyMembers([
461
+ {
462
+ status: "success",
463
+ data: "OLD DATA",
464
+ },
465
+ ]);
466
+ });
467
+
468
+ it("should ignore resolution of pending request fulfillment when key from options changes", async () => {
469
+ // Arrange
470
+ const oldRequest = Promise.reject("OLD ERROR");
471
+ const oldHandler: IRequestHandler<string, string> = {
472
+ fulfillRequest: jest
473
+ .fn()
474
+ .mockReturnValueOnce(oldRequest)
475
+ .mockReturnValue(
476
+ new Promise(() => {
477
+ /*let's have the new request remain pending*/
478
+ }),
479
+ ),
480
+ getKey: (o) => o,
481
+ type: "MY_HANDLER",
482
+ hydrate: true,
483
+ };
484
+
485
+ // Act
486
+ const render = clientRenderHook(
487
+ ({options}) => useData(oldHandler, options),
488
+ {
489
+ initialProps: {options: "OLD OPTIONS"},
490
+ },
491
+ );
492
+ render.rerender({options: "NEW OPTIONS"});
493
+ await act((): Promise<mixed> => oldRequest.catch(() => {}));
494
+ const result = render.result.all;
495
+
496
+ // Assert
497
+ expect(result).not.toIncludeAnyMembers([
498
+ {
499
+ status: "error",
500
+ error: "OLD ERROR",
501
+ },
502
+ ]);
503
+ });
504
+
505
+ it("should ignore rejection of pending request fulfillment when handler changes", async () => {
506
+ // Arrange
507
+ const oldRequest = Promise.reject("OLD ERROR");
508
+ const oldHandler: IRequestHandler<string, string> = {
509
+ fulfillRequest: jest.fn().mockReturnValue(
510
+ new Promise(() => {
511
+ /*this doesn't get called for this test case*/
512
+ }),
513
+ ),
514
+ getKey: (o) => o,
515
+ type: "MY_HANDLER",
516
+ hydrate: true,
517
+ };
518
+ const newHandler: IRequestHandler<string, string> = {
519
+ fulfillRequest: jest.fn().mockReturnValue(
520
+ new Promise(() => {
521
+ /*let's have the new request remain pending*/
522
+ }),
523
+ ),
524
+ getKey: (o) => o,
525
+ type: "MY_NEW_HANDLER",
526
+ hydrate: true,
527
+ };
528
+ jest.spyOn(RequestFulfillment.Default, "fulfill")
529
+ .mockReturnValueOnce(oldRequest)
530
+ .mockReturnValue(
531
+ new Promise(() => {
532
+ /*leave the second request pending*/
533
+ }),
534
+ );
535
+
536
+ // Act
537
+ const render = clientRenderHook(
538
+ ({handler}) => useData(handler, "options"),
539
+ {
540
+ initialProps: {handler: oldHandler},
541
+ },
542
+ );
543
+ render.rerender({handler: newHandler});
544
+ await act((): Promise<mixed> => oldRequest.catch(() => {}));
545
+ const result = render.result.all;
546
+
547
+ // Assert
548
+ expect(result).not.toIncludeAnyMembers([
549
+ {
550
+ status: "error",
551
+ error: "OLD ERROR",
552
+ },
553
+ ]);
554
+ });
555
+
556
+ it("should ignore rejection of pending request fulfillment when key from options changes", async () => {
557
+ // Arrange
558
+ const oldRequest = Promise.resolve("OLD DATA");
559
+ const oldHandler: IRequestHandler<string, string> = {
560
+ fulfillRequest: jest.fn().mockReturnValue(
561
+ new Promise(() => {
562
+ /*this won't ever get called in this version*/
563
+ }),
564
+ ),
565
+ getKey: (o) => o,
566
+ type: "MY_HANDLER",
567
+ hydrate: true,
568
+ };
569
+ jest.spyOn(RequestFulfillment.Default, "fulfill")
570
+ .mockReturnValueOnce(oldRequest)
571
+ .mockReturnValue(
572
+ new Promise(() => {
573
+ /*leave the second request pending*/
574
+ }),
575
+ );
576
+
577
+ // Act
578
+ const render = clientRenderHook(
579
+ ({options}) => useData(oldHandler, options),
580
+ {
581
+ initialProps: {options: "OLD OPTIONS"},
582
+ },
583
+ );
584
+ render.rerender({options: "NEW OPTIONS"});
585
+ await act((): Promise<mixed> => oldRequest);
586
+ const result = render.result.all;
587
+
588
+ // Assert
589
+ expect(result).not.toIncludeAnyMembers([
590
+ {
591
+ status: "success",
592
+ data: "OLD DATA",
593
+ },
594
+ ]);
595
+ });
596
+
597
+ it("should return to loading status when handler changes", async () => {
598
+ // Arrange
599
+ const oldRequest = Promise.resolve("OLD DATA");
600
+ const oldHandler: IRequestHandler<string, string> = {
601
+ fulfillRequest: jest.fn().mockReturnValue(oldRequest),
602
+ getKey: (o) => o,
603
+ type: "MY_HANDLER",
604
+ hydrate: true,
605
+ };
606
+ const newHandler: IRequestHandler<string, string> = {
607
+ fulfillRequest: jest.fn().mockReturnValue(
608
+ new Promise(() => {
609
+ /*let's have the new request remain pending*/
610
+ }),
611
+ ),
612
+ getKey: (o) => o,
613
+ type: "MY_NEW_HANDLER",
614
+ hydrate: true,
615
+ };
616
+
617
+ // Act
618
+ const render = clientRenderHook(
619
+ ({handler}) => useData(handler, "options"),
620
+ {
621
+ initialProps: {handler: oldHandler},
622
+ },
623
+ );
624
+ await act((): Promise<mixed> => oldRequest);
625
+ render.rerender({handler: newHandler});
626
+ const result = render.result.current;
627
+
628
+ // Assert
629
+ expect(result).toEqual({
630
+ status: "loading",
631
+ });
632
+ });
633
+
634
+ it("should return to loading status when key from options changes", async () => {
635
+ // Arrange
636
+ const oldRequest = Promise.resolve("OLD DATA");
637
+ const oldHandler: IRequestHandler<string, string> = {
638
+ fulfillRequest: jest
639
+ .fn()
640
+ .mockReturnValueOnce(oldRequest)
641
+ .mockReturnValue(
642
+ new Promise(() => {
643
+ /*let's have the new request remain pending*/
644
+ }),
645
+ ),
646
+ getKey: (o) => o,
647
+ type: "MY_HANDLER",
648
+ hydrate: true,
649
+ };
650
+
651
+ // Act
652
+ const render = clientRenderHook(
653
+ ({options}) => useData(oldHandler, options),
654
+ {
655
+ initialProps: {options: "OLD OPTIONS"},
656
+ },
657
+ );
658
+ await act((): Promise<mixed> => oldRequest);
659
+ render.rerender({options: "NEW OPTIONS"});
660
+ const result = render.result.current;
661
+
662
+ // Assert
663
+ expect(result).toEqual({
664
+ status: "loading",
665
+ });
666
+ });
667
+
668
+ it("should not return to loading state if options change but represent the same key", async () => {
669
+ // Arrange
670
+ const oldRequest = Promise.resolve("DATA");
671
+ const oldHandler: IRequestHandler<
672
+ {|key: string, someThing: string|},
673
+ string,
674
+ > = {
675
+ fulfillRequest: jest
676
+ .fn()
677
+ .mockReturnValueOnce(oldRequest)
678
+ .mockReturnValue(
679
+ new Promise(() => {
680
+ /*let's have the new request remain pending*/
681
+ }),
682
+ ),
683
+ getKey: (o) => o.key,
684
+ type: "MY_HANDLER",
685
+ hydrate: true,
686
+ };
687
+
688
+ // Act
689
+ const render = clientRenderHook(
690
+ ({options}) => useData(oldHandler, options),
691
+ {
692
+ initialProps: {
693
+ options: {key: "SAME KEY", someThing: "else"},
694
+ },
695
+ },
696
+ );
697
+ await act((): Promise<mixed> => oldRequest);
698
+ render.rerender({options: {key: "SAME KEY", someThing: "new"}});
699
+ const result = render.result.current;
700
+
701
+ // Assert
702
+ expect(result).toEqual({
703
+ status: "success",
704
+ data: "DATA",
705
+ });
706
+ });
707
+
708
+ describe("with interceptor", () => {
709
+ it("should return the result of the interceptor request resolution", async () => {
710
+ // Arrange
711
+ const intercepted = Promise.resolve("INTERCEPTED");
712
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
713
+ const fakeHandler: IRequestHandler<string, string> = {
714
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
715
+ getKey: (o) => o,
716
+ type: "MY_HANDLER",
717
+ hydrate: true,
718
+ };
719
+ const wrapper = ({children}) => (
720
+ <InterceptData
721
+ fulfillRequest={() => intercepted}
722
+ handler={fakeHandler}
723
+ >
724
+ {children}
725
+ </InterceptData>
726
+ );
727
+
728
+ // Act
729
+ const render = clientRenderHook(
730
+ () => useData(fakeHandler, "options"),
731
+ {
732
+ wrapper,
733
+ },
734
+ );
735
+ await act((): Promise<mixed> =>
736
+ Promise.all([notIntercepted, intercepted]),
737
+ );
738
+ const result = render.result.current;
739
+
740
+ // Assert
741
+ expect(result).toEqual({
742
+ status: "success",
743
+ data: "INTERCEPTED",
744
+ });
745
+ });
746
+
747
+ it("should return the result of the interceptor request rejection", async () => {
748
+ // Arrange
749
+ const intercepted = Promise.reject("INTERCEPTED");
750
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
751
+ const fakeHandler: IRequestHandler<string, string> = {
752
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
753
+ getKey: (o) => o,
754
+ type: "MY_HANDLER",
755
+ hydrate: true,
756
+ };
757
+ const wrapper = ({children}) => (
758
+ <InterceptData
759
+ fulfillRequest={() => intercepted}
760
+ handler={fakeHandler}
761
+ >
762
+ {children}
763
+ </InterceptData>
764
+ );
765
+
766
+ // Act
767
+ const render = clientRenderHook(
768
+ () => useData(fakeHandler, "options"),
769
+ {
770
+ wrapper,
771
+ },
772
+ );
773
+ await notIntercepted;
774
+ await act(async (): Promise<mixed> => {
775
+ try {
776
+ await intercepted;
777
+ } catch (e) {
778
+ /* ignore, it's ok */
779
+ }
780
+ });
781
+ const result = render.result.current;
782
+
783
+ // Assert
784
+ expect(result).toEqual({
785
+ status: "error",
786
+ error: "INTERCEPTED",
787
+ });
788
+ });
789
+
790
+ it("should return the result of the handler if the interceptor returns null", async () => {
791
+ // Arrange
792
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
793
+ const fakeHandler: IRequestHandler<string, string> = {
794
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
795
+ getKey: (o) => o,
796
+ type: "MY_HANDLER",
797
+ hydrate: true,
798
+ };
799
+ const wrapper = ({children}) => (
800
+ <InterceptData
801
+ fulfillRequest={() => null}
802
+ handler={fakeHandler}
803
+ >
804
+ {children}
805
+ </InterceptData>
806
+ );
807
+
808
+ // Act
809
+ const render = clientRenderHook(
810
+ () => useData(fakeHandler, "options"),
811
+ {
812
+ wrapper,
813
+ },
814
+ );
815
+ await act((): Promise<mixed> => notIntercepted);
816
+ const result = render.result.current;
817
+
818
+ // Assert
819
+ expect(result).toEqual({
820
+ status: "success",
821
+ data: "NOT INTERCEPTED",
822
+ });
823
+ });
824
+ });
825
+ });
826
+ });