@khanacademy/wonder-blocks-data 3.1.3 → 5.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.
- package/CHANGELOG.md +41 -0
- package/dist/es/index.js +408 -349
- package/dist/index.js +599 -494
- package/docs.md +17 -35
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
- package/src/__tests__/generated-snapshot.test.js +60 -126
- package/src/components/__tests__/data.test.js +373 -313
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +139 -21
- package/src/components/data.md +38 -69
- package/src/components/intercept-context.js +6 -3
- package/src/components/intercept-requests.js +69 -0
- package/src/components/intercept-requests.md +54 -0
- package/src/components/track-data.md +9 -23
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +217 -0
- package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
- package/src/hooks/use-gql.js +36 -23
- package/src/hooks/use-request-interception.js +54 -0
- package/src/hooks/use-server-effect.js +45 -0
- package/src/hooks/use-shared-cache.js +106 -0
- package/src/index.js +18 -20
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/request-fulfillment.test.js +42 -85
- package/src/util/__tests__/request-tracking.test.js +72 -191
- package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
- package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
- package/src/util/__tests__/ssr-cache.test.js +639 -0
- package/src/util/request-fulfillment.js +36 -44
- package/src/util/request-tracking.js +62 -75
- package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
- package/src/util/scoped-in-memory-cache.js +149 -0
- package/src/util/ssr-cache.js +206 -0
- package/src/util/types.js +43 -108
- package/src/components/__tests__/intercept-data.test.js +0 -87
- package/src/components/intercept-data.js +0 -77
- package/src/components/intercept-data.md +0 -65
- package/src/hooks/__tests__/use-data.test.js +0 -826
- package/src/hooks/use-data.js +0 -143
- package/src/util/__tests__/memory-cache.test.js +0 -446
- package/src/util/__tests__/request-handler.test.js +0 -121
- package/src/util/__tests__/response-cache.test.js +0 -879
- package/src/util/memory-cache.js +0 -187
- package/src/util/request-handler.js +0 -42
- package/src/util/request-handler.md +0 -51
- package/src/util/response-cache.js +0 -213
|
@@ -1,879 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
3
|
-
import {ResponseCache} from "../response-cache.js";
|
|
4
|
-
import MemoryCache from "../memory-cache.js";
|
|
5
|
-
|
|
6
|
-
import type {IRequestHandler} from "../types.js";
|
|
7
|
-
|
|
8
|
-
describe("../response-cache.js", () => {
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
/**
|
|
11
|
-
* This is needed or the JSON.stringify mocks need to be
|
|
12
|
-
* mockImplementationOnce. This is because if the snapshots need
|
|
13
|
-
* to update, they write the inline snapshot and that appears to invoke
|
|
14
|
-
* prettier which in turn, calls JSON.stringify. And if that mock
|
|
15
|
-
* throws, then boom. No snapshot update and a big old confusing test
|
|
16
|
-
* failure.
|
|
17
|
-
*/
|
|
18
|
-
jest.restoreAllMocks();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe("@Default", () => {
|
|
22
|
-
it("should return an instance of ResponseCache", () => {
|
|
23
|
-
// Arrange
|
|
24
|
-
|
|
25
|
-
// Act
|
|
26
|
-
const result = ResponseCache.Default;
|
|
27
|
-
|
|
28
|
-
// Assert
|
|
29
|
-
expect(result).toBeInstanceOf(ResponseCache);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should return the same instance on each call", () => {
|
|
33
|
-
// Arrange
|
|
34
|
-
|
|
35
|
-
// Act
|
|
36
|
-
const result1 = ResponseCache.Default;
|
|
37
|
-
const result2 = ResponseCache.Default;
|
|
38
|
-
|
|
39
|
-
// Assert
|
|
40
|
-
expect(result1).toBe(result2);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("#initialize", () => {
|
|
45
|
-
it("should initialize the cache with the given data", () => {
|
|
46
|
-
// Arrange
|
|
47
|
-
const cache = new ResponseCache();
|
|
48
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
49
|
-
getKey: () => "MY_KEY",
|
|
50
|
-
type: "MY_HANDLER",
|
|
51
|
-
fulfillRequest: jest.fn(),
|
|
52
|
-
hydrate: true,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Act
|
|
56
|
-
cache.initialize({
|
|
57
|
-
MY_HANDLER: {
|
|
58
|
-
MY_KEY: {data: "THE_DATA"},
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
62
|
-
|
|
63
|
-
// Assert
|
|
64
|
-
expect(result).toStrictEqual({data: "THE_DATA"});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("should throw if the cache is already intialized", () => {
|
|
68
|
-
// Arrange
|
|
69
|
-
const internalCache = new MemoryCache({
|
|
70
|
-
MY_HANDLER: {
|
|
71
|
-
MY_KEY: {data: "THE_DATA"},
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
const cache = new ResponseCache(internalCache);
|
|
75
|
-
|
|
76
|
-
// Act
|
|
77
|
-
const underTest = () =>
|
|
78
|
-
cache.initialize({
|
|
79
|
-
MY_HANDLER: {
|
|
80
|
-
MY_OTHER_KEY: {data: "MORE_DATA"},
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// Assert
|
|
85
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
86
|
-
`"Cannot initialize data response cache more than once"`,
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("should throw if the data is invalid", () => {
|
|
91
|
-
// Arrange
|
|
92
|
-
jest.spyOn(JSON, "stringify").mockImplementation(() => {
|
|
93
|
-
throw new Error("BANG!");
|
|
94
|
-
});
|
|
95
|
-
const internalCache = new MemoryCache();
|
|
96
|
-
const cache = new ResponseCache(internalCache);
|
|
97
|
-
|
|
98
|
-
// Act
|
|
99
|
-
const underTest = () => cache.initialize({});
|
|
100
|
-
|
|
101
|
-
// Assert
|
|
102
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
103
|
-
`"An error occurred trying to initialize the data response cache: Error: An error occurred trying to initialize from a response cache snapshot: Error: BANG!"`,
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("should deep clone the cached data", () => {
|
|
108
|
-
// Arrange
|
|
109
|
-
const cache = new ResponseCache();
|
|
110
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
111
|
-
getKey: () => "MY_KEY",
|
|
112
|
-
type: "MY_HANDLER",
|
|
113
|
-
fulfillRequest: jest.fn(),
|
|
114
|
-
hydrate: true,
|
|
115
|
-
};
|
|
116
|
-
const sourceData = {
|
|
117
|
-
MY_HANDLER: {
|
|
118
|
-
MY_KEY: {data: "THE_DATA"},
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Act
|
|
123
|
-
cache.initialize(sourceData);
|
|
124
|
-
// Try to mutate the cache.
|
|
125
|
-
sourceData["MY_HANDLER"]["MY_KEY"] = {data: "SOME_NEW_DATA"};
|
|
126
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
127
|
-
|
|
128
|
-
// Assert
|
|
129
|
-
expect(result).toStrictEqual({data: "THE_DATA"});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe("#cacheData", () => {
|
|
134
|
-
describe("when client-side", () => {
|
|
135
|
-
it("should not store the entry in the hydration cache", () => {
|
|
136
|
-
// Arrange
|
|
137
|
-
const hydrationCache = new MemoryCache();
|
|
138
|
-
const cache = new ResponseCache(hydrationCache);
|
|
139
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
140
|
-
getKey: () => "MY_KEY",
|
|
141
|
-
type: "MY_HANDLER",
|
|
142
|
-
fulfillRequest: jest.fn(),
|
|
143
|
-
hydrate: true,
|
|
144
|
-
};
|
|
145
|
-
const hydrationStoreSpy = jest.spyOn(hydrationCache, "store");
|
|
146
|
-
|
|
147
|
-
// Act
|
|
148
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
149
|
-
|
|
150
|
-
// Assert
|
|
151
|
-
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("should not store the entry in the ssrOnly cache", () => {
|
|
155
|
-
// Arrange
|
|
156
|
-
const hydrationCache = new MemoryCache();
|
|
157
|
-
const ssrOnlyCache = new MemoryCache();
|
|
158
|
-
const cache = new ResponseCache(hydrationCache, ssrOnlyCache);
|
|
159
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
160
|
-
getKey: () => "MY_KEY",
|
|
161
|
-
type: "MY_HANDLER",
|
|
162
|
-
fulfillRequest: jest.fn(),
|
|
163
|
-
hydrate: true,
|
|
164
|
-
};
|
|
165
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
166
|
-
|
|
167
|
-
// Act
|
|
168
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
169
|
-
|
|
170
|
-
// Assert
|
|
171
|
-
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe("when server-side", () => {
|
|
176
|
-
beforeEach(() => {
|
|
177
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe("when the handler wants hydration", () => {
|
|
181
|
-
it("should store the entry in the hydration cache", () => {
|
|
182
|
-
// Arrange
|
|
183
|
-
const hydrationCache = new MemoryCache();
|
|
184
|
-
const cache = new ResponseCache(hydrationCache);
|
|
185
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
186
|
-
getKey: () => "MY_KEY",
|
|
187
|
-
type: "MY_HANDLER",
|
|
188
|
-
fulfillRequest: jest.fn(),
|
|
189
|
-
hydrate: true,
|
|
190
|
-
};
|
|
191
|
-
const hydrationStoreSpy = jest.spyOn(
|
|
192
|
-
hydrationCache,
|
|
193
|
-
"store",
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Act
|
|
197
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
198
|
-
|
|
199
|
-
// Assert
|
|
200
|
-
expect(hydrationStoreSpy).toHaveBeenCalledWith(
|
|
201
|
-
fakeHandler,
|
|
202
|
-
"options",
|
|
203
|
-
{data: "data"},
|
|
204
|
-
);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("should not store the entry in the ssrOnly cache", () => {
|
|
208
|
-
// Arrange
|
|
209
|
-
const hydrationCache = new MemoryCache();
|
|
210
|
-
const ssrOnlyCache = new MemoryCache();
|
|
211
|
-
const cache = new ResponseCache(
|
|
212
|
-
hydrationCache,
|
|
213
|
-
ssrOnlyCache,
|
|
214
|
-
);
|
|
215
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
216
|
-
getKey: () => "MY_KEY",
|
|
217
|
-
type: "MY_HANDLER",
|
|
218
|
-
fulfillRequest: jest.fn(),
|
|
219
|
-
hydrate: true,
|
|
220
|
-
};
|
|
221
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
222
|
-
|
|
223
|
-
// Act
|
|
224
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
225
|
-
|
|
226
|
-
// Assert
|
|
227
|
-
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
describe("when the handler does not hydration", () => {
|
|
232
|
-
it("should store the entry in the ssr-only cache", () => {
|
|
233
|
-
// Arrange
|
|
234
|
-
const hydrationCache = new MemoryCache();
|
|
235
|
-
const ssrOnlyCache = new MemoryCache();
|
|
236
|
-
const cache = new ResponseCache(
|
|
237
|
-
hydrationCache,
|
|
238
|
-
ssrOnlyCache,
|
|
239
|
-
);
|
|
240
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
241
|
-
getKey: () => "MY_KEY",
|
|
242
|
-
type: "MY_HANDLER",
|
|
243
|
-
fulfillRequest: jest.fn(),
|
|
244
|
-
hydrate: false,
|
|
245
|
-
};
|
|
246
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
247
|
-
|
|
248
|
-
// Act
|
|
249
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
250
|
-
|
|
251
|
-
// Assert
|
|
252
|
-
expect(ssrOnlyStoreSpy).toHaveBeenCalledWith(
|
|
253
|
-
fakeHandler,
|
|
254
|
-
"options",
|
|
255
|
-
{
|
|
256
|
-
data: "data",
|
|
257
|
-
},
|
|
258
|
-
);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("should not store the entry in the hydration cache", () => {
|
|
262
|
-
// Arrange
|
|
263
|
-
const hydrationCache = new MemoryCache();
|
|
264
|
-
const ssrOnlyCache = new MemoryCache();
|
|
265
|
-
const cache = new ResponseCache(
|
|
266
|
-
hydrationCache,
|
|
267
|
-
ssrOnlyCache,
|
|
268
|
-
);
|
|
269
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
270
|
-
getKey: () => "MY_KEY",
|
|
271
|
-
type: "MY_HANDLER",
|
|
272
|
-
fulfillRequest: jest.fn(),
|
|
273
|
-
hydrate: false,
|
|
274
|
-
};
|
|
275
|
-
const hydrationStoreSpy = jest.spyOn(
|
|
276
|
-
hydrationCache,
|
|
277
|
-
"store",
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
// Act
|
|
281
|
-
cache.cacheData(fakeHandler, "options", "data");
|
|
282
|
-
|
|
283
|
-
// Assert
|
|
284
|
-
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
describe("#cacheError", () => {
|
|
291
|
-
describe("when client-side", () => {
|
|
292
|
-
it("should not store the entry in the hydration cache", () => {
|
|
293
|
-
// Arrange
|
|
294
|
-
const hydrationCache = new MemoryCache();
|
|
295
|
-
const cache = new ResponseCache(hydrationCache);
|
|
296
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
297
|
-
getKey: () => "MY_KEY",
|
|
298
|
-
type: "MY_HANDLER",
|
|
299
|
-
fulfillRequest: jest.fn(),
|
|
300
|
-
hydrate: true,
|
|
301
|
-
};
|
|
302
|
-
const hydrationStoreSpy = jest.spyOn(hydrationCache, "store");
|
|
303
|
-
|
|
304
|
-
// Act
|
|
305
|
-
cache.cacheError(fakeHandler, "options", new Error("Ooops!"));
|
|
306
|
-
|
|
307
|
-
// Assert
|
|
308
|
-
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("should not store the entry in the ssrOnly cache", () => {
|
|
312
|
-
// Arrange
|
|
313
|
-
const hydrationCache = new MemoryCache();
|
|
314
|
-
const ssrOnlyCache = new MemoryCache();
|
|
315
|
-
const cache = new ResponseCache(hydrationCache, ssrOnlyCache);
|
|
316
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
317
|
-
getKey: () => "MY_KEY",
|
|
318
|
-
type: "MY_HANDLER",
|
|
319
|
-
fulfillRequest: jest.fn(),
|
|
320
|
-
hydrate: true,
|
|
321
|
-
};
|
|
322
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
323
|
-
|
|
324
|
-
// Act
|
|
325
|
-
cache.cacheError(fakeHandler, "options", "Ooops!");
|
|
326
|
-
|
|
327
|
-
// Assert
|
|
328
|
-
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe("when server-side", () => {
|
|
333
|
-
beforeEach(() => {
|
|
334
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe("when the handler wants hydration", () => {
|
|
338
|
-
it("should store the entry in the hydration cache", () => {
|
|
339
|
-
// Arrange
|
|
340
|
-
const hydrationCache = new MemoryCache();
|
|
341
|
-
const cache = new ResponseCache(hydrationCache);
|
|
342
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
343
|
-
getKey: () => "MY_KEY",
|
|
344
|
-
type: "MY_HANDLER",
|
|
345
|
-
fulfillRequest: jest.fn(),
|
|
346
|
-
hydrate: true,
|
|
347
|
-
};
|
|
348
|
-
const hydrationStoreSpy = jest.spyOn(
|
|
349
|
-
hydrationCache,
|
|
350
|
-
"store",
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
// Act
|
|
354
|
-
cache.cacheError(
|
|
355
|
-
fakeHandler,
|
|
356
|
-
"options",
|
|
357
|
-
new Error("Ooops!"),
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
// Assert
|
|
361
|
-
expect(hydrationStoreSpy).toHaveBeenCalledWith(
|
|
362
|
-
fakeHandler,
|
|
363
|
-
"options",
|
|
364
|
-
{error: "Ooops!"},
|
|
365
|
-
);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it("should not store the entry in the ssrOnly cache", () => {
|
|
369
|
-
// Arrange
|
|
370
|
-
const hydrationCache = new MemoryCache();
|
|
371
|
-
const ssrOnlyCache = new MemoryCache();
|
|
372
|
-
const cache = new ResponseCache(
|
|
373
|
-
hydrationCache,
|
|
374
|
-
ssrOnlyCache,
|
|
375
|
-
);
|
|
376
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
377
|
-
getKey: () => "MY_KEY",
|
|
378
|
-
type: "MY_HANDLER",
|
|
379
|
-
fulfillRequest: jest.fn(),
|
|
380
|
-
hydrate: true,
|
|
381
|
-
};
|
|
382
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
383
|
-
|
|
384
|
-
// Act
|
|
385
|
-
cache.cacheError(
|
|
386
|
-
fakeHandler,
|
|
387
|
-
"options",
|
|
388
|
-
new Error("Ooops!"),
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
// Assert
|
|
392
|
-
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
describe("when the handler does not hydration", () => {
|
|
397
|
-
it("should store the entry in the ssr-only cache", () => {
|
|
398
|
-
// Arrange
|
|
399
|
-
const hydrationCache = new MemoryCache();
|
|
400
|
-
const ssrOnlyCache = new MemoryCache();
|
|
401
|
-
const cache = new ResponseCache(
|
|
402
|
-
hydrationCache,
|
|
403
|
-
ssrOnlyCache,
|
|
404
|
-
);
|
|
405
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
406
|
-
getKey: () => "MY_KEY",
|
|
407
|
-
type: "MY_HANDLER",
|
|
408
|
-
fulfillRequest: jest.fn(),
|
|
409
|
-
hydrate: false,
|
|
410
|
-
};
|
|
411
|
-
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "store");
|
|
412
|
-
|
|
413
|
-
// Act
|
|
414
|
-
cache.cacheError(
|
|
415
|
-
fakeHandler,
|
|
416
|
-
"options",
|
|
417
|
-
new Error("Ooops!"),
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
// Assert
|
|
421
|
-
expect(ssrOnlyStoreSpy).toHaveBeenCalledWith(
|
|
422
|
-
fakeHandler,
|
|
423
|
-
"options",
|
|
424
|
-
{error: "Ooops!"},
|
|
425
|
-
);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it("should not store the entry in the hydration cache", () => {
|
|
429
|
-
// Arrange
|
|
430
|
-
const hydrationCache = new MemoryCache();
|
|
431
|
-
const ssrOnlyCache = new MemoryCache();
|
|
432
|
-
const cache = new ResponseCache(
|
|
433
|
-
hydrationCache,
|
|
434
|
-
ssrOnlyCache,
|
|
435
|
-
);
|
|
436
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
437
|
-
getKey: () => "MY_KEY",
|
|
438
|
-
type: "MY_HANDLER",
|
|
439
|
-
fulfillRequest: jest.fn(),
|
|
440
|
-
hydrate: false,
|
|
441
|
-
};
|
|
442
|
-
const hydrationStoreSpy = jest.spyOn(
|
|
443
|
-
hydrationCache,
|
|
444
|
-
"store",
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
// Act
|
|
448
|
-
cache.cacheError(
|
|
449
|
-
fakeHandler,
|
|
450
|
-
"options",
|
|
451
|
-
new Error("Ooops!"),
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
// Assert
|
|
455
|
-
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
describe("#getEntry", () => {
|
|
462
|
-
describe("when client-side", () => {
|
|
463
|
-
beforeEach(() => {
|
|
464
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(false);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
describe("handler wants hydration", () => {
|
|
468
|
-
it("should return null if not in the hydration cache", () => {
|
|
469
|
-
// Arrange
|
|
470
|
-
const hydrationCache = new MemoryCache();
|
|
471
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue(
|
|
472
|
-
null,
|
|
473
|
-
);
|
|
474
|
-
const cache = new ResponseCache(hydrationCache);
|
|
475
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
476
|
-
getKey: () => "MY_KEY",
|
|
477
|
-
type: "MY_HANDLER",
|
|
478
|
-
fulfillRequest: jest.fn(),
|
|
479
|
-
hydrate: true,
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
// Act
|
|
483
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
484
|
-
|
|
485
|
-
// Assert
|
|
486
|
-
expect(result).toBeNull();
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it("should return the cached entry if in the hydration cache", () => {
|
|
490
|
-
// Arrange
|
|
491
|
-
const hydrationCache = new MemoryCache();
|
|
492
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue({
|
|
493
|
-
data: "data!",
|
|
494
|
-
});
|
|
495
|
-
const cache = new ResponseCache(hydrationCache);
|
|
496
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
497
|
-
getKey: () => "MY_KEY",
|
|
498
|
-
type: "MY_HANDLER",
|
|
499
|
-
fulfillRequest: jest.fn(),
|
|
500
|
-
hydrate: true,
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
// Act
|
|
504
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
505
|
-
|
|
506
|
-
// Assert
|
|
507
|
-
expect(result).toStrictEqual({data: "data!"});
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
describe("handler does not want hyrdation", () => {
|
|
512
|
-
it("should return undefined", () => {
|
|
513
|
-
// Arrange
|
|
514
|
-
const hydrationCache = new MemoryCache();
|
|
515
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue({
|
|
516
|
-
data: "data!",
|
|
517
|
-
});
|
|
518
|
-
const cache = new ResponseCache(hydrationCache);
|
|
519
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
520
|
-
getKey: () => "MY_KEY",
|
|
521
|
-
type: "MY_HANDLER",
|
|
522
|
-
fulfillRequest: jest.fn(),
|
|
523
|
-
hydrate: false,
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
// Act
|
|
527
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
528
|
-
|
|
529
|
-
// Assert
|
|
530
|
-
expect(result).toBeUndefined();
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
describe("when server-side", () => {
|
|
536
|
-
beforeEach(() => {
|
|
537
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
describe("handler wants hydration", () => {
|
|
541
|
-
it("should return null if not in the hydration cache", () => {
|
|
542
|
-
// Arrange
|
|
543
|
-
const hydrationCache = new MemoryCache();
|
|
544
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue(
|
|
545
|
-
null,
|
|
546
|
-
);
|
|
547
|
-
const cache = new ResponseCache(hydrationCache);
|
|
548
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
549
|
-
getKey: () => "MY_KEY",
|
|
550
|
-
type: "MY_HANDLER",
|
|
551
|
-
fulfillRequest: jest.fn(),
|
|
552
|
-
hydrate: true,
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
// Act
|
|
556
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
557
|
-
|
|
558
|
-
// Assert
|
|
559
|
-
expect(result).toBeNull();
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it("should return the cached entry if in the hydration cache", () => {
|
|
563
|
-
// Arrange
|
|
564
|
-
const hydrationCache = new MemoryCache();
|
|
565
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue({
|
|
566
|
-
data: "data!",
|
|
567
|
-
});
|
|
568
|
-
const cache = new ResponseCache(hydrationCache);
|
|
569
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
570
|
-
getKey: () => "MY_KEY",
|
|
571
|
-
type: "MY_HANDLER",
|
|
572
|
-
fulfillRequest: jest.fn(),
|
|
573
|
-
hydrate: true,
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
// Act
|
|
577
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
578
|
-
|
|
579
|
-
// Assert
|
|
580
|
-
expect(result).toStrictEqual({data: "data!"});
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
describe("handler does not want hyrdation", () => {
|
|
585
|
-
it("should return null if not in the ssr-only cache", () => {
|
|
586
|
-
// Arrange
|
|
587
|
-
const hydrationCache = new MemoryCache();
|
|
588
|
-
const ssrOnlyCache = new MemoryCache();
|
|
589
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue(
|
|
590
|
-
"NOT NULL",
|
|
591
|
-
);
|
|
592
|
-
jest.spyOn(ssrOnlyCache, "retrieve").mockReturnValue(null);
|
|
593
|
-
const cache = new ResponseCache(
|
|
594
|
-
hydrationCache,
|
|
595
|
-
ssrOnlyCache,
|
|
596
|
-
);
|
|
597
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
598
|
-
getKey: () => "MY_KEY",
|
|
599
|
-
type: "MY_HANDLER",
|
|
600
|
-
fulfillRequest: jest.fn(),
|
|
601
|
-
hydrate: false,
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
// Act
|
|
605
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
606
|
-
|
|
607
|
-
// Assert
|
|
608
|
-
expect(result).toBeNull();
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
it("should return the cached entry if in the ssr-only cache", () => {
|
|
612
|
-
// Arrange
|
|
613
|
-
const hydrationCache = new MemoryCache();
|
|
614
|
-
const ssrOnlyCache = new MemoryCache();
|
|
615
|
-
jest.spyOn(hydrationCache, "retrieve").mockReturnValue({
|
|
616
|
-
data: "wrong data!",
|
|
617
|
-
});
|
|
618
|
-
jest.spyOn(ssrOnlyCache, "retrieve").mockReturnValue({
|
|
619
|
-
data: "data!",
|
|
620
|
-
});
|
|
621
|
-
const cache = new ResponseCache(
|
|
622
|
-
hydrationCache,
|
|
623
|
-
ssrOnlyCache,
|
|
624
|
-
);
|
|
625
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
626
|
-
getKey: () => "MY_KEY",
|
|
627
|
-
type: "MY_HANDLER",
|
|
628
|
-
fulfillRequest: jest.fn(),
|
|
629
|
-
hydrate: false,
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
// Act
|
|
633
|
-
const result = cache.getEntry(fakeHandler, "options");
|
|
634
|
-
|
|
635
|
-
// Assert
|
|
636
|
-
expect(result).toStrictEqual({data: "data!"});
|
|
637
|
-
});
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
describe("#remove", () => {
|
|
643
|
-
describe("when handler wants hydration", () => {
|
|
644
|
-
it("should return false if nothing was removed from hydration cache", () => {
|
|
645
|
-
// Arrange
|
|
646
|
-
const hydrationCache = new MemoryCache();
|
|
647
|
-
jest.spyOn(hydrationCache, "remove").mockReturnValue(false);
|
|
648
|
-
const cache = new ResponseCache(hydrationCache);
|
|
649
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
650
|
-
getKey: () => "MY_KEY",
|
|
651
|
-
type: "MY_HANDLER",
|
|
652
|
-
fulfillRequest: jest.fn(),
|
|
653
|
-
hydrate: true,
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
// Act
|
|
657
|
-
const result = cache.remove(fakeHandler, "optionsA");
|
|
658
|
-
|
|
659
|
-
// Assert
|
|
660
|
-
expect(result).toBeFalsy();
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it("should return true if something was removed from hydration cache", () => {
|
|
664
|
-
// Arrange
|
|
665
|
-
const hydrationCache = new MemoryCache();
|
|
666
|
-
jest.spyOn(hydrationCache, "remove").mockReturnValue(true);
|
|
667
|
-
const cache = new ResponseCache(hydrationCache);
|
|
668
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
669
|
-
getKey: () => "MY_KEY",
|
|
670
|
-
type: "MY_HANDLER",
|
|
671
|
-
fulfillRequest: jest.fn(),
|
|
672
|
-
hydrate: true,
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
// Act
|
|
676
|
-
const result = cache.remove(fakeHandler, "optionsA");
|
|
677
|
-
|
|
678
|
-
// Assert
|
|
679
|
-
expect(result).toBeTruthy();
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
describe("when handler does not want hydration", () => {
|
|
684
|
-
it("should return false", () => {
|
|
685
|
-
// Arrange
|
|
686
|
-
const hydrationCache = new MemoryCache();
|
|
687
|
-
const ssrOnlyCache = new MemoryCache();
|
|
688
|
-
jest.spyOn(hydrationCache, "remove").mockReturnValue(true);
|
|
689
|
-
jest.spyOn(ssrOnlyCache, "remove").mockReturnValue(true);
|
|
690
|
-
const cache = new ResponseCache(hydrationCache, ssrOnlyCache);
|
|
691
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
692
|
-
getKey: () => "MY_KEY",
|
|
693
|
-
type: "MY_HANDLER",
|
|
694
|
-
fulfillRequest: jest.fn(),
|
|
695
|
-
hydrate: false,
|
|
696
|
-
};
|
|
697
|
-
|
|
698
|
-
// Act
|
|
699
|
-
const result = cache.remove(fakeHandler, "optionsA");
|
|
700
|
-
|
|
701
|
-
// Assert
|
|
702
|
-
expect(result).toBeFalsy();
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
describe("when server-side", () => {
|
|
706
|
-
beforeEach(() => {
|
|
707
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
it("should return false if nothing was removed from ssr-only cache", () => {
|
|
711
|
-
// Arrange
|
|
712
|
-
const hydrationCache = new MemoryCache();
|
|
713
|
-
const ssrOnlyCache = new MemoryCache();
|
|
714
|
-
jest.spyOn(hydrationCache, "remove").mockReturnValue(true);
|
|
715
|
-
jest.spyOn(ssrOnlyCache, "remove").mockReturnValue(false);
|
|
716
|
-
const cache = new ResponseCache(
|
|
717
|
-
hydrationCache,
|
|
718
|
-
ssrOnlyCache,
|
|
719
|
-
);
|
|
720
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
721
|
-
getKey: () => "MY_KEY",
|
|
722
|
-
type: "MY_HANDLER",
|
|
723
|
-
fulfillRequest: jest.fn(),
|
|
724
|
-
hydrate: false,
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// Act
|
|
728
|
-
const result = cache.remove(fakeHandler, "optionsA");
|
|
729
|
-
|
|
730
|
-
// Assert
|
|
731
|
-
expect(result).toBeFalsy();
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
it("should return true if something was removed from ssr-only cache", () => {
|
|
735
|
-
// Arrange
|
|
736
|
-
const hydrationCache = new MemoryCache();
|
|
737
|
-
const ssrOnlyCache = new MemoryCache();
|
|
738
|
-
jest.spyOn(ssrOnlyCache, "remove").mockReturnValue(true);
|
|
739
|
-
const cache = new ResponseCache(
|
|
740
|
-
hydrationCache,
|
|
741
|
-
ssrOnlyCache,
|
|
742
|
-
);
|
|
743
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
744
|
-
getKey: () => "MY_KEY",
|
|
745
|
-
type: "MY_HANDLER",
|
|
746
|
-
fulfillRequest: jest.fn(),
|
|
747
|
-
hydrate: false,
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// Act
|
|
751
|
-
const result = cache.remove(fakeHandler, "optionsA");
|
|
752
|
-
|
|
753
|
-
// Assert
|
|
754
|
-
expect(result).toBeTruthy();
|
|
755
|
-
});
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
describe("#removeAll", () => {
|
|
761
|
-
describe("when handler wants hydration", () => {
|
|
762
|
-
it("should return total number of entries removed from hydration cache", () => {
|
|
763
|
-
// Arrange
|
|
764
|
-
const hydrationCache = new MemoryCache();
|
|
765
|
-
jest.spyOn(hydrationCache, "removeAll").mockReturnValue(1);
|
|
766
|
-
const cache = new ResponseCache(hydrationCache);
|
|
767
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
768
|
-
getKey: () => "MY_KEY",
|
|
769
|
-
type: "MY_HANDLER",
|
|
770
|
-
fulfillRequest: jest.fn(),
|
|
771
|
-
hydrate: true,
|
|
772
|
-
};
|
|
773
|
-
|
|
774
|
-
// Act
|
|
775
|
-
const result = cache.removeAll(fakeHandler);
|
|
776
|
-
|
|
777
|
-
// Assert
|
|
778
|
-
expect(result).toBe(1);
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
describe("when handler does not want hydration", () => {
|
|
783
|
-
it("should return zero", () => {
|
|
784
|
-
// Arrange
|
|
785
|
-
const hydrationCache = new MemoryCache();
|
|
786
|
-
const ssrOnlyCache = new MemoryCache();
|
|
787
|
-
jest.spyOn(hydrationCache, "removeAll").mockReturnValue(42);
|
|
788
|
-
jest.spyOn(ssrOnlyCache, "removeAll").mockReturnValue(42);
|
|
789
|
-
const cache = new ResponseCache(hydrationCache, ssrOnlyCache);
|
|
790
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
791
|
-
getKey: () => "MY_KEY",
|
|
792
|
-
type: "MY_HANDLER",
|
|
793
|
-
fulfillRequest: jest.fn(),
|
|
794
|
-
hydrate: false,
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
// Act
|
|
798
|
-
const result = cache.removeAll(fakeHandler);
|
|
799
|
-
|
|
800
|
-
// Assert
|
|
801
|
-
expect(result).toBe(0);
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
describe("when server-side", () => {
|
|
805
|
-
beforeEach(() => {
|
|
806
|
-
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
it("should return total number of entries removed from ssr-only cache", () => {
|
|
810
|
-
// Arrange
|
|
811
|
-
const hydrationCache = new MemoryCache();
|
|
812
|
-
const ssrOnlyCache = new MemoryCache();
|
|
813
|
-
jest.spyOn(hydrationCache, "removeAll").mockReturnValue(13);
|
|
814
|
-
jest.spyOn(ssrOnlyCache, "removeAll").mockReturnValue(42);
|
|
815
|
-
const cache = new ResponseCache(
|
|
816
|
-
hydrationCache,
|
|
817
|
-
ssrOnlyCache,
|
|
818
|
-
);
|
|
819
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
820
|
-
getKey: () => "MY_KEY",
|
|
821
|
-
type: "MY_HANDLER",
|
|
822
|
-
fulfillRequest: jest.fn(),
|
|
823
|
-
hydrate: false,
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
// Act
|
|
827
|
-
const result = cache.removeAll(fakeHandler);
|
|
828
|
-
|
|
829
|
-
// Assert
|
|
830
|
-
expect(result).toBe(42);
|
|
831
|
-
});
|
|
832
|
-
});
|
|
833
|
-
});
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
describe("#cloneHydratableData", () => {
|
|
837
|
-
it("should clone the hydration cache", () => {
|
|
838
|
-
// Arrange
|
|
839
|
-
const hydrationCache = new MemoryCache();
|
|
840
|
-
const cloneSpy = jest
|
|
841
|
-
.spyOn(hydrationCache, "cloneData")
|
|
842
|
-
.mockReturnValue("CLONE!");
|
|
843
|
-
const cache = new ResponseCache(hydrationCache);
|
|
844
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
845
|
-
getKey: (options) => options,
|
|
846
|
-
type: "MY_HANDLER",
|
|
847
|
-
fulfillRequest: jest.fn(),
|
|
848
|
-
hydrate: true,
|
|
849
|
-
};
|
|
850
|
-
// Let's add to the initialized state to check that everything
|
|
851
|
-
// is cloning as we expect.
|
|
852
|
-
cache.cacheData(fakeHandler, "OPTIONS1", "DATA");
|
|
853
|
-
cache.cacheError(fakeHandler, "OPTIONS2", new Error("OH NO!"));
|
|
854
|
-
|
|
855
|
-
// Act
|
|
856
|
-
const result = cache.cloneHydratableData();
|
|
857
|
-
|
|
858
|
-
// Assert
|
|
859
|
-
expect(cloneSpy).toHaveBeenCalled();
|
|
860
|
-
expect(result).toBe("CLONE!");
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
it("should throw if the cloning fails", () => {
|
|
864
|
-
// Arrange
|
|
865
|
-
const cache = new ResponseCache();
|
|
866
|
-
jest.spyOn(JSON, "stringify").mockImplementation(() => {
|
|
867
|
-
throw new Error("BANG!");
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
// Act
|
|
871
|
-
const underTest = () => cache.cloneHydratableData();
|
|
872
|
-
|
|
873
|
-
// Assert
|
|
874
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
875
|
-
`"An error occurred while trying to clone the cache: Error: BANG!"`,
|
|
876
|
-
);
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
});
|