@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.
- package/CHANGELOG.md +31 -0
- package/dist/es/index.js +793 -375
- package/dist/index.js +1203 -523
- package/legacy-docs.md +3 -0
- package/package.json +2 -2
- package/src/__docs__/_overview_.stories.mdx +18 -0
- package/src/__docs__/_overview_graphql.stories.mdx +35 -0
- package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
- package/src/__docs__/_overview_testing_.stories.mdx +123 -0
- package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
- package/src/__docs__/exports.data-error.stories.mdx +23 -0
- package/src/__docs__/exports.data-errors.stories.mdx +23 -0
- package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
- package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
- package/src/__docs__/exports.gql-error.stories.mdx +23 -0
- package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
- package/src/__docs__/exports.gql-router.stories.mdx +29 -0
- package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
- package/src/__docs__/exports.intercept-requests.stories.mdx +69 -0
- package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
- package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
- package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
- package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
- package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
- package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
- package/src/__docs__/exports.status.stories.mdx +31 -0
- package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
- package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
- package/src/__docs__/exports.use-gql.stories.mdx +73 -0
- package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
- package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
- package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
- package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
- package/src/__docs__/types.cached-response.stories.mdx +29 -0
- package/src/__docs__/types.error-options.stories.mdx +21 -0
- package/src/__docs__/types.gql-context.stories.mdx +20 -0
- package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
- package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation.stories.mdx +67 -0
- package/src/__docs__/types.response-cache.stories.mdx +33 -0
- package/src/__docs__/types.result.stories.mdx +39 -0
- package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
- package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
- package/src/__tests__/generated-snapshot.test.js +7 -31
- package/src/components/__tests__/data.test.js +160 -154
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +22 -126
- package/src/components/intercept-context.js +4 -5
- package/src/components/intercept-requests.js +69 -0
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
- package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
- package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -30
- package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +39 -11
- package/src/hooks/use-cached-effect.js +225 -0
- package/src/hooks/use-gql-router-context.js +50 -0
- package/src/hooks/use-gql.js +22 -52
- package/src/hooks/use-hydratable-effect.js +206 -0
- package/src/hooks/use-request-interception.js +51 -0
- package/src/hooks/use-server-effect.js +14 -7
- package/src/hooks/use-shared-cache.js +13 -11
- package/src/index.js +54 -2
- package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/merge-gql-context.test.js +74 -0
- package/src/util/__tests__/request-fulfillment.test.js +23 -42
- package/src/util/__tests__/request-tracking.test.js +26 -7
- package/src/util/__tests__/result-from-cache-response.test.js +19 -5
- package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
- package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
- package/src/util/__tests__/ssr-cache.test.js +52 -52
- package/src/util/abort-error.js +15 -0
- package/src/util/data-error.js +58 -0
- package/src/util/get-gql-data-from-response.js +3 -2
- package/src/util/gql-error.js +19 -11
- package/src/util/merge-gql-context.js +34 -0
- package/src/util/request-fulfillment.js +49 -46
- package/src/util/request-tracking.js +69 -15
- package/src/util/result-from-cache-response.js +12 -16
- package/src/util/scoped-in-memory-cache.js +24 -47
- package/src/util/serializable-in-memory-cache.js +49 -0
- package/src/util/ssr-cache.js +9 -8
- package/src/util/status.js +30 -0
- package/src/util/types.js +18 -1
- package/docs.md +0 -122
- package/src/components/__tests__/intercept-data.test.js +0 -63
- package/src/components/intercept-data.js +0 -66
- 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
|
|
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(
|
|
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
|
|
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(() =>
|
|
139
|
+
await act(() =>
|
|
140
|
+
fulfillSpy.mock.results[0].value.catch(() => {
|
|
141
|
+
/* do nothing */
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
129
144
|
|
|
130
145
|
// Assert
|
|
131
|
-
expect(fakeChildrenFn).
|
|
132
|
-
expect(fakeChildrenFn).toHaveBeenLastCalledWith({
|
|
146
|
+
expect(fakeChildrenFn).toHaveBeenNthCalledWith(2, {
|
|
133
147
|
status: "error",
|
|
134
|
-
error:
|
|
148
|
+
error: expect.any(Error),
|
|
135
149
|
});
|
|
150
|
+
expect(fakeChildrenFn).toHaveBeenCalledTimes(2);
|
|
136
151
|
});
|
|
137
152
|
|
|
138
|
-
it("should render with data if the
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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="
|
|
453
|
-
|
|
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="
|
|
462
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
510
|
+
expect(fakeChildrenFn).toHaveBeenCalledWith({
|
|
511
|
+
status: "success",
|
|
512
|
+
data: "YAY! DATA!",
|
|
513
|
+
});
|
|
492
514
|
});
|
|
493
515
|
|
|
494
|
-
it("should request data
|
|
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
|
-
|
|
526
|
+
clientBehavior={
|
|
527
|
+
WhenClientSide.ExecuteWhenNoSuccessResult
|
|
528
|
+
}
|
|
505
529
|
>
|
|
506
530
|
{fakeChildrenFn}
|
|
507
531
|
</Data>,
|
|
508
532
|
);
|
|
509
533
|
|
|
510
534
|
// Assert
|
|
511
|
-
expect(fakeHandler).
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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:
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
+
});
|