@khanacademy/wonder-blocks-data 5.0.1 → 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 +19 -0
- package/dist/es/index.js +771 -372
- package/dist/index.js +1191 -550
- 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/{components/intercept-requests.md → __docs__/exports.intercept-requests.stories.mdx} +16 -1
- 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 +0 -24
- package/src/components/__tests__/data.test.js +149 -128
- package/src/components/data.js +22 -112
- package/src/components/intercept-requests.js +1 -1
- 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-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 +20 -23
- package/src/hooks/use-server-effect.js +12 -5
- package/src/hooks/use-shared-cache.js +13 -11
- package/src/index.js +53 -3
- 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
|
@@ -78,7 +78,7 @@ describe("../request-tracking.js", () => {
|
|
|
78
78
|
it("should track each matching request once", async () => {
|
|
79
79
|
// Arrange
|
|
80
80
|
const requestTracker = createRequestTracker();
|
|
81
|
-
const fakeHandler = jest.fn().mockResolvedValue(
|
|
81
|
+
const fakeHandler = jest.fn().mockResolvedValue("DATA");
|
|
82
82
|
|
|
83
83
|
// Act
|
|
84
84
|
requestTracker.trackDataRequest("ID", fakeHandler, true);
|
|
@@ -177,7 +177,7 @@ describe("../request-tracking.js", () => {
|
|
|
177
177
|
// Assert
|
|
178
178
|
expect(result).toStrictEqual({
|
|
179
179
|
ID: {
|
|
180
|
-
error: "
|
|
180
|
+
error: "Request failed",
|
|
181
181
|
},
|
|
182
182
|
});
|
|
183
183
|
});
|
|
@@ -230,7 +230,7 @@ describe("../request-tracking.js", () => {
|
|
|
230
230
|
// Assert
|
|
231
231
|
expect(result).toStrictEqual({
|
|
232
232
|
BAD_REQUEST: {
|
|
233
|
-
error: "
|
|
233
|
+
error: "Request failed",
|
|
234
234
|
},
|
|
235
235
|
BAD_HANDLER: {
|
|
236
236
|
error: "OH NO!",
|
|
@@ -244,14 +244,33 @@ describe("../request-tracking.js", () => {
|
|
|
244
244
|
});
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
it("should
|
|
247
|
+
it("should ignore loading results", async () => {
|
|
248
|
+
// Arrange
|
|
249
|
+
const requestTracker = createRequestTracker();
|
|
250
|
+
jest.spyOn(
|
|
251
|
+
requestTracker._requestFulfillment,
|
|
252
|
+
"fulfill",
|
|
253
|
+
).mockResolvedValue({status: "loading"});
|
|
254
|
+
const fakeValidHandler = () =>
|
|
255
|
+
Promise.reject(new Error("Not called for this test case"));
|
|
256
|
+
requestTracker.trackDataRequest("ID", fakeValidHandler, true);
|
|
257
|
+
|
|
258
|
+
// Act
|
|
259
|
+
const result = await requestTracker.fulfillTrackedRequests();
|
|
260
|
+
|
|
261
|
+
// Assert
|
|
262
|
+
expect(result).toStrictEqual({});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should ignore aborted results", async () => {
|
|
248
266
|
// Arrange
|
|
249
267
|
const requestTracker = createRequestTracker();
|
|
250
268
|
jest.spyOn(
|
|
251
269
|
requestTracker._requestFulfillment,
|
|
252
270
|
"fulfill",
|
|
253
|
-
).
|
|
254
|
-
const fakeValidHandler = () =>
|
|
271
|
+
).mockResolvedValue({status: "aborted"});
|
|
272
|
+
const fakeValidHandler = () =>
|
|
273
|
+
Promise.reject(new Error("Not called for this test case"));
|
|
255
274
|
requestTracker.trackDataRequest("ID", fakeValidHandler, false);
|
|
256
275
|
|
|
257
276
|
// Act
|
|
@@ -285,7 +304,7 @@ describe("../request-tracking.js", () => {
|
|
|
285
304
|
it("should clear the tracked data requests", async () => {
|
|
286
305
|
// Arrange
|
|
287
306
|
const requestTracker = createRequestTracker();
|
|
288
|
-
const fakeHandler = jest.fn().mockResolvedValue(
|
|
307
|
+
const fakeHandler = jest.fn().mockResolvedValue("DATA");
|
|
289
308
|
requestTracker.trackDataRequest("ID", fakeHandler, true);
|
|
290
309
|
|
|
291
310
|
// Act
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {resultFromCachedResponse} from "../result-from-cache-response.js";
|
|
3
3
|
|
|
4
4
|
describe("#resultFromCachedResponse", () => {
|
|
5
|
-
it("should return
|
|
5
|
+
it("should return null cache entry is null", () => {
|
|
6
6
|
// Arrange
|
|
7
7
|
const cacheEntry = null;
|
|
8
8
|
|
|
@@ -10,9 +10,7 @@ describe("#resultFromCachedResponse", () => {
|
|
|
10
10
|
const result = resultFromCachedResponse(cacheEntry);
|
|
11
11
|
|
|
12
12
|
// Assert
|
|
13
|
-
expect(result).
|
|
14
|
-
status: "loading",
|
|
15
|
-
});
|
|
13
|
+
expect(result).toBeNull();
|
|
16
14
|
});
|
|
17
15
|
|
|
18
16
|
it("should return success status if cache entry has data", () => {
|
|
@@ -61,7 +59,23 @@ describe("#resultFromCachedResponse", () => {
|
|
|
61
59
|
// Assert
|
|
62
60
|
expect(result).toStrictEqual({
|
|
63
61
|
status: "error",
|
|
64
|
-
error:
|
|
62
|
+
error: expect.any(Error),
|
|
65
63
|
});
|
|
66
64
|
});
|
|
65
|
+
|
|
66
|
+
it("should hydrate the error into an error object", () => {
|
|
67
|
+
// Arrange
|
|
68
|
+
const cacheEntry: any = {
|
|
69
|
+
data: null,
|
|
70
|
+
error: "ERROR",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Act
|
|
74
|
+
// $FlowIgnore[incompatible-use]
|
|
75
|
+
// $FlowIgnore[prop-missing]
|
|
76
|
+
const {error} = resultFromCachedResponse(cacheEntry);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(error).toMatchInlineSnapshot(`[HydratedDataError: ERROR]`);
|
|
80
|
+
});
|
|
67
81
|
});
|
|
@@ -1,48 +1,7 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import * as WSCore from "@khanacademy/wonder-stuff-core";
|
|
3
2
|
import {ScopedInMemoryCache} from "../scoped-in-memory-cache.js";
|
|
4
3
|
|
|
5
4
|
describe("ScopedInMemoryCache", () => {
|
|
6
|
-
describe("#constructor", () => {
|
|
7
|
-
it("should clone the passed source data", () => {
|
|
8
|
-
// Arrange
|
|
9
|
-
const sourceData = {
|
|
10
|
-
scope: {
|
|
11
|
-
key: "value",
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// Act
|
|
16
|
-
const cache = new ScopedInMemoryCache(sourceData);
|
|
17
|
-
// Try to mutate the cache.
|
|
18
|
-
sourceData["scope"] = {key: "SOME_NEW_DATA"};
|
|
19
|
-
const result = cache.get("scope", "key");
|
|
20
|
-
|
|
21
|
-
// Assert
|
|
22
|
-
expect(result).toStrictEqual("value");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("should throw if the cloning fails", () => {
|
|
26
|
-
// Arrange
|
|
27
|
-
jest.spyOn(WSCore, "clone").mockImplementationOnce(() => {
|
|
28
|
-
throw new Error("BANG!");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Act
|
|
32
|
-
const underTest = () =>
|
|
33
|
-
new ScopedInMemoryCache({
|
|
34
|
-
scope: {
|
|
35
|
-
BAD: "FOOD",
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Assert
|
|
40
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
41
|
-
`"An error occurred trying to initialize from a response cache snapshot: Error: BANG!"`,
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
5
|
describe("#set", () => {
|
|
47
6
|
it.each`
|
|
48
7
|
id
|
|
@@ -175,7 +134,7 @@ describe("ScopedInMemoryCache", () => {
|
|
|
175
134
|
|
|
176
135
|
// Act
|
|
177
136
|
cache.purge("scope1", "key2");
|
|
178
|
-
const result = cache.
|
|
137
|
+
const result = cache._cache;
|
|
179
138
|
|
|
180
139
|
// Assert
|
|
181
140
|
expect(result).toStrictEqual({
|
|
@@ -202,7 +161,7 @@ describe("ScopedInMemoryCache", () => {
|
|
|
202
161
|
|
|
203
162
|
// Act
|
|
204
163
|
cache.purge("scope1", "key2");
|
|
205
|
-
const result = cache.
|
|
164
|
+
const result = cache._cache;
|
|
206
165
|
|
|
207
166
|
// Assert
|
|
208
167
|
expect(result).toStrictEqual({
|
|
@@ -232,7 +191,7 @@ describe("ScopedInMemoryCache", () => {
|
|
|
232
191
|
|
|
233
192
|
// Act
|
|
234
193
|
cache.purgeScope("scope1", (id, value) => value === "a");
|
|
235
|
-
const result = cache.
|
|
194
|
+
const result = cache._cache;
|
|
236
195
|
|
|
237
196
|
// Assert
|
|
238
197
|
expect(result).toStrictEqual({
|
|
@@ -262,7 +221,7 @@ describe("ScopedInMemoryCache", () => {
|
|
|
262
221
|
|
|
263
222
|
// Act
|
|
264
223
|
cache.purgeScope("scope1");
|
|
265
|
-
const result = cache.
|
|
224
|
+
const result = cache._cache;
|
|
266
225
|
|
|
267
226
|
// Assert
|
|
268
227
|
expect(result).toStrictEqual({
|
|
@@ -299,7 +258,7 @@ describe("ScopedInMemoryCache", () => {
|
|
|
299
258
|
|
|
300
259
|
// Act
|
|
301
260
|
cache.purgeAll((scope, id, value) => value === "2");
|
|
302
|
-
const result = cache.
|
|
261
|
+
const result = cache._cache;
|
|
303
262
|
|
|
304
263
|
// Assert
|
|
305
264
|
expect(result).toStrictEqual({
|
|
@@ -316,51 +275,13 @@ describe("ScopedInMemoryCache", () => {
|
|
|
316
275
|
|
|
317
276
|
// Act
|
|
318
277
|
cache.purgeAll();
|
|
319
|
-
const result = cache.
|
|
278
|
+
const result = cache._cache;
|
|
320
279
|
|
|
321
280
|
// Assert
|
|
322
281
|
expect(result).toStrictEqual({});
|
|
323
282
|
});
|
|
324
283
|
});
|
|
325
284
|
|
|
326
|
-
describe("#clone", () => {
|
|
327
|
-
it("should return a copy of the cache data", () => {
|
|
328
|
-
// Arrange
|
|
329
|
-
const data = {
|
|
330
|
-
scope1: {key: "2"},
|
|
331
|
-
scope2: {key: "1"},
|
|
332
|
-
scope3: {key: "2"},
|
|
333
|
-
};
|
|
334
|
-
const cache = new ScopedInMemoryCache(data);
|
|
335
|
-
|
|
336
|
-
// Act
|
|
337
|
-
const result = cache.clone();
|
|
338
|
-
|
|
339
|
-
// Assert
|
|
340
|
-
expect(result).not.toBe(data);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("should throw if there is an error during cloning", () => {
|
|
344
|
-
// Arrange
|
|
345
|
-
const cache = new ScopedInMemoryCache({
|
|
346
|
-
scope1: {key: "2"},
|
|
347
|
-
scope2: {key: "1"},
|
|
348
|
-
scope3: {key: "2"},
|
|
349
|
-
});
|
|
350
|
-
jest.spyOn(WSCore, "clone").mockImplementationOnce(() => {
|
|
351
|
-
throw new Error("BANG!");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// Act
|
|
355
|
-
const act = () => cache.clone();
|
|
356
|
-
|
|
357
|
-
// Assert
|
|
358
|
-
expect(act).toThrowErrorMatchingInlineSnapshot(
|
|
359
|
-
`"An error occurred while trying to clone the cache: Error: BANG!"`,
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
285
|
describe("@inUse", () => {
|
|
365
286
|
it("should return true if the cache contains data", () => {
|
|
366
287
|
// Arrange
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as WSCore from "@khanacademy/wonder-stuff-core";
|
|
3
|
+
import {SerializableInMemoryCache} from "../serializable-in-memory-cache.js";
|
|
4
|
+
|
|
5
|
+
describe("SerializableInMemoryCache", () => {
|
|
6
|
+
describe("#constructor", () => {
|
|
7
|
+
it("should clone the passed source data", () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const sourceData = {
|
|
10
|
+
scope: {
|
|
11
|
+
key: "value",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Act
|
|
16
|
+
const cache = new SerializableInMemoryCache(sourceData);
|
|
17
|
+
// Try to mutate the cache.
|
|
18
|
+
sourceData["scope"] = {key: "SOME_NEW_DATA"};
|
|
19
|
+
const result = cache.get("scope", "key");
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
expect(result).toStrictEqual("value");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should throw if the cloning fails", () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
jest.spyOn(WSCore, "clone").mockImplementationOnce(() => {
|
|
28
|
+
throw new Error("BANG!");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Act
|
|
32
|
+
const underTest = () =>
|
|
33
|
+
new SerializableInMemoryCache({
|
|
34
|
+
scope: {
|
|
35
|
+
BAD: "FOOD",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
41
|
+
`"An error occurred trying to initialize from a response cache snapshot: Error: BANG!"`,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("#set", () => {
|
|
47
|
+
it.each`
|
|
48
|
+
id
|
|
49
|
+
${null}
|
|
50
|
+
${""}
|
|
51
|
+
${5}
|
|
52
|
+
${() => "BOO"}
|
|
53
|
+
`("should throw if the id is $id", ({id}) => {
|
|
54
|
+
// Arrange
|
|
55
|
+
const cache = new SerializableInMemoryCache();
|
|
56
|
+
|
|
57
|
+
// Act
|
|
58
|
+
const underTest = () => cache.set("scope", id, "value");
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it.each`
|
|
65
|
+
scope
|
|
66
|
+
${null}
|
|
67
|
+
${""}
|
|
68
|
+
${5}
|
|
69
|
+
${() => "BOO"}
|
|
70
|
+
`("should throw if the scope is $scope", ({scope}) => {
|
|
71
|
+
// Arrange
|
|
72
|
+
const cache = new SerializableInMemoryCache();
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
const underTest = () => cache.set(scope, "key", "value");
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should throw if the value is a function", () => {
|
|
82
|
+
// Arrange
|
|
83
|
+
const cache = new SerializableInMemoryCache();
|
|
84
|
+
|
|
85
|
+
// Act
|
|
86
|
+
const underTest = () => cache.set("scope", "key", () => "value");
|
|
87
|
+
|
|
88
|
+
// Assert
|
|
89
|
+
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should store the entry in the cache", () => {
|
|
93
|
+
// Arrange
|
|
94
|
+
const cache = new SerializableInMemoryCache();
|
|
95
|
+
|
|
96
|
+
// Act
|
|
97
|
+
cache.set("scope", "key", "data");
|
|
98
|
+
const result = cache.get("scope", "key");
|
|
99
|
+
|
|
100
|
+
// Assert
|
|
101
|
+
expect(result).toStrictEqual("data");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should replace the entry in the cache", () => {
|
|
105
|
+
// Arrange
|
|
106
|
+
const cache = new SerializableInMemoryCache({
|
|
107
|
+
scope: {
|
|
108
|
+
key: "data",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Act
|
|
113
|
+
cache.set("scope", "key", "other_data");
|
|
114
|
+
const result = cache.get("scope", "key");
|
|
115
|
+
|
|
116
|
+
// Assert
|
|
117
|
+
expect(result).toStrictEqual("other_data");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("#retrieve", () => {
|
|
122
|
+
it("should return null if the scope is not cached", () => {
|
|
123
|
+
// Arrange
|
|
124
|
+
const cache = new SerializableInMemoryCache();
|
|
125
|
+
|
|
126
|
+
// Act
|
|
127
|
+
const result = cache.get("scope", "key");
|
|
128
|
+
|
|
129
|
+
// Assert
|
|
130
|
+
expect(result).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should return null if the value is not in the cache scope", () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
const cache = new SerializableInMemoryCache({
|
|
136
|
+
scope: {
|
|
137
|
+
key1: "data",
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Act
|
|
142
|
+
const result = cache.get("scope", "key2");
|
|
143
|
+
|
|
144
|
+
// Assert
|
|
145
|
+
expect(result).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should return the entry if it exists", () => {
|
|
149
|
+
// Arrange
|
|
150
|
+
const cache = new SerializableInMemoryCache({
|
|
151
|
+
scope: {key: "value"},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Act
|
|
155
|
+
const result = cache.get("scope", "key");
|
|
156
|
+
|
|
157
|
+
// Assert
|
|
158
|
+
expect(result).toStrictEqual("value");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("#purge", () => {
|
|
163
|
+
it("should remove the entry", () => {
|
|
164
|
+
// Arrange
|
|
165
|
+
const cache = new SerializableInMemoryCache({
|
|
166
|
+
scope1: {
|
|
167
|
+
key1: "data1",
|
|
168
|
+
key2: "data2",
|
|
169
|
+
},
|
|
170
|
+
scope2: {
|
|
171
|
+
key1: "data1",
|
|
172
|
+
key2: "data2",
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
cache.purge("scope1", "key2");
|
|
178
|
+
const result = cache.clone();
|
|
179
|
+
|
|
180
|
+
// Assert
|
|
181
|
+
expect(result).toStrictEqual({
|
|
182
|
+
scope1: {
|
|
183
|
+
key1: "data1",
|
|
184
|
+
},
|
|
185
|
+
scope2: {
|
|
186
|
+
key1: "data1",
|
|
187
|
+
key2: "data2",
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should remove the entire scope if the purged item is the last item in the scope", () => {
|
|
193
|
+
const cache = new SerializableInMemoryCache({
|
|
194
|
+
scope1: {
|
|
195
|
+
key2: "data2",
|
|
196
|
+
},
|
|
197
|
+
scope2: {
|
|
198
|
+
key1: "data1",
|
|
199
|
+
key2: "data2",
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Act
|
|
204
|
+
cache.purge("scope1", "key2");
|
|
205
|
+
const result = cache.clone();
|
|
206
|
+
|
|
207
|
+
// Assert
|
|
208
|
+
expect(result).toStrictEqual({
|
|
209
|
+
scope2: {
|
|
210
|
+
key1: "data1",
|
|
211
|
+
key2: "data2",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("#purgeScope", () => {
|
|
218
|
+
it("should remove matching entries only", () => {
|
|
219
|
+
// Arrange
|
|
220
|
+
const cache = new SerializableInMemoryCache({
|
|
221
|
+
scope1: {
|
|
222
|
+
key1: "a",
|
|
223
|
+
key2: "b",
|
|
224
|
+
key3: "a",
|
|
225
|
+
},
|
|
226
|
+
scope2: {
|
|
227
|
+
key1: "a",
|
|
228
|
+
key2: "b",
|
|
229
|
+
key3: "a",
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Act
|
|
234
|
+
cache.purgeScope("scope1", (id, value) => value === "a");
|
|
235
|
+
const result = cache.clone();
|
|
236
|
+
|
|
237
|
+
// Assert
|
|
238
|
+
expect(result).toStrictEqual({
|
|
239
|
+
scope1: {
|
|
240
|
+
key2: "b",
|
|
241
|
+
},
|
|
242
|
+
scope2: {
|
|
243
|
+
key1: "a",
|
|
244
|
+
key2: "b",
|
|
245
|
+
key3: "a",
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should remove the entire scope when there is no predicate", () => {
|
|
251
|
+
// Arrange
|
|
252
|
+
const cache = new SerializableInMemoryCache({
|
|
253
|
+
scope1: {
|
|
254
|
+
key1: "data1",
|
|
255
|
+
key2: "data2",
|
|
256
|
+
},
|
|
257
|
+
scope2: {
|
|
258
|
+
key1: "data1",
|
|
259
|
+
key2: "data2",
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Act
|
|
264
|
+
cache.purgeScope("scope1");
|
|
265
|
+
const result = cache.clone();
|
|
266
|
+
|
|
267
|
+
// Assert
|
|
268
|
+
expect(result).toStrictEqual({
|
|
269
|
+
scope2: {
|
|
270
|
+
key1: "data1",
|
|
271
|
+
key2: "data2",
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should not throw if the scope does not exist", () => {
|
|
277
|
+
// Arrange
|
|
278
|
+
const cache = new SerializableInMemoryCache({
|
|
279
|
+
scope1: {
|
|
280
|
+
key1: "data1",
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Act
|
|
285
|
+
const act = () => cache.purgeScope("scope2");
|
|
286
|
+
|
|
287
|
+
// Arrange
|
|
288
|
+
expect(act).not.toThrow();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("#purgeAll", () => {
|
|
293
|
+
it("should remove matching entries only", () => {
|
|
294
|
+
const cache = new SerializableInMemoryCache({
|
|
295
|
+
scope1: {key: "2"},
|
|
296
|
+
scope2: {key: "1"},
|
|
297
|
+
scope3: {key: "2"},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Act
|
|
301
|
+
cache.purgeAll((scope, id, value) => value === "2");
|
|
302
|
+
const result = cache.clone();
|
|
303
|
+
|
|
304
|
+
// Assert
|
|
305
|
+
expect(result).toStrictEqual({
|
|
306
|
+
scope2: {key: "1"},
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("should remove the all items if there is no predicate", () => {
|
|
311
|
+
const cache = new SerializableInMemoryCache({
|
|
312
|
+
scope1: {key: "2"},
|
|
313
|
+
scope2: {key: "1"},
|
|
314
|
+
scope3: {key: "2"},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Act
|
|
318
|
+
cache.purgeAll();
|
|
319
|
+
const result = cache.clone();
|
|
320
|
+
|
|
321
|
+
// Assert
|
|
322
|
+
expect(result).toStrictEqual({});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe("#clone", () => {
|
|
327
|
+
it("should return a copy of the cache data", () => {
|
|
328
|
+
// Arrange
|
|
329
|
+
const data = {
|
|
330
|
+
scope1: {key: "2"},
|
|
331
|
+
scope2: {key: "1"},
|
|
332
|
+
scope3: {key: "2"},
|
|
333
|
+
};
|
|
334
|
+
const cache = new SerializableInMemoryCache(data);
|
|
335
|
+
|
|
336
|
+
// Act
|
|
337
|
+
const result = cache.clone();
|
|
338
|
+
|
|
339
|
+
// Assert
|
|
340
|
+
expect(result).not.toBe(data);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should throw if there is an error during cloning", () => {
|
|
344
|
+
// Arrange
|
|
345
|
+
const cache = new SerializableInMemoryCache({
|
|
346
|
+
scope1: {key: "2"},
|
|
347
|
+
scope2: {key: "1"},
|
|
348
|
+
scope3: {key: "2"},
|
|
349
|
+
});
|
|
350
|
+
jest.spyOn(WSCore, "clone").mockImplementationOnce(() => {
|
|
351
|
+
throw new Error("BANG!");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Act
|
|
355
|
+
const act = () => cache.clone();
|
|
356
|
+
|
|
357
|
+
// Assert
|
|
358
|
+
expect(act).toThrowErrorMatchingInlineSnapshot(`
|
|
359
|
+
"An error occurred while trying to clone the cache
|
|
360
|
+
caused by
|
|
361
|
+
Error: BANG!"
|
|
362
|
+
`);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("@inUse", () => {
|
|
367
|
+
it("should return true if the cache contains data", () => {
|
|
368
|
+
// Arrange
|
|
369
|
+
const cache = new SerializableInMemoryCache({
|
|
370
|
+
scope1: {key: "2"},
|
|
371
|
+
scope2: {key: "1"},
|
|
372
|
+
scope3: {key: "2"},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Act
|
|
376
|
+
const result = cache.inUse;
|
|
377
|
+
|
|
378
|
+
// Assert
|
|
379
|
+
expect(result).toBeTruthy();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should return false if the cache is empty", () => {
|
|
383
|
+
// Arrange
|
|
384
|
+
const cache = new SerializableInMemoryCache({
|
|
385
|
+
scope1: {key: "2"},
|
|
386
|
+
scope2: {key: "1"},
|
|
387
|
+
scope3: {key: "2"},
|
|
388
|
+
});
|
|
389
|
+
cache.purgeAll();
|
|
390
|
+
|
|
391
|
+
// Act
|
|
392
|
+
const result = cache.inUse;
|
|
393
|
+
|
|
394
|
+
// Assert
|
|
395
|
+
expect(result).toBeFalsy();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
});
|