@khanacademy/wonder-blocks-data 5.0.1 → 7.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 (84) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/es/index.js +767 -371
  3. package/dist/index.js +1194 -564
  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/{components/intercept-requests.md → __docs__/exports.intercept-requests.stories.mdx} +16 -1
  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 +50 -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 +0 -24
  47. package/src/components/__tests__/data.test.js +149 -128
  48. package/src/components/data.js +22 -112
  49. package/src/components/intercept-requests.js +1 -1
  50. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
  51. package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
  52. package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
  53. package/src/hooks/__tests__/use-gql.test.js +1 -30
  54. package/src/hooks/__tests__/use-hydratable-effect.test.js +705 -0
  55. package/src/hooks/__tests__/use-server-effect.test.js +90 -11
  56. package/src/hooks/use-cached-effect.js +225 -0
  57. package/src/hooks/use-gql-router-context.js +50 -0
  58. package/src/hooks/use-gql.js +22 -52
  59. package/src/hooks/use-hydratable-effect.js +206 -0
  60. package/src/hooks/use-request-interception.js +20 -23
  61. package/src/hooks/use-server-effect.js +42 -10
  62. package/src/hooks/use-shared-cache.js +13 -11
  63. package/src/index.js +53 -3
  64. package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
  65. package/src/util/__tests__/merge-gql-context.test.js +74 -0
  66. package/src/util/__tests__/request-fulfillment.test.js +23 -42
  67. package/src/util/__tests__/request-tracking.test.js +26 -7
  68. package/src/util/__tests__/result-from-cache-response.test.js +19 -5
  69. package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
  70. package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
  71. package/src/util/__tests__/ssr-cache.test.js +52 -52
  72. package/src/util/data-error.js +58 -0
  73. package/src/util/get-gql-data-from-response.js +3 -2
  74. package/src/util/gql-error.js +19 -11
  75. package/src/util/merge-gql-context.js +34 -0
  76. package/src/util/request-fulfillment.js +49 -46
  77. package/src/util/request-tracking.js +69 -15
  78. package/src/util/result-from-cache-response.js +12 -16
  79. package/src/util/scoped-in-memory-cache.js +24 -47
  80. package/src/util/serializable-in-memory-cache.js +49 -0
  81. package/src/util/ssr-cache.js +9 -8
  82. package/src/util/status.js +30 -0
  83. package/src/util/types.js +18 -1
  84. package/docs.md +0 -122
@@ -0,0 +1,507 @@
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
+ import {Status} from "../../util/status.js";
10
+
11
+ import {RequestFulfillment} from "../../util/request-fulfillment.js";
12
+ import * as UseRequestInterception from "../use-request-interception.js";
13
+ import * as UseSharedCache from "../use-shared-cache.js";
14
+
15
+ import {useCachedEffect} from "../use-cached-effect.js";
16
+
17
+ jest.mock("../use-request-interception.js");
18
+ jest.mock("../use-shared-cache.js");
19
+
20
+ describe("#useCachedEffect", () => {
21
+ beforeEach(() => {
22
+ jest.resetAllMocks();
23
+
24
+ // When we have request aborting and things, this can be nicer, but
25
+ // for now, let's just clear out inflight requests between tests
26
+ // by being cheeky.
27
+ RequestFulfillment.Default._requests = {};
28
+
29
+ // Simple implementation of request interception that just returns
30
+ // the handler.
31
+ jest.spyOn(
32
+ UseRequestInterception,
33
+ "useRequestInterception",
34
+ ).mockImplementation((_, handler) => handler);
35
+
36
+ // We need the cache to work a little so that we get our result.
37
+ const cache = {};
38
+ jest.spyOn(UseSharedCache, "useSharedCache").mockImplementation(
39
+ (id, _, defaultValue) => {
40
+ const setCache = (v) => (cache[id] = v);
41
+ return [cache[id] ?? defaultValue, setCache];
42
+ },
43
+ );
44
+ });
45
+
46
+ describe("when server-side", () => {
47
+ beforeEach(() => {
48
+ jest.spyOn(Server, "isServerSide").mockReturnValue(true);
49
+ });
50
+
51
+ it("should call useRequestInterception", () => {
52
+ // Arrange
53
+ const useRequestInterceptSpy = jest
54
+ .spyOn(UseRequestInterception, "useRequestInterception")
55
+ .mockReturnValue(jest.fn());
56
+ const fakeHandler = jest.fn();
57
+
58
+ // Act
59
+ serverRenderHook(() => useCachedEffect("ID", fakeHandler));
60
+
61
+ // Assert
62
+ expect(useRequestInterceptSpy).toHaveBeenCalledWith(
63
+ "ID",
64
+ fakeHandler,
65
+ );
66
+ });
67
+
68
+ it.each`
69
+ scope | expectedScope
70
+ ${undefined} | ${"useCachedEffect"}
71
+ ${"foo"} | ${"foo"}
72
+ `(
73
+ "should call useSharedCache with id, scope=$scope, without a default",
74
+ ({scope, cachedResult, expectedScope}) => {
75
+ const fakeHandler = jest.fn();
76
+ const useSharedCacheSpy = jest.spyOn(
77
+ UseSharedCache,
78
+ "useSharedCache",
79
+ );
80
+
81
+ // Act
82
+ serverRenderHook(() =>
83
+ useCachedEffect("ID", fakeHandler, {scope}),
84
+ );
85
+
86
+ // Assert
87
+ expect(useSharedCacheSpy).toHaveBeenCalledWith(
88
+ "ID",
89
+ expectedScope,
90
+ );
91
+ },
92
+ );
93
+
94
+ it("should not request data", () => {
95
+ // Arrange
96
+ const fakeHandler = jest.fn().mockResolvedValue("data");
97
+
98
+ // Act
99
+ serverRenderHook(() => useCachedEffect("ID", fakeHandler));
100
+
101
+ // Assert
102
+ expect(fakeHandler).not.toHaveBeenCalled();
103
+ });
104
+
105
+ describe("without cached result", () => {
106
+ it("should return a loading result", () => {
107
+ // Arrange
108
+ const fakeHandler = jest.fn();
109
+
110
+ // Act
111
+ const {
112
+ result: {current: result},
113
+ } = serverRenderHook(() => useCachedEffect("ID", fakeHandler));
114
+
115
+ // Assert
116
+ expect(result).toStrictEqual(Status.loading());
117
+ });
118
+ });
119
+
120
+ describe("with cached result", () => {
121
+ it("should return the result", () => {
122
+ // Arrange
123
+ const fakeHandler = jest.fn();
124
+ const cachedResult = Status.success("data");
125
+ jest.spyOn(UseSharedCache, "useSharedCache").mockReturnValue([
126
+ cachedResult,
127
+ jest.fn(),
128
+ ]);
129
+
130
+ // Act
131
+ const {
132
+ result: {current: result},
133
+ } = serverRenderHook(() => useCachedEffect("ID", fakeHandler));
134
+
135
+ // Assert
136
+ expect(result).toEqual(cachedResult);
137
+ });
138
+ });
139
+ });
140
+
141
+ describe("when client-side", () => {
142
+ beforeEach(() => {
143
+ jest.spyOn(Server, "isServerSide").mockReturnValue(false);
144
+ });
145
+
146
+ it("should call useRequestInterception", () => {
147
+ // Arrange
148
+ const useRequestInterceptSpy = jest
149
+ .spyOn(UseRequestInterception, "useRequestInterception")
150
+ .mockReturnValue(jest.fn());
151
+ const fakeHandler = jest.fn();
152
+
153
+ // Act
154
+ clientRenderHook(() => useCachedEffect("ID", fakeHandler));
155
+
156
+ // Assert
157
+ expect(useRequestInterceptSpy).toHaveBeenCalledWith(
158
+ "ID",
159
+ fakeHandler,
160
+ );
161
+ });
162
+
163
+ it("should fulfill request when there is no cached value", () => {
164
+ // Arrange
165
+ const fakeHandler = jest.fn();
166
+ jest.spyOn(UseSharedCache, "useSharedCache").mockReturnValue([
167
+ null,
168
+ jest.fn(),
169
+ ]);
170
+
171
+ // Act
172
+ clientRenderHook(() => useCachedEffect("ID", fakeHandler));
173
+
174
+ // Assert
175
+ expect(fakeHandler).toHaveBeenCalled();
176
+ });
177
+
178
+ it("should share inflight requests for the same requestId", () => {
179
+ // Arrange
180
+ const pending = new Promise((resolve, reject) => {
181
+ /*pending*/
182
+ });
183
+ const fakeHandler = jest.fn().mockReturnValue(pending);
184
+
185
+ // Act
186
+ clientRenderHook(() => useCachedEffect("ID", fakeHandler));
187
+ clientRenderHook(() => useCachedEffect("ID", fakeHandler));
188
+
189
+ // Assert
190
+ expect(fakeHandler).toHaveBeenCalledTimes(1);
191
+ });
192
+
193
+ it.each`
194
+ cachedResult
195
+ ${Status.error(new Error("some error"))}
196
+ ${Status.success("data")}
197
+ ${Status.aborted()}
198
+ `(
199
+ "should not fulfill request when there is a cached response of $cachedResult",
200
+ ({cachedResult}) => {
201
+ const fakeHandler = jest.fn();
202
+ jest.spyOn(UseSharedCache, "useSharedCache").mockReturnValue([
203
+ cachedResult,
204
+ jest.fn(),
205
+ ]);
206
+
207
+ // Act
208
+ clientRenderHook(() => useCachedEffect("ID", fakeHandler));
209
+
210
+ // Assert
211
+ expect(fakeHandler).not.toHaveBeenCalled();
212
+ },
213
+ );
214
+
215
+ it("should fulfill request once only if requestId does not change", async () => {
216
+ // Arrange
217
+ const fakeHandler = jest.fn().mockResolvedValue("data");
218
+
219
+ // Act
220
+ const {rerender, waitForNextUpdate} = clientRenderHook(() =>
221
+ useCachedEffect("ID", fakeHandler),
222
+ );
223
+ rerender();
224
+ await waitForNextUpdate();
225
+
226
+ // Assert
227
+ expect(fakeHandler).toHaveBeenCalledTimes(1);
228
+ });
229
+
230
+ it("should fulfill request again if requestId changes", async () => {
231
+ // Arrange
232
+ const fakeHandler = jest.fn().mockResolvedValue("data");
233
+
234
+ // Act
235
+ const {rerender, waitForNextUpdate} = clientRenderHook(
236
+ ({requestId}) => useCachedEffect(requestId, fakeHandler),
237
+ {
238
+ initialProps: {requestId: "ID"},
239
+ },
240
+ );
241
+ rerender({requestId: "ID2"});
242
+ await waitForNextUpdate();
243
+
244
+ // Assert
245
+ expect(fakeHandler).toHaveBeenCalledTimes(2);
246
+ });
247
+
248
+ it("should update shared cache with result when request is fulfilled", async () => {
249
+ // Arrange
250
+ const setCacheFn = jest.fn();
251
+ jest.spyOn(UseSharedCache, "useSharedCache").mockReturnValue([
252
+ null,
253
+ setCacheFn,
254
+ ]);
255
+ const fakeHandler = jest.fn().mockResolvedValue("DATA");
256
+
257
+ // Act
258
+ const {waitForNextUpdate} = clientRenderHook(() =>
259
+ useCachedEffect("ID", fakeHandler),
260
+ );
261
+ await waitForNextUpdate();
262
+
263
+ // Assert
264
+ expect(setCacheFn).toHaveBeenCalledWith(Status.success("DATA"));
265
+ });
266
+
267
+ it("should ignore inflight request if requestId changes", async () => {
268
+ // Arrange
269
+ const response1 = Promise.resolve("DATA1");
270
+ const response2 = Promise.resolve("DATA2");
271
+ const fakeHandler = jest
272
+ .fn()
273
+ .mockReturnValueOnce(response1)
274
+ .mockReturnValueOnce(response2);
275
+
276
+ // Act
277
+ const {rerender, result} = clientRenderHook(
278
+ ({requestId}) => useCachedEffect(requestId, fakeHandler),
279
+ {
280
+ initialProps: {requestId: "ID"},
281
+ },
282
+ );
283
+ rerender({requestId: "ID2"});
284
+ await act((): Promise<mixed> =>
285
+ Promise.all([response1, response2]),
286
+ );
287
+
288
+ // Assert
289
+ expect(result.all).not.toContainEqual(Status.success("DATA1"));
290
+ });
291
+
292
+ it("should return result of fulfilled request for current requestId", async () => {
293
+ // Arrange
294
+ const response1 = Promise.resolve("DATA1");
295
+ const response2 = Promise.resolve("DATA2");
296
+ const fakeHandler = jest
297
+ .fn()
298
+ .mockReturnValueOnce(response1)
299
+ .mockReturnValueOnce(response2);
300
+
301
+ // Act
302
+ const {rerender, result} = clientRenderHook(
303
+ ({requestId}) => useCachedEffect(requestId, fakeHandler),
304
+ {
305
+ initialProps: {requestId: "ID"},
306
+ },
307
+ );
308
+ rerender({requestId: "ID2"});
309
+ await act((): Promise<mixed> =>
310
+ Promise.all([response1, response2]),
311
+ );
312
+
313
+ // Assert
314
+ expect(result.current).toStrictEqual(Status.success("DATA2"));
315
+ });
316
+
317
+ it("should not fulfill request when skip is true", () => {
318
+ // Arrange
319
+ const fakeHandler = jest.fn();
320
+
321
+ // Act
322
+ clientRenderHook(() =>
323
+ useCachedEffect("ID", fakeHandler, {skip: true}),
324
+ );
325
+
326
+ // Assert
327
+ expect(fakeHandler).not.toHaveBeenCalled();
328
+ });
329
+
330
+ it("should ignore inflight request if skip changes", async () => {
331
+ // Arrange
332
+ const response1 = Promise.resolve("DATA1");
333
+ const fakeHandler = jest.fn().mockReturnValueOnce(response1);
334
+
335
+ // Act
336
+ const {rerender, result} = clientRenderHook(
337
+ ({skip}) => useCachedEffect("ID", fakeHandler, {skip}),
338
+ {
339
+ initialProps: {skip: false},
340
+ },
341
+ );
342
+ rerender({skip: true});
343
+ await act((): Promise<mixed> => response1);
344
+
345
+ // Assert
346
+ expect(result.all).not.toContainEqual(Status.success("DATA1"));
347
+ });
348
+
349
+ it("should not ignore inflight request if handler changes", async () => {
350
+ // Arrange
351
+ const response1 = Promise.resolve("DATA1");
352
+ const response2 = Promise.resolve("DATA2");
353
+ const fakeHandler1 = jest.fn().mockReturnValueOnce(response1);
354
+ const fakeHandler2 = jest.fn().mockReturnValueOnce(response2);
355
+
356
+ // Act
357
+ const {rerender, result} = clientRenderHook(
358
+ ({handler}) => useCachedEffect("ID", handler),
359
+ {
360
+ initialProps: {handler: fakeHandler1},
361
+ },
362
+ );
363
+ rerender({handler: fakeHandler2});
364
+ await act((): Promise<mixed> =>
365
+ Promise.all([response1, response2]),
366
+ );
367
+
368
+ // Assert
369
+ expect(result.current).toStrictEqual(Status.success("DATA1"));
370
+ });
371
+
372
+ it("should not ignore inflight request if options (other than skip) change", async () => {
373
+ // Arrange
374
+ const response1 = Promise.resolve("DATA1");
375
+ const fakeHandler = jest.fn().mockReturnValueOnce(response1);
376
+
377
+ // Act
378
+ const {rerender, result} = clientRenderHook(
379
+ ({options}) => useCachedEffect("ID", fakeHandler),
380
+ {
381
+ initialProps: {options: undefined},
382
+ },
383
+ );
384
+ rerender({
385
+ options: {
386
+ scope: "BLAH!",
387
+ },
388
+ });
389
+ await act((): Promise<mixed> => response1);
390
+
391
+ // Assert
392
+ expect(result.current).toStrictEqual(Status.success("DATA1"));
393
+ });
394
+
395
+ it("should return previous result when requestId changes and retainResultOnChange is true", async () => {
396
+ // Arrange
397
+ const response1 = Promise.resolve("DATA1");
398
+ const response2 = Promise.resolve("DATA2");
399
+ const fakeHandler = jest
400
+ .fn()
401
+ .mockReturnValueOnce(response1)
402
+ .mockReturnValueOnce(response2);
403
+
404
+ // Act
405
+ const {
406
+ rerender,
407
+ result: hookResult,
408
+ waitForNextUpdate,
409
+ } = clientRenderHook(
410
+ ({requestId}) =>
411
+ useCachedEffect(requestId, fakeHandler, {
412
+ retainResultOnChange: true,
413
+ }),
414
+ {
415
+ initialProps: {requestId: "ID"},
416
+ },
417
+ );
418
+ await act((): Promise<mixed> => response1);
419
+ rerender({requestId: "ID2"});
420
+ const result = hookResult.current;
421
+ await waitForNextUpdate();
422
+
423
+ // Assert
424
+ expect(result).toStrictEqual(Status.success("DATA1"));
425
+ });
426
+
427
+ it("should return loading status when requestId changes and retainResultOnChange is false", async () => {
428
+ // Arrange
429
+ const response1 = Promise.resolve("DATA1");
430
+ const response2 = new Promise(() => {
431
+ /*pending*/
432
+ });
433
+ const fakeHandler = jest
434
+ .fn()
435
+ .mockReturnValueOnce(response1)
436
+ .mockReturnValueOnce(response2);
437
+
438
+ // Act
439
+ const {rerender, result} = clientRenderHook(
440
+ ({requestId}) =>
441
+ useCachedEffect(requestId, fakeHandler, {
442
+ retainResultOnChange: false,
443
+ }),
444
+ {
445
+ initialProps: {requestId: "ID"},
446
+ },
447
+ );
448
+ await act((): Promise<mixed> => response1);
449
+ rerender({requestId: "ID2"});
450
+
451
+ // Assert
452
+ expect(result.current).toStrictEqual(Status.loading());
453
+ });
454
+
455
+ it("should trigger render when request is fulfilled and onResultChanged is undefined", async () => {
456
+ // Arrange
457
+ const response = Promise.resolve("DATA");
458
+ const fakeHandler = jest.fn().mockReturnValue(response);
459
+
460
+ // Act
461
+ const {result} = clientRenderHook(() =>
462
+ useCachedEffect("ID", fakeHandler),
463
+ );
464
+ await act((): Promise<mixed> => response);
465
+
466
+ // Assert
467
+ expect(result.current).toStrictEqual(Status.success("DATA"));
468
+ });
469
+
470
+ it("should not trigger render when request is fulfilled and onResultChanged is defined", async () => {
471
+ // Arrange
472
+ const response = Promise.resolve("DATA");
473
+ const fakeHandler = jest.fn().mockReturnValue(response);
474
+
475
+ // Act
476
+ const {result} = clientRenderHook(() =>
477
+ useCachedEffect("ID", fakeHandler, {
478
+ onResultChanged: () => {},
479
+ }),
480
+ );
481
+ await act((): Promise<mixed> => response);
482
+
483
+ // Assert
484
+ expect(result.current).toStrictEqual(Status.loading());
485
+ });
486
+
487
+ it("should call onResultChanged when request is fulfilled and onResultChanged is defined", async () => {
488
+ // Arrange
489
+ const response = Promise.resolve("DATA");
490
+ const fakeHandler = jest.fn().mockReturnValue(response);
491
+ const onResultChanged = jest.fn();
492
+
493
+ // Act
494
+ clientRenderHook(() =>
495
+ useCachedEffect("ID", fakeHandler, {
496
+ onResultChanged,
497
+ }),
498
+ );
499
+ await act((): Promise<mixed> => response);
500
+
501
+ // Assert
502
+ expect(onResultChanged).toHaveBeenCalledWith(
503
+ Status.success("DATA"),
504
+ );
505
+ });
506
+ });
507
+ });
@@ -0,0 +1,133 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {renderHook} from "@testing-library/react-hooks";
4
+
5
+ import {GqlRouterContext} from "../../util/gql-router-context.js";
6
+ import {useGqlRouterContext} from "../use-gql-router-context.js";
7
+
8
+ describe("#useGqlRouterContext", () => {
9
+ it("should throw if there is no GqlRouterContext", () => {
10
+ // Arrange
11
+
12
+ // Act
13
+ const {
14
+ result: {error: result},
15
+ } = renderHook(() => useGqlRouterContext());
16
+
17
+ // Assert
18
+ expect(result).toMatchInlineSnapshot(
19
+ `[InternalGqlError: No GqlRouter]`,
20
+ );
21
+ });
22
+
23
+ it("should return an equivalent to the GqlRouterContext if no overrides given", () => {
24
+ // Arrange
25
+ const baseContext = {
26
+ fetch: jest.fn(),
27
+ defaultContext: {
28
+ foo: "bar",
29
+ },
30
+ };
31
+ const Wrapper = ({children}: any) => (
32
+ <GqlRouterContext.Provider value={baseContext}>
33
+ {children}
34
+ </GqlRouterContext.Provider>
35
+ );
36
+
37
+ // Act
38
+ const {
39
+ result: {current: result},
40
+ } = renderHook(() => useGqlRouterContext(), {wrapper: Wrapper});
41
+
42
+ // Assert
43
+ expect(result).toStrictEqual(baseContext);
44
+ });
45
+
46
+ it("should return the same object if nothing has changed", () => {
47
+ // Arrange
48
+ const baseContext = {
49
+ fetch: jest.fn(),
50
+ defaultContext: {
51
+ foo: "bar",
52
+ },
53
+ };
54
+ const Wrapper = ({children}: any) => (
55
+ <GqlRouterContext.Provider value={baseContext}>
56
+ {children}
57
+ </GqlRouterContext.Provider>
58
+ );
59
+
60
+ // Act
61
+ const wrapper = renderHook(() => useGqlRouterContext(), {
62
+ wrapper: Wrapper,
63
+ });
64
+ const result1 = wrapper.result.current;
65
+ wrapper.rerender();
66
+ const result2 = wrapper.result.current;
67
+
68
+ // Assert
69
+ expect(result1).toBe(result2);
70
+ });
71
+
72
+ it("should return the same object if the object adds overrides that don't change the merged context", () => {
73
+ // Arrange
74
+ const baseContext = {
75
+ fetch: jest.fn(),
76
+ defaultContext: {
77
+ foo: "bar",
78
+ },
79
+ };
80
+ const Wrapper = ({children}: any) => (
81
+ <GqlRouterContext.Provider value={baseContext}>
82
+ {children}
83
+ </GqlRouterContext.Provider>
84
+ );
85
+
86
+ // Act
87
+ const wrapper = renderHook(
88
+ ({overrides}) => useGqlRouterContext(overrides),
89
+ {
90
+ wrapper: Wrapper,
91
+ initialProps: {},
92
+ },
93
+ );
94
+ const result1 = wrapper.result.current;
95
+ wrapper.rerender({overrides: {foo: "bar"}});
96
+ const result2 = wrapper.result.current;
97
+
98
+ // Assert
99
+ expect(result1).toBe(result2);
100
+ });
101
+
102
+ it("should return an updated object if the object adds overrides that change the merged context", () => {
103
+ // Arrange
104
+ const baseContext = {
105
+ fetch: jest.fn(),
106
+ defaultContext: {
107
+ foo: "bar",
108
+ },
109
+ };
110
+ const Wrapper = ({children}: any) => (
111
+ <GqlRouterContext.Provider value={baseContext}>
112
+ {children}
113
+ </GqlRouterContext.Provider>
114
+ );
115
+
116
+ // Act
117
+ const wrapper = renderHook(
118
+ ({overrides}) => useGqlRouterContext(overrides),
119
+ {
120
+ wrapper: Wrapper,
121
+ initialProps: {
122
+ overrides: {fiz: "baz"},
123
+ },
124
+ },
125
+ );
126
+ const result1 = wrapper.result.current;
127
+ wrapper.rerender({overrides: {}});
128
+ const result2 = wrapper.result.current;
129
+
130
+ // Assert
131
+ expect(result1).not.toBe(result2);
132
+ });
133
+ });
@@ -21,7 +21,7 @@ describe("#useGql", () => {
21
21
 
22
22
  // Assert
23
23
  expect(result).toMatchInlineSnapshot(
24
- `[GqlInternalError: No GqlRouter]`,
24
+ `[InternalGqlError: No GqlRouter]`,
25
25
  );
26
26
  });
27
27
 
@@ -167,35 +167,6 @@ describe("#useGql", () => {
167
167
  );
168
168
  });
169
169
 
170
- it("should resolve to null if the fetch was aborted", async () => {
171
- // Arrange
172
- const abortError = new Error("Aborted");
173
- abortError.name = "AbortError";
174
- const gqlRouterContext = {
175
- fetch: jest.fn().mockRejectedValue(abortError),
176
- defaultContext: {},
177
- };
178
- const {
179
- result: {current: gqlFetch},
180
- } = renderHook(() => useGql(), {
181
- wrapper: ({children}) => (
182
- <GqlRouterContext.Provider value={gqlRouterContext}>
183
- {children}
184
- </GqlRouterContext.Provider>
185
- ),
186
- });
187
- const gqlOp = {
188
- type: "query",
189
- id: "MyQuery",
190
- };
191
-
192
- // Act
193
- const result = await gqlFetch(gqlOp);
194
-
195
- // Assert
196
- expect(result).toBeNull();
197
- });
198
-
199
170
  it("should resolve to the response data", async () => {
200
171
  // Arrange
201
172
  jest.spyOn(