@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/es/index.js +771 -372
  3. package/dist/index.js +1191 -550
  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 +38 -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 +708 -0
  55. package/src/hooks/__tests__/use-server-effect.test.js +39 -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 +12 -5
  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/abort-error.js +15 -0
  73. package/src/util/data-error.js +58 -0
  74. package/src/util/get-gql-data-from-response.js +3 -2
  75. package/src/util/gql-error.js +19 -11
  76. package/src/util/merge-gql-context.js +34 -0
  77. package/src/util/request-fulfillment.js +49 -46
  78. package/src/util/request-tracking.js +69 -15
  79. package/src/util/result-from-cache-response.js +12 -16
  80. package/src/util/scoped-in-memory-cache.js +24 -47
  81. package/src/util/serializable-in-memory-cache.js +49 -0
  82. package/src/util/ssr-cache.js +9 -8
  83. package/src/util/status.js +30 -0
  84. package/src/util/types.js +18 -1
  85. 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(null);
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: "OH NO!",
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: "OH NO!",
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 cope gracefully with null fulfillments", async () => {
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
- ).mockReturnValue(null);
254
- const fakeValidHandler = () => Promise.resolve("DATA");
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(null);
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 loading status if cache entry is null", () => {
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).toStrictEqual({
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: "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.clone();
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.clone();
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.clone();
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.clone();
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.clone();
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.clone();
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
+ });