@khanacademy/wonder-blocks-data 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/es/index.js +793 -375
  3. package/dist/index.js +1203 -523
  4. package/legacy-docs.md +3 -0
  5. package/package.json +2 -2
  6. package/src/__docs__/_overview_.stories.mdx +18 -0
  7. package/src/__docs__/_overview_graphql.stories.mdx +35 -0
  8. package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
  9. package/src/__docs__/_overview_testing_.stories.mdx +123 -0
  10. package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
  11. package/src/__docs__/exports.data-error.stories.mdx +23 -0
  12. package/src/__docs__/exports.data-errors.stories.mdx +23 -0
  13. package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
  14. package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
  15. package/src/__docs__/exports.gql-error.stories.mdx +23 -0
  16. package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
  17. package/src/__docs__/exports.gql-router.stories.mdx +29 -0
  18. package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
  19. package/src/__docs__/exports.intercept-requests.stories.mdx +69 -0
  20. package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
  21. package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
  22. package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
  23. package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
  24. package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
  25. package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
  26. package/src/__docs__/exports.status.stories.mdx +31 -0
  27. package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
  28. package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
  29. package/src/__docs__/exports.use-gql.stories.mdx +73 -0
  30. package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
  31. package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
  32. package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
  33. package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
  34. package/src/__docs__/types.cached-response.stories.mdx +29 -0
  35. package/src/__docs__/types.error-options.stories.mdx +21 -0
  36. package/src/__docs__/types.gql-context.stories.mdx +20 -0
  37. package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
  38. package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
  39. package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
  40. package/src/__docs__/types.gql-operation.stories.mdx +67 -0
  41. package/src/__docs__/types.response-cache.stories.mdx +33 -0
  42. package/src/__docs__/types.result.stories.mdx +39 -0
  43. package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
  44. package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
  45. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
  46. package/src/__tests__/generated-snapshot.test.js +7 -31
  47. package/src/components/__tests__/data.test.js +160 -154
  48. package/src/components/__tests__/intercept-requests.test.js +58 -0
  49. package/src/components/data.js +22 -126
  50. package/src/components/intercept-context.js +4 -5
  51. package/src/components/intercept-requests.js +69 -0
  52. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
  53. package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
  54. package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
  55. package/src/hooks/__tests__/use-gql.test.js +1 -30
  56. package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
  57. package/src/hooks/__tests__/use-request-interception.test.js +255 -0
  58. package/src/hooks/__tests__/use-server-effect.test.js +39 -11
  59. package/src/hooks/use-cached-effect.js +225 -0
  60. package/src/hooks/use-gql-router-context.js +50 -0
  61. package/src/hooks/use-gql.js +22 -52
  62. package/src/hooks/use-hydratable-effect.js +206 -0
  63. package/src/hooks/use-request-interception.js +51 -0
  64. package/src/hooks/use-server-effect.js +14 -7
  65. package/src/hooks/use-shared-cache.js +13 -11
  66. package/src/index.js +54 -2
  67. package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
  68. package/src/util/__tests__/merge-gql-context.test.js +74 -0
  69. package/src/util/__tests__/request-fulfillment.test.js +23 -42
  70. package/src/util/__tests__/request-tracking.test.js +26 -7
  71. package/src/util/__tests__/result-from-cache-response.test.js +19 -5
  72. package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
  73. package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
  74. package/src/util/__tests__/ssr-cache.test.js +52 -52
  75. package/src/util/abort-error.js +15 -0
  76. package/src/util/data-error.js +58 -0
  77. package/src/util/get-gql-data-from-response.js +3 -2
  78. package/src/util/gql-error.js +19 -11
  79. package/src/util/merge-gql-context.js +34 -0
  80. package/src/util/request-fulfillment.js +49 -46
  81. package/src/util/request-tracking.js +69 -15
  82. package/src/util/result-from-cache-response.js +12 -16
  83. package/src/util/scoped-in-memory-cache.js +24 -47
  84. package/src/util/serializable-in-memory-cache.js +49 -0
  85. package/src/util/ssr-cache.js +9 -8
  86. package/src/util/status.js +30 -0
  87. package/src/util/types.js +18 -1
  88. package/docs.md +0 -122
  89. package/src/components/__tests__/intercept-data.test.js +0 -63
  90. package/src/components/intercept-data.js +0 -66
  91. package/src/components/intercept-data.md +0 -51
@@ -7,19 +7,29 @@ import {render, act} from "@testing-library/react";
7
7
  import * as ReactDOMServer from "react-dom/server";
8
8
  import {Server, View} from "@khanacademy/wonder-blocks-core";
9
9
 
10
+ import {clearSharedCache} from "../../hooks/use-shared-cache.js";
10
11
  import TrackData from "../track-data.js";
11
12
  import {RequestFulfillment} from "../../util/request-fulfillment.js";
12
13
  import {SsrCache} from "../../util/ssr-cache.js";
13
14
  import {RequestTracker} from "../../util/request-tracking.js";
14
- import InterceptData from "../intercept-data.js";
15
+ import InterceptRequests from "../intercept-requests.js";
15
16
  import Data from "../data.js";
17
+ import {
18
+ // TODO(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
19
+ // have fixed:
20
+ // https://github.com/import-js/eslint-plugin-import/issues/2073
21
+ // eslint-disable-next-line import/named
22
+ WhenClientSide,
23
+ } from "../../hooks/use-hydratable-effect.js";
16
24
 
17
25
  describe("Data", () => {
18
26
  beforeEach(() => {
27
+ clearSharedCache();
28
+
19
29
  const responseCache = new SsrCache();
20
30
  jest.spyOn(SsrCache, "Default", "get").mockReturnValue(responseCache);
21
31
  jest.spyOn(RequestFulfillment, "Default", "get").mockReturnValue(
22
- new RequestFulfillment(responseCache),
32
+ new RequestFulfillment(),
23
33
  );
24
34
  jest.spyOn(RequestTracker, "Default", "get").mockReturnValue(
25
35
  new RequestTracker(responseCache),
@@ -35,7 +45,7 @@ describe("Data", () => {
35
45
  jest.spyOn(Server, "isServerSide").mockReturnValue(false);
36
46
  });
37
47
 
38
- describe("without cached data", () => {
48
+ describe("without hydrated data", () => {
39
49
  beforeEach(() => {
40
50
  /**
41
51
  * Each of these test cases will not have cached data to be
@@ -107,7 +117,7 @@ describe("Data", () => {
107
117
  expect(fakeHandler).toHaveBeenCalledTimes(1);
108
118
  });
109
119
 
110
- it("should render with an error if the request rejects to an error", async () => {
120
+ it("should render with an error if the handler request rejects to an error", async () => {
111
121
  // Arrange
112
122
  const fulfillSpy = jest.spyOn(
113
123
  RequestFulfillment.Default,
@@ -122,20 +132,25 @@ describe("Data", () => {
122
132
  {fakeChildrenFn}
123
133
  </Data>,
124
134
  );
135
+
125
136
  /**
126
137
  * We wait for the fulfillment to resolve.
127
138
  */
128
- await act(() => fulfillSpy.mock.results[0].value);
139
+ await act(() =>
140
+ fulfillSpy.mock.results[0].value.catch(() => {
141
+ /* do nothing */
142
+ }),
143
+ );
129
144
 
130
145
  // Assert
131
- expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
132
- expect(fakeChildrenFn).toHaveBeenLastCalledWith({
146
+ expect(fakeChildrenFn).toHaveBeenNthCalledWith(2, {
133
147
  status: "error",
134
- error: "OH NOES!",
148
+ error: expect.any(Error),
135
149
  });
150
+ expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
136
151
  });
137
152
 
138
- it("should render with data if the request resolves with data", async () => {
153
+ it("should render with data if the handler resolves with data", async () => {
139
154
  // Arrange
140
155
  const fulfillSpy = jest.spyOn(
141
156
  RequestFulfillment.Default,
@@ -164,52 +179,68 @@ describe("Data", () => {
164
179
  });
165
180
  });
166
181
 
167
- it.each`
168
- error
169
- ${"CATASTROPHE!"}
170
- ${new Error("CATASTROPHE!")}
171
- `(
172
- "should render with an error if the request rejects with $error",
173
- async ({error}) => {
174
- // Arrange
175
- const fulfillSpy = jest
176
- .spyOn(RequestFulfillment.Default, "fulfill")
177
- .mockReturnValue(Promise.reject(error));
182
+ it("should render with aborted if the request rejects with an abort error", async () => {
183
+ // Arrange
184
+ const fulfillSpy = jest.spyOn(
185
+ RequestFulfillment.Default,
186
+ "fulfill",
187
+ );
178
188
 
179
- const fakeHandler = () => Promise.resolve("YAY!");
180
- const fakeChildrenFn = jest.fn(() => null);
181
- const consoleSpy = jest
182
- .spyOn(console, "error")
183
- .mockImplementation(() => {
184
- /* Just to shut it up */
185
- });
189
+ const abortError = new Error("bang bang, abort!");
190
+ abortError.name = "AbortError";
191
+ const fakeHandler = () => Promise.reject(abortError);
192
+ const fakeChildrenFn = jest.fn(() => null);
186
193
 
187
- // Act
188
- render(
189
- <Data handler={fakeHandler} requestId="ID">
190
- {fakeChildrenFn}
191
- </Data>,
192
- );
193
- /**
194
- * We wait for the fulfillment to reject.
195
- */
196
- await act(() =>
197
- fulfillSpy.mock.results[0].value.catch(() => {}),
198
- );
194
+ // Act
195
+ render(
196
+ <Data handler={fakeHandler} requestId="ID">
197
+ {fakeChildrenFn}
198
+ </Data>,
199
+ );
200
+ /**
201
+ * We wait for the fulfillment to resolve.
202
+ */
203
+ await act(() => fulfillSpy.mock.results[0].value);
199
204
 
200
- // Assert
201
- expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
202
- expect(fakeChildrenFn).toHaveBeenLastCalledWith({
205
+ // Assert
206
+ expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
207
+ expect(fakeChildrenFn).toHaveBeenLastCalledWith({
208
+ status: "aborted",
209
+ });
210
+ });
211
+
212
+ it("should render with an error if the RequestFulfillment rejects with an error", async () => {
213
+ // Arrange
214
+ const fulfillSpy = jest
215
+ .spyOn(RequestFulfillment.Default, "fulfill")
216
+ .mockResolvedValue({
203
217
  status: "error",
204
- error: "CATASTROPHE!",
218
+ error: new Error("CATASTROPHE!"),
205
219
  });
206
- expect(consoleSpy).toHaveBeenCalledWith(
207
- expect.stringMatching(
208
- "Unexpected error occurred during data fulfillment:(?: Error:)? CATASTROPHE!",
209
- ),
210
- );
211
- },
212
- );
220
+
221
+ const fakeHandler = () => Promise.resolve("YAY!");
222
+ const fakeChildrenFn = jest.fn(() => null);
223
+
224
+ // Act
225
+ render(
226
+ <Data handler={fakeHandler} requestId="ID">
227
+ {fakeChildrenFn}
228
+ </Data>,
229
+ );
230
+ /**
231
+ * We wait for the fulfillment to reject.
232
+ */
233
+ await act(() =>
234
+ fulfillSpy.mock.results[0].value.catch(() => {}),
235
+ );
236
+
237
+ // Assert
238
+ expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
239
+ expect(fakeChildrenFn).toHaveBeenLastCalledWith({
240
+ status: "error",
241
+ error: expect.any(Error),
242
+ });
243
+ });
213
244
 
214
245
  it("should start loading if the id changes and request not cached", async () => {
215
246
  // Arrange
@@ -239,12 +270,13 @@ describe("Data", () => {
239
270
  );
240
271
 
241
272
  // Assert
242
- // Render 1: Caused by handler changed
243
- // Render 2: Caused by result state changing to null
244
- expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
273
+ expect(fakeChildrenFn).toHaveBeenCalledTimes(1);
245
274
  expect(fakeChildrenFn).toHaveBeenLastCalledWith({
246
275
  status: "loading",
247
276
  });
277
+
278
+ // We have to do this or testing-library gets very upset.
279
+ await act(() => fulfillSpy.mock.results[0].value);
248
280
  });
249
281
 
250
282
  it("should ignore resolution of pending handler fulfillment when id changes", async () => {
@@ -319,7 +351,10 @@ describe("Data", () => {
319
351
 
320
352
  it("should ignore catastrophic request fulfillment when id changes", async () => {
321
353
  // Arrange
322
- const catastrophe = Promise.reject("CATASTROPHE!");
354
+ const catastrophe = Promise.resolve({
355
+ status: "error",
356
+ error: new Error("CATASTROPHE!"),
357
+ });
323
358
  jest.spyOn(
324
359
  RequestFulfillment.Default,
325
360
  "fulfill",
@@ -347,12 +382,12 @@ describe("Data", () => {
347
382
  // Assert
348
383
  expect(fakeChildrenFn).not.toHaveBeenCalledWith({
349
384
  status: "error",
350
- error: "CATASTROPHE!",
385
+ error: expect.any(Error),
351
386
  });
352
387
  });
353
388
 
354
389
  describe("with data interceptor", () => {
355
- it("should request data from interceptor", () => {
390
+ it("should request data from interceptor", async () => {
356
391
  // Arrange
357
392
  const fakeHandler = jest.fn().mockResolvedValue("data");
358
393
  const fakeChildrenFn = jest.fn(() => null);
@@ -362,22 +397,20 @@ describe("Data", () => {
362
397
 
363
398
  // Act
364
399
  render(
365
- <InterceptData
366
- requestId="ID"
367
- handler={interceptHandler}
368
- >
400
+ <InterceptRequests interceptor={interceptHandler}>
369
401
  <Data handler={fakeHandler} requestId="ID">
370
402
  {fakeChildrenFn}
371
403
  </Data>
372
- </InterceptData>,
404
+ </InterceptRequests>,
373
405
  );
406
+ await act(() => interceptHandler.mock.results[0].value);
374
407
 
375
408
  // Assert
376
409
  expect(interceptHandler).toHaveBeenCalledTimes(1);
377
410
  expect(fakeHandler).not.toHaveBeenCalled();
378
411
  });
379
412
 
380
- it("should invoke handler method if interceptor method returns null", () => {
413
+ it("should invoke handler method if interceptor method returns null", async () => {
381
414
  // Arrange
382
415
  const fakeHandler = jest.fn().mockResolvedValue("data");
383
416
  const fakeChildrenFn = jest.fn(() => null);
@@ -385,59 +418,21 @@ describe("Data", () => {
385
418
 
386
419
  // Act
387
420
  render(
388
- <InterceptData
389
- handler={interceptHandler}
390
- requestId="ID"
391
- >
421
+ <InterceptRequests interceptor={interceptHandler}>
392
422
  <Data handler={fakeHandler} requestId="ID">
393
423
  {fakeChildrenFn}
394
424
  </Data>
395
- </InterceptData>,
425
+ </InterceptRequests>,
396
426
  );
427
+ await act(() => fakeHandler.mock.results[0].value);
397
428
 
398
429
  // Assert
399
430
  expect(interceptHandler).toHaveBeenCalledTimes(1);
400
431
  expect(fakeHandler).toHaveBeenCalledTimes(1);
401
432
  });
402
433
  });
403
- });
404
-
405
- describe("with cache data", () => {
406
- beforeEach(() => {
407
- /**
408
- * Each of these test cases will start out with some cached data
409
- * retrieved.
410
- */
411
- jest.spyOn(
412
- SsrCache.Default,
413
- "getEntry",
414
- // Fake once because that's how the cache would work,
415
- // deleting the hydrated value as soon as it was used.
416
- ).mockReturnValueOnce({
417
- data: "YAY! DATA!",
418
- });
419
- });
420
434
 
421
- it("should render first time with the cached data", () => {
422
- // Arrange
423
- const fakeHandler = () => Promise.resolve("data");
424
- const fakeChildrenFn = jest.fn(() => null);
425
-
426
- // Act
427
- render(
428
- <Data handler={fakeHandler} requestId="ID">
429
- {fakeChildrenFn}
430
- </Data>,
431
- );
432
-
433
- // Assert
434
- expect(fakeChildrenFn).toHaveBeenCalledWith({
435
- status: "success",
436
- data: "YAY! DATA!",
437
- });
438
- });
439
-
440
- it("should retain old data while reloading if showOldDataWhileLoading is true", async () => {
435
+ it("should retain old data while reloading if retainResultOnChange is true", async () => {
441
436
  // Arrange
442
437
  const response1 = Promise.resolve("data1");
443
438
  const response2 = Promise.resolve("data2");
@@ -449,49 +444,76 @@ describe("Data", () => {
449
444
  const wrapper = render(
450
445
  <Data
451
446
  handler={fakeHandler1}
452
- requestId="ID"
453
- showOldDataWhileLoading={false}
447
+ requestId="ID1"
448
+ retainResultOnChange={true}
454
449
  >
455
450
  {fakeChildrenFn}
456
451
  </Data>,
457
452
  );
453
+ fakeChildrenFn.mockClear();
454
+ await act(() => response1);
458
455
  wrapper.rerender(
459
456
  <Data
460
457
  handler={fakeHandler2}
461
- requestId="ID"
462
- showOldDataWhileLoading={true}
458
+ requestId="ID2"
459
+ retainResultOnChange={true}
463
460
  >
464
461
  {fakeChildrenFn}
465
462
  </Data>,
466
463
  );
464
+ await act(() => response2);
467
465
 
468
466
  // Assert
469
467
  expect(fakeChildrenFn).not.toHaveBeenCalledWith({
470
468
  status: "loading",
471
469
  });
470
+ expect(fakeChildrenFn).toHaveBeenCalledWith({
471
+ status: "success",
472
+ data: "data1",
473
+ });
474
+ expect(fakeChildrenFn).toHaveBeenLastCalledWith({
475
+ status: "success",
476
+ data: "data2",
477
+ });
472
478
  });
479
+ });
473
480
 
474
- it("should not request data when alwaysRequestOnHydration is false and cache has a valid data result", () => {
481
+ describe("with hydrated data", () => {
482
+ beforeEach(() => {
483
+ /**
484
+ * Each of these test cases will start out with some cached data
485
+ * retrieved.
486
+ */
487
+ jest.spyOn(
488
+ SsrCache.Default,
489
+ "getEntry",
490
+ // Fake once because that's how the cache would work,
491
+ // deleting the hydrated value as soon as it was used.
492
+ ).mockReturnValueOnce({
493
+ data: "YAY! DATA!",
494
+ });
495
+ });
496
+
497
+ it("should render first time with the cached data", () => {
475
498
  // Arrange
476
- const fakeHandler = jest.fn().mockResolvedValue("data");
499
+ const fakeHandler = () => Promise.resolve("data");
477
500
  const fakeChildrenFn = jest.fn(() => null);
478
501
 
479
502
  // Act
480
503
  render(
481
- <Data
482
- handler={fakeHandler}
483
- requestId="ID"
484
- alwaysRequestOnHydration={false}
485
- >
504
+ <Data handler={fakeHandler} requestId="ID">
486
505
  {fakeChildrenFn}
487
506
  </Data>,
488
507
  );
489
508
 
490
509
  // Assert
491
- expect(fakeHandler).not.toHaveBeenCalled();
510
+ expect(fakeChildrenFn).toHaveBeenCalledWith({
511
+ status: "success",
512
+ data: "YAY! DATA!",
513
+ });
492
514
  });
493
515
 
494
- it("should request data if cached data value is valid but alwaysRequestOnHydration is true", () => {
516
+ it("should not request data when clientBehavior is WhenClientSide.ExecuteWhenNoSuccessResult and cache has a valid success result", () => {
495
517
  // Arrange
496
518
  const fakeHandler = jest.fn().mockResolvedValue("data");
497
519
  const fakeChildrenFn = jest.fn(() => null);
@@ -501,43 +523,34 @@ describe("Data", () => {
501
523
  <Data
502
524
  handler={fakeHandler}
503
525
  requestId="ID"
504
- alwaysRequestOnHydration={true}
526
+ clientBehavior={
527
+ WhenClientSide.ExecuteWhenNoSuccessResult
528
+ }
505
529
  >
506
530
  {fakeChildrenFn}
507
531
  </Data>,
508
532
  );
509
533
 
510
534
  // Assert
511
- expect(fakeHandler).toHaveBeenCalledTimes(1);
512
- });
513
- });
514
-
515
- describe("with cached abort", () => {
516
- beforeEach(() => {
517
- /**
518
- * Each of these test cases will start out with a cached abort.
519
- */
520
- jest.spyOn(
521
- SsrCache.Default,
522
- "getEntry",
523
- // Fake once because that's how the cache would work,
524
- // deleting the hydrated value as soon as it was used.
525
- ).mockReturnValueOnce({
526
- data: null,
527
- });
535
+ expect(fakeHandler).not.toHaveBeenCalled();
528
536
  });
529
537
 
530
- it("should request data if cached data value is null (i.e. represents an aborted request)", () => {
538
+ it("should request data if cached data value is valid but clientBehavior is WhenClientSide.AlwaysExecute is true", async () => {
531
539
  // Arrange
532
540
  const fakeHandler = jest.fn().mockResolvedValue("data");
533
541
  const fakeChildrenFn = jest.fn(() => null);
534
542
 
535
543
  // Act
536
544
  render(
537
- <Data handler={fakeHandler} requestId="ID">
545
+ <Data
546
+ handler={fakeHandler}
547
+ requestId="ID"
548
+ clientBehavior={WhenClientSide.AlwaysExecute}
549
+ >
538
550
  {fakeChildrenFn}
539
551
  </Data>,
540
552
  );
553
+ await act(() => fakeHandler.mock.results[0].value);
541
554
 
542
555
  // Assert
543
556
  expect(fakeHandler).toHaveBeenCalledTimes(1);
@@ -559,7 +572,7 @@ describe("Data", () => {
559
572
  });
560
573
  });
561
574
 
562
- it("should always request data if there's a cached error", () => {
575
+ it("should always request data if there's a cached error", async () => {
563
576
  // Arrange
564
577
  const fakeHandler = jest.fn().mockResolvedValue("data");
565
578
  const fakeChildrenFn = jest.fn(() => null);
@@ -570,6 +583,8 @@ describe("Data", () => {
570
583
  {fakeChildrenFn}
571
584
  </Data>,
572
585
  );
586
+ // Have to await the promise in an act to keep TL/R happy.
587
+ await act(() => fakeHandler.mock.results[0].value);
573
588
 
574
589
  // Assert
575
590
  expect(fakeHandler).toHaveBeenCalledTimes(1);
@@ -662,14 +677,11 @@ describe("Data", () => {
662
677
 
663
678
  // Act
664
679
  ReactDOMServer.renderToString(
665
- <InterceptData
666
- handler={interceptedHandler}
667
- requestId="ID"
668
- >
680
+ <InterceptRequests interceptor={interceptedHandler}>
669
681
  <Data handler={fakeHandler} requestId="ID">
670
682
  {fakeChildrenFn}
671
683
  </Data>
672
- </InterceptData>,
684
+ </InterceptRequests>,
673
685
  );
674
686
 
675
687
  // Assert
@@ -692,14 +704,11 @@ describe("Data", () => {
692
704
  // Act
693
705
  ReactDOMServer.renderToString(
694
706
  <TrackData>
695
- <InterceptData
696
- requestId="ID"
697
- handler={interceptedHandler}
698
- >
707
+ <InterceptRequests interceptor={interceptedHandler}>
699
708
  <Data handler={fakeHandler} requestId="ID">
700
709
  {fakeChildrenFn}
701
710
  </Data>
702
- </InterceptData>
711
+ </InterceptRequests>
703
712
  </TrackData>,
704
713
  );
705
714
 
@@ -777,7 +786,7 @@ describe("Data", () => {
777
786
  // Assert
778
787
  expect(fakeChildrenFn).toHaveBeenCalledWith({
779
788
  status: "error",
780
- error: "OH NO! IT GO BOOM",
789
+ error: expect.any(Error),
781
790
  });
782
791
  });
783
792
 
@@ -817,14 +826,11 @@ describe("Data", () => {
817
826
 
818
827
  // Act
819
828
  ReactDOMServer.renderToString(
820
- <InterceptData
821
- handler={interceptHandler}
822
- requestId="ID"
823
- >
829
+ <InterceptRequests interceptor={interceptHandler}>
824
830
  <Data handler={fakeHandler} requestId="ID">
825
831
  {fakeChildrenFn}
826
832
  </Data>
827
- </InterceptData>,
833
+ </InterceptRequests>,
828
834
  );
829
835
 
830
836
  // Assert
@@ -0,0 +1,58 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {render} from "@testing-library/react";
4
+
5
+ import InterceptContext from "../intercept-context.js";
6
+ import InterceptRequests from "../intercept-requests.js";
7
+
8
+ describe("InterceptRequests", () => {
9
+ afterEach(() => {
10
+ jest.resetAllMocks();
11
+ });
12
+
13
+ it("should update context with fulfillRequest method", () => {
14
+ // Arrange
15
+ const fakeHandler = (requestId): Promise<string> =>
16
+ Promise.resolve("data");
17
+ const props = {
18
+ interceptor: fakeHandler,
19
+ };
20
+ const captureContextFn = jest.fn();
21
+
22
+ // Act
23
+ render(
24
+ <InterceptRequests {...props}>
25
+ <InterceptContext.Consumer>
26
+ {captureContextFn}
27
+ </InterceptContext.Consumer>
28
+ </InterceptRequests>,
29
+ );
30
+
31
+ // Assert
32
+ expect(captureContextFn).toHaveBeenCalledWith([fakeHandler]);
33
+ });
34
+
35
+ it("should override parent InterceptRequests", () => {
36
+ // Arrange
37
+ const fakeHandler1 = jest.fn();
38
+ const fakeHandler2 = jest.fn();
39
+ const captureContextFn = jest.fn();
40
+
41
+ // Act
42
+ render(
43
+ <InterceptRequests interceptor={fakeHandler1}>
44
+ <InterceptRequests interceptor={fakeHandler2}>
45
+ <InterceptContext.Consumer>
46
+ {captureContextFn}
47
+ </InterceptContext.Consumer>
48
+ </InterceptRequests>
49
+ </InterceptRequests>,
50
+ );
51
+
52
+ // Assert
53
+ expect(captureContextFn).toHaveBeenCalledWith([
54
+ fakeHandler1,
55
+ fakeHandler2,
56
+ ]);
57
+ });
58
+ });