@khanacademy/wonder-blocks-data 3.1.1 → 4.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 +41 -0
- package/dist/es/index.js +375 -335
- package/dist/index.js +527 -461
- package/docs.md +17 -35
- package/package.json +3 -3
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
- package/src/__tests__/generated-snapshot.test.js +56 -122
- package/src/components/__tests__/data.test.js +372 -297
- package/src/components/__tests__/intercept-data.test.js +6 -30
- package/src/components/data.js +153 -21
- package/src/components/data.md +38 -69
- package/src/components/gql-router.js +1 -1
- package/src/components/intercept-context.js +6 -2
- package/src/components/intercept-data.js +40 -51
- package/src/components/intercept-data.md +13 -27
- 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-server-effect.test.js +217 -0
- package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
- package/src/hooks/use-gql.js +39 -31
- package/src/hooks/use-server-effect.js +45 -0
- package/src/hooks/use-shared-cache.js +106 -0
- package/src/index.js +15 -19
- 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/gql-types.js +5 -10
- 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/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
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
3
|
+
import {SsrCache} from "../ssr-cache.js";
|
|
4
|
+
import {ScopedInMemoryCache} from "../scoped-in-memory-cache.js";
|
|
5
|
+
|
|
6
|
+
describe("../ssr-cache.js", () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
/**
|
|
9
|
+
* This is needed or the JSON.stringify mocks need to be
|
|
10
|
+
* mockImplementationOnce. This is because if the snapshots need
|
|
11
|
+
* to update, they write the inline snapshot and that appears to invoke
|
|
12
|
+
* prettier which in turn, calls JSON.stringify. And if that mock
|
|
13
|
+
* throws, then boom. No snapshot update and a big old confusing test
|
|
14
|
+
* failure.
|
|
15
|
+
*/
|
|
16
|
+
jest.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("@Default", () => {
|
|
20
|
+
it("should return an instance of SsrCache", () => {
|
|
21
|
+
// Arrange
|
|
22
|
+
|
|
23
|
+
// Act
|
|
24
|
+
const result = SsrCache.Default;
|
|
25
|
+
|
|
26
|
+
// Assert
|
|
27
|
+
expect(result).toBeInstanceOf(SsrCache);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should return the same instance on each call", () => {
|
|
31
|
+
// Arrange
|
|
32
|
+
|
|
33
|
+
// Act
|
|
34
|
+
const result1 = SsrCache.Default;
|
|
35
|
+
const result2 = SsrCache.Default;
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(result1).toBe(result2);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("#initialize", () => {
|
|
43
|
+
it("should initialize the cache with the given data", () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const cache = new SsrCache();
|
|
46
|
+
|
|
47
|
+
// Act
|
|
48
|
+
cache.initialize({
|
|
49
|
+
MY_KEY: {data: "THE_DATA"},
|
|
50
|
+
});
|
|
51
|
+
const result = cache.getEntry("MY_KEY");
|
|
52
|
+
|
|
53
|
+
// Assert
|
|
54
|
+
expect(result).toStrictEqual({data: "THE_DATA"});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should throw if the cache is already intialized", () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
const internalCache = new ScopedInMemoryCache({
|
|
60
|
+
MY_KEY: {data: "THE_DATA"},
|
|
61
|
+
});
|
|
62
|
+
const cache = new SsrCache(internalCache);
|
|
63
|
+
|
|
64
|
+
// Act
|
|
65
|
+
const underTest = () =>
|
|
66
|
+
cache.initialize({
|
|
67
|
+
MY_OTHER_KEY: {data: "MORE_DATA"},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
72
|
+
`"Cannot initialize data response cache more than once"`,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should deep clone the cached data", () => {
|
|
77
|
+
// Arrange
|
|
78
|
+
const cache = new SsrCache();
|
|
79
|
+
const sourceData = {
|
|
80
|
+
MY_KEY: {data: "THE_DATA"},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Act
|
|
84
|
+
cache.initialize(sourceData);
|
|
85
|
+
// Try to mutate the cache.
|
|
86
|
+
sourceData["MY_KEY"] = {data: "SOME_NEW_DATA"};
|
|
87
|
+
const result = cache.getEntry("MY_KEY");
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect(result).toStrictEqual({data: "THE_DATA"});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("#cacheData", () => {
|
|
95
|
+
describe("when client-side", () => {
|
|
96
|
+
it("should not store the entry in the hydration cache", () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
99
|
+
const cache = new SsrCache(hydrationCache);
|
|
100
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
101
|
+
|
|
102
|
+
// Act
|
|
103
|
+
cache.cacheData("MY_KEY", "data", true);
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should not store the entry in the ssrOnly cache", () => {
|
|
110
|
+
// Arrange
|
|
111
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
112
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
113
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
114
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
115
|
+
|
|
116
|
+
// Act
|
|
117
|
+
cache.cacheData("MY_KEY", "data", false);
|
|
118
|
+
|
|
119
|
+
// Assert
|
|
120
|
+
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("when server-side", () => {
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("when hydrate is true", () => {
|
|
130
|
+
it("should store the entry in the hydration cache", () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
133
|
+
const cache = new SsrCache(hydrationCache);
|
|
134
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
135
|
+
|
|
136
|
+
// Act
|
|
137
|
+
cache.cacheData("MY_KEY", "data", true);
|
|
138
|
+
|
|
139
|
+
// Assert
|
|
140
|
+
expect(hydrationStoreSpy).toHaveBeenCalledWith(
|
|
141
|
+
"default",
|
|
142
|
+
"MY_KEY",
|
|
143
|
+
{
|
|
144
|
+
data: "data",
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should not store the entry in the ssrOnly cache", () => {
|
|
150
|
+
// Arrange
|
|
151
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
152
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
153
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
154
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
155
|
+
|
|
156
|
+
// Act
|
|
157
|
+
cache.cacheData("MY_KEY", "data", true);
|
|
158
|
+
|
|
159
|
+
// Assert
|
|
160
|
+
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("when hydrate is false", () => {
|
|
165
|
+
it("should store the entry in the ssr-only cache", () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
168
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
169
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
170
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
171
|
+
|
|
172
|
+
// Act
|
|
173
|
+
cache.cacheData("MY_KEY", "data", false);
|
|
174
|
+
|
|
175
|
+
// Assert
|
|
176
|
+
expect(ssrOnlyStoreSpy).toHaveBeenCalledWith(
|
|
177
|
+
"default",
|
|
178
|
+
"MY_KEY",
|
|
179
|
+
{
|
|
180
|
+
data: "data",
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should not store the entry in the hydration cache", () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
188
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
189
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
190
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
191
|
+
|
|
192
|
+
// Act
|
|
193
|
+
cache.cacheData("MY_KEY", "data", false);
|
|
194
|
+
|
|
195
|
+
// Assert
|
|
196
|
+
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("#cacheError", () => {
|
|
203
|
+
describe("when client-side", () => {
|
|
204
|
+
it("should not store the entry in the hydration cache", () => {
|
|
205
|
+
// Arrange
|
|
206
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
207
|
+
const cache = new SsrCache(hydrationCache);
|
|
208
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
209
|
+
|
|
210
|
+
// Act
|
|
211
|
+
cache.cacheError("MY_KEY", new Error("Ooops!"), true);
|
|
212
|
+
|
|
213
|
+
// Assert
|
|
214
|
+
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should not store the entry in the ssrOnly cache", () => {
|
|
218
|
+
// Arrange
|
|
219
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
220
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
221
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
222
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
223
|
+
|
|
224
|
+
// Act
|
|
225
|
+
cache.cacheError("MY_KEY", "Ooops!", false);
|
|
226
|
+
|
|
227
|
+
// Assert
|
|
228
|
+
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("when server-side", () => {
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("when hydrate is true", () => {
|
|
238
|
+
it("should store the entry in the hydration cache", () => {
|
|
239
|
+
// Arrange
|
|
240
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
241
|
+
const cache = new SsrCache(hydrationCache);
|
|
242
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
243
|
+
|
|
244
|
+
// Act
|
|
245
|
+
cache.cacheError("MY_KEY", new Error("Ooops!"), true);
|
|
246
|
+
|
|
247
|
+
// Assert
|
|
248
|
+
expect(hydrationStoreSpy).toHaveBeenCalledWith(
|
|
249
|
+
"default",
|
|
250
|
+
"MY_KEY",
|
|
251
|
+
{
|
|
252
|
+
error: "Ooops!",
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should not store the entry in the ssrOnly cache", () => {
|
|
258
|
+
// Arrange
|
|
259
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
260
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
261
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
262
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
263
|
+
|
|
264
|
+
// Act
|
|
265
|
+
cache.cacheError("MY_KEY", new Error("Ooops!"), true);
|
|
266
|
+
|
|
267
|
+
// Assert
|
|
268
|
+
expect(ssrOnlyStoreSpy).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("when hydrate is false", () => {
|
|
273
|
+
it("should store the entry in the ssr-only cache", () => {
|
|
274
|
+
// Arrange
|
|
275
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
276
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
277
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
278
|
+
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
279
|
+
|
|
280
|
+
// Act
|
|
281
|
+
cache.cacheError("MY_KEY", new Error("Ooops!"), false);
|
|
282
|
+
|
|
283
|
+
// Assert
|
|
284
|
+
expect(ssrOnlyStoreSpy).toHaveBeenCalledWith(
|
|
285
|
+
"default",
|
|
286
|
+
"MY_KEY",
|
|
287
|
+
{
|
|
288
|
+
error: "Ooops!",
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should not store the entry in the hydration cache", () => {
|
|
294
|
+
// Arrange
|
|
295
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
296
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
297
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
298
|
+
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
299
|
+
|
|
300
|
+
// Act
|
|
301
|
+
cache.cacheError("MY_KEY", new Error("Ooops!"), false);
|
|
302
|
+
|
|
303
|
+
// Assert
|
|
304
|
+
expect(hydrationStoreSpy).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("#getEntry", () => {
|
|
311
|
+
describe("when client-side", () => {
|
|
312
|
+
beforeEach(() => {
|
|
313
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should return null if not in the hydration cache", () => {
|
|
317
|
+
// Arrange
|
|
318
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
319
|
+
jest.spyOn(hydrationCache, "get").mockReturnValue(null);
|
|
320
|
+
const cache = new SsrCache(hydrationCache);
|
|
321
|
+
|
|
322
|
+
// Act
|
|
323
|
+
const result = cache.getEntry("MY_KEY");
|
|
324
|
+
|
|
325
|
+
// Assert
|
|
326
|
+
expect(result).toBeNull();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should return the cached entry if in the hydration cache", () => {
|
|
330
|
+
// Arrange
|
|
331
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
332
|
+
jest.spyOn(hydrationCache, "get").mockReturnValue({
|
|
333
|
+
data: "data!",
|
|
334
|
+
});
|
|
335
|
+
const cache = new SsrCache(hydrationCache);
|
|
336
|
+
|
|
337
|
+
// Act
|
|
338
|
+
const result = cache.getEntry("MY_KEY");
|
|
339
|
+
|
|
340
|
+
// Assert
|
|
341
|
+
expect(result).toStrictEqual({data: "data!"});
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe("when server-side", () => {
|
|
346
|
+
beforeEach(() => {
|
|
347
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should return null in any cache", () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
353
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
354
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
355
|
+
|
|
356
|
+
// Act
|
|
357
|
+
const result = cache.getEntry("MY_KEY");
|
|
358
|
+
|
|
359
|
+
// Assert
|
|
360
|
+
expect(result).toBeNull();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should return the cached entry if in the hydration cache", () => {
|
|
364
|
+
// Arrange
|
|
365
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
366
|
+
jest.spyOn(hydrationCache, "get").mockReturnValue({
|
|
367
|
+
data: "data!",
|
|
368
|
+
});
|
|
369
|
+
const cache = new SsrCache(hydrationCache);
|
|
370
|
+
|
|
371
|
+
// Act
|
|
372
|
+
const result = cache.getEntry("MY_KEY");
|
|
373
|
+
|
|
374
|
+
// Assert
|
|
375
|
+
expect(result).toStrictEqual({data: "data!"});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("should return the cached entry if in the ssr-only cache", () => {
|
|
379
|
+
// Arrange
|
|
380
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
381
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
382
|
+
jest.spyOn(ssrOnlyCache, "get").mockReturnValue({
|
|
383
|
+
data: "data!",
|
|
384
|
+
});
|
|
385
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
386
|
+
|
|
387
|
+
// Act
|
|
388
|
+
const result = cache.getEntry("MY_KEY");
|
|
389
|
+
|
|
390
|
+
// Assert
|
|
391
|
+
expect(result).toStrictEqual({data: "data!"});
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("#remove", () => {
|
|
397
|
+
it("should return false if nothing was removed", () => {
|
|
398
|
+
// Arrange
|
|
399
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
400
|
+
const ssrOnlycache = new ScopedInMemoryCache();
|
|
401
|
+
jest.spyOn(hydrationCache, "purge").mockReturnValue(false);
|
|
402
|
+
jest.spyOn(ssrOnlycache, "purge").mockReturnValue(false);
|
|
403
|
+
const cache = new SsrCache(hydrationCache, ssrOnlycache);
|
|
404
|
+
|
|
405
|
+
// Act
|
|
406
|
+
const result = cache.remove("A");
|
|
407
|
+
|
|
408
|
+
// Assert
|
|
409
|
+
expect(result).toBeFalsy();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("should return true if something was removed from hydration cache", () => {
|
|
413
|
+
// Arrange
|
|
414
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
415
|
+
jest.spyOn(hydrationCache, "purge").mockReturnValue(true);
|
|
416
|
+
const cache = new SsrCache(hydrationCache);
|
|
417
|
+
|
|
418
|
+
// Act
|
|
419
|
+
const result = cache.remove("A");
|
|
420
|
+
|
|
421
|
+
// Assert
|
|
422
|
+
expect(result).toBeTruthy();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe("when server-side", () => {
|
|
426
|
+
beforeEach(() => {
|
|
427
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("should return true if something was removed from ssr-only cache", () => {
|
|
431
|
+
// Arrange
|
|
432
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
433
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
434
|
+
jest.spyOn(ssrOnlyCache, "purge").mockReturnValue(true);
|
|
435
|
+
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
436
|
+
|
|
437
|
+
// Act
|
|
438
|
+
const result = cache.remove("A");
|
|
439
|
+
|
|
440
|
+
// Assert
|
|
441
|
+
expect(result).toBeTruthy();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe("#cloneHydratableData", () => {
|
|
447
|
+
it("should clone the hydration cache", () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
450
|
+
const cloneSpy = jest
|
|
451
|
+
.spyOn(hydrationCache, "clone")
|
|
452
|
+
.mockReturnValue({
|
|
453
|
+
default: "CLONE!",
|
|
454
|
+
});
|
|
455
|
+
const cache = new SsrCache(hydrationCache);
|
|
456
|
+
// Let's add to the initialized state to check that everything
|
|
457
|
+
// is cloning as we expect.
|
|
458
|
+
cache.cacheData("KEY1", "DATA", true);
|
|
459
|
+
cache.cacheError("KEY2", new Error("OH NO!"), true);
|
|
460
|
+
|
|
461
|
+
// Act
|
|
462
|
+
const result = cache.cloneHydratableData();
|
|
463
|
+
|
|
464
|
+
// Assert
|
|
465
|
+
expect(cloneSpy).toHaveBeenCalled();
|
|
466
|
+
expect(result).toBe("CLONE!");
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe("#removeAll", () => {
|
|
471
|
+
describe("when client-side", () => {
|
|
472
|
+
beforeEach(() => {
|
|
473
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(false);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("should remove all entries from the hydration cache when client-side without predicate", () => {
|
|
477
|
+
// Arrange
|
|
478
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
479
|
+
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
480
|
+
const cache = new SsrCache(hydrationCache);
|
|
481
|
+
|
|
482
|
+
// Act
|
|
483
|
+
cache.removeAll();
|
|
484
|
+
|
|
485
|
+
// Assert
|
|
486
|
+
expect(purgeAllSpy).toHaveBeenCalledWith(undefined);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("should pass a predicate to hydration cache purge if a predicate is passed", () => {
|
|
490
|
+
// Arrange
|
|
491
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
492
|
+
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
493
|
+
const cache = new SsrCache(
|
|
494
|
+
hydrationCache,
|
|
495
|
+
new ScopedInMemoryCache(),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Act
|
|
499
|
+
cache.removeAll(() => true);
|
|
500
|
+
|
|
501
|
+
// Assert
|
|
502
|
+
expect(purgeAllSpy).toHaveBeenCalledWith(expect.any(Function));
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("should pass a predicate to the hydration cache that calls the predicate it was given", () => {
|
|
506
|
+
// Arrange
|
|
507
|
+
const hydrationCache = new ScopedInMemoryCache({
|
|
508
|
+
default: {
|
|
509
|
+
KEY1: {
|
|
510
|
+
data: "DATA",
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
const cache = new SsrCache(
|
|
515
|
+
hydrationCache,
|
|
516
|
+
new ScopedInMemoryCache(),
|
|
517
|
+
);
|
|
518
|
+
const predicate = jest.fn().mockReturnValue(false);
|
|
519
|
+
|
|
520
|
+
// Act
|
|
521
|
+
cache.removeAll(predicate);
|
|
522
|
+
|
|
523
|
+
// Assert
|
|
524
|
+
expect(predicate).toHaveBeenCalledWith("KEY1", {data: "DATA"});
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe("when server-side", () => {
|
|
529
|
+
beforeEach(() => {
|
|
530
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("should remove all entries from hydration cache when server-side without predicate", () => {
|
|
534
|
+
// Arrange
|
|
535
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
536
|
+
const hydrationPurgeAllSpy = jest.spyOn(
|
|
537
|
+
hydrationCache,
|
|
538
|
+
"purgeAll",
|
|
539
|
+
);
|
|
540
|
+
const cache = new SsrCache(
|
|
541
|
+
hydrationCache,
|
|
542
|
+
new ScopedInMemoryCache(),
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
// Act
|
|
546
|
+
cache.removeAll();
|
|
547
|
+
|
|
548
|
+
// Assert
|
|
549
|
+
expect(hydrationPurgeAllSpy).toHaveBeenCalledWith(undefined);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should remove all entries from ssr cache when server-side without predicate", () => {
|
|
553
|
+
// Arrange
|
|
554
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
555
|
+
const ssrPurgeAllSpy = jest.spyOn(ssrOnlyCache, "purgeAll");
|
|
556
|
+
const cache = new SsrCache(
|
|
557
|
+
new ScopedInMemoryCache(),
|
|
558
|
+
ssrOnlyCache,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
// Act
|
|
562
|
+
cache.removeAll();
|
|
563
|
+
|
|
564
|
+
// Assert
|
|
565
|
+
expect(ssrPurgeAllSpy).toHaveBeenCalledWith(undefined);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it("should pass a predicate to hydration cache purge if a predicate is passed", () => {
|
|
569
|
+
// Arrange
|
|
570
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
571
|
+
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
572
|
+
const cache = new SsrCache(
|
|
573
|
+
hydrationCache,
|
|
574
|
+
new ScopedInMemoryCache(),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// Act
|
|
578
|
+
cache.removeAll(() => true);
|
|
579
|
+
|
|
580
|
+
// Assert
|
|
581
|
+
expect(purgeAllSpy).toHaveBeenCalledWith(expect.any(Function));
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("should pass a predicate to srr cache purge if a predicate is passed", () => {
|
|
585
|
+
// Arrange
|
|
586
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
587
|
+
const purgeAllSpy = jest.spyOn(ssrOnlyCache, "purgeAll");
|
|
588
|
+
const cache = new SsrCache(
|
|
589
|
+
new ScopedInMemoryCache(),
|
|
590
|
+
ssrOnlyCache,
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// Act
|
|
594
|
+
cache.removeAll(() => true);
|
|
595
|
+
|
|
596
|
+
// Assert
|
|
597
|
+
expect(purgeAllSpy).toHaveBeenCalledWith(expect.any(Function));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("should pass a predicate to the hydration cache that calls the predicate it was given", () => {
|
|
601
|
+
// Arrange
|
|
602
|
+
const hydrationCache = new ScopedInMemoryCache();
|
|
603
|
+
const cache = new SsrCache(
|
|
604
|
+
hydrationCache,
|
|
605
|
+
new ScopedInMemoryCache(),
|
|
606
|
+
);
|
|
607
|
+
cache.cacheData("KEY1", "DATA", true);
|
|
608
|
+
const predicate = jest.fn().mockReturnValue(false);
|
|
609
|
+
|
|
610
|
+
// Act
|
|
611
|
+
cache.removeAll(predicate);
|
|
612
|
+
|
|
613
|
+
// Assert
|
|
614
|
+
expect(predicate).toHaveBeenCalledWith("KEY1", {data: "DATA"});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should pass a predicate to the ssr cache that calls the predicate it was given", () => {
|
|
618
|
+
// Arrange
|
|
619
|
+
const ssrOnlyCache = new ScopedInMemoryCache();
|
|
620
|
+
const cache = new SsrCache(
|
|
621
|
+
new ScopedInMemoryCache(),
|
|
622
|
+
ssrOnlyCache,
|
|
623
|
+
);
|
|
624
|
+
cache.cacheData(
|
|
625
|
+
"KEY1",
|
|
626
|
+
"DATA",
|
|
627
|
+
false /*false so that the data goes into the ssr only cache*/,
|
|
628
|
+
);
|
|
629
|
+
const predicate = jest.fn().mockReturnValue(false);
|
|
630
|
+
|
|
631
|
+
// Act
|
|
632
|
+
cache.removeAll(predicate);
|
|
633
|
+
|
|
634
|
+
// Assert
|
|
635
|
+
expect(predicate).toHaveBeenCalledWith("KEY1", {data: "DATA"});
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
});
|
package/src/util/gql-types.js
CHANGED
|
@@ -8,7 +8,6 @@ export type GqlOperationType = "mutation" | "query";
|
|
|
8
8
|
* A GraphQL operation.
|
|
9
9
|
*/
|
|
10
10
|
export type GqlOperation<
|
|
11
|
-
TType: GqlOperationType,
|
|
12
11
|
// TData is not used to define a field on this type, but it is used
|
|
13
12
|
// to ensure that calls using this operation will properly return the
|
|
14
13
|
// correct data type.
|
|
@@ -20,13 +19,14 @@ export type GqlOperation<
|
|
|
20
19
|
// eslint-disable-next-line no-unused-vars
|
|
21
20
|
TVariables: {...} = Empty,
|
|
22
21
|
> = {
|
|
23
|
-
type:
|
|
22
|
+
type: GqlOperationType,
|
|
24
23
|
id: string,
|
|
25
24
|
// We allow other things here to be passed along to the fetch function.
|
|
26
25
|
// For example, we might want to pass the full query/mutation definition
|
|
27
26
|
// as a string here to allow that to be sent to an Apollo server that
|
|
28
27
|
// expects it. This is a courtesy to calling code; these additional
|
|
29
28
|
// values are ignored by WB Data, and passed through as-is.
|
|
29
|
+
[key: string]: mixed,
|
|
30
30
|
...
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -37,13 +37,8 @@ export type GqlContext = {|
|
|
|
37
37
|
/**
|
|
38
38
|
* Functions that make fetches of GQL operations.
|
|
39
39
|
*/
|
|
40
|
-
export type GqlFetchFn<
|
|
41
|
-
|
|
42
|
-
TData,
|
|
43
|
-
TVariables: {...},
|
|
44
|
-
TContext: GqlContext,
|
|
45
|
-
> = (
|
|
46
|
-
operation: GqlOperation<TType, TData, TVariables>,
|
|
40
|
+
export type GqlFetchFn<TData, TVariables: {...}, TContext: GqlContext> = (
|
|
41
|
+
operation: GqlOperation<TData, TVariables>,
|
|
47
42
|
variables: ?TVariables,
|
|
48
43
|
context: TContext,
|
|
49
44
|
) => Promise<Response>;
|
|
@@ -52,7 +47,7 @@ export type GqlFetchFn<
|
|
|
52
47
|
* The configuration stored in the GqlRouterContext context.
|
|
53
48
|
*/
|
|
54
49
|
export type GqlRouterConfiguration<TContext: GqlContext> = {|
|
|
55
|
-
fetch: GqlFetchFn<any, any, any
|
|
50
|
+
fetch: GqlFetchFn<any, any, any>,
|
|
56
51
|
defaultContext: TContext,
|
|
57
52
|
|};
|
|
58
53
|
|