@khanacademy/wonder-blocks-data 2.3.2 → 3.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 +14 -0
- package/dist/es/index.js +210 -439
- package/dist/index.js +235 -478
- package/docs.md +19 -13
- package/package.json +6 -7
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +40 -160
- package/src/__tests__/generated-snapshot.test.js +15 -195
- package/src/components/__tests__/data.test.js +159 -965
- package/src/components/__tests__/intercept-data.test.js +9 -66
- package/src/components/__tests__/track-data.test.js +6 -5
- package/src/components/data.js +9 -119
- package/src/components/data.md +38 -60
- package/src/components/intercept-context.js +2 -3
- package/src/components/intercept-data.js +2 -34
- package/src/components/intercept-data.md +7 -105
- package/src/hooks/__tests__/use-data.test.js +826 -0
- package/src/hooks/use-data.js +143 -0
- package/src/index.js +1 -3
- package/src/util/__tests__/memory-cache.test.js +134 -35
- package/src/util/__tests__/request-fulfillment.test.js +21 -36
- package/src/util/__tests__/request-handler.test.js +30 -30
- package/src/util/__tests__/request-tracking.test.js +29 -30
- package/src/util/__tests__/response-cache.test.js +521 -561
- package/src/util/__tests__/result-from-cache-entry.test.js +68 -0
- package/src/util/memory-cache.js +20 -15
- package/src/util/request-fulfillment.js +4 -0
- package/src/util/request-handler.js +4 -28
- package/src/util/request-handler.md +0 -32
- package/src/util/request-tracking.js +2 -3
- package/src/util/response-cache.js +50 -110
- package/src/util/result-from-cache-entry.js +38 -0
- package/src/util/types.js +14 -35
- package/LICENSE +0 -21
- package/src/components/__tests__/intercept-cache.test.js +0 -124
- package/src/components/__tests__/internal-data.test.js +0 -1030
- package/src/components/intercept-cache.js +0 -79
- package/src/components/intercept-cache.md +0 -103
- package/src/components/internal-data.js +0 -219
- package/src/util/__tests__/no-cache.test.js +0 -112
- package/src/util/no-cache.js +0 -66
- package/src/util/no-cache.md +0 -66
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
3
|
+
import {useState, useEffect, useContext, useRef} from "react";
|
|
4
|
+
import {RequestFulfillment} from "../util/request-fulfillment.js";
|
|
5
|
+
import InterceptContext from "../components/intercept-context.js";
|
|
6
|
+
import {TrackerContext} from "../util/request-tracking.js";
|
|
7
|
+
import {resultFromCacheEntry} from "../util/result-from-cache-entry.js";
|
|
8
|
+
import {ResponseCache} from "../util/response-cache.js";
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Result,
|
|
12
|
+
IRequestHandler,
|
|
13
|
+
ValidData,
|
|
14
|
+
CacheEntry,
|
|
15
|
+
} from "../util/types.js";
|
|
16
|
+
|
|
17
|
+
export const useData = <TOptions, TData: ValidData>(
|
|
18
|
+
handler: IRequestHandler<TOptions, TData>,
|
|
19
|
+
options: TOptions,
|
|
20
|
+
): Result<TData> => {
|
|
21
|
+
// If we're server-side or hydrating, we'll have a cached entry to use.
|
|
22
|
+
// So we get that and use it to initialize our state.
|
|
23
|
+
// This works in both hydration and SSR because the very first call to
|
|
24
|
+
// this will have cached data in those cases as it will be present on the
|
|
25
|
+
// initial render - and subsequent renders on the client it will be null.
|
|
26
|
+
const cachedResult = ResponseCache.Default.getEntry<TOptions, TData>(
|
|
27
|
+
handler,
|
|
28
|
+
options,
|
|
29
|
+
);
|
|
30
|
+
const [result, setResult] = useState<?CacheEntry<TData>>(cachedResult);
|
|
31
|
+
|
|
32
|
+
// Lookup to see if there's an interceptor for the handler.
|
|
33
|
+
// If we have one, we need to replace the handler with one that
|
|
34
|
+
// uses the interceptor.
|
|
35
|
+
const interceptorMap = useContext(InterceptContext);
|
|
36
|
+
const interceptor = interceptorMap[handler.type];
|
|
37
|
+
|
|
38
|
+
// If we have an interceptor, we need to replace the handler with one that
|
|
39
|
+
// uses the interceptor. This helper function generates a new handler.
|
|
40
|
+
// We need this before we track the request as we want the interceptor
|
|
41
|
+
// to also work for tracked requests to simplify testing the server-side
|
|
42
|
+
// request fulfillment.
|
|
43
|
+
const getMaybeInterceptedHandler = () => {
|
|
44
|
+
if (interceptor == null) {
|
|
45
|
+
return handler;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const fulfillRequestFn = (options) =>
|
|
49
|
+
interceptor.fulfillRequest(options) ??
|
|
50
|
+
handler.fulfillRequest(options);
|
|
51
|
+
return {
|
|
52
|
+
fulfillRequest: fulfillRequestFn,
|
|
53
|
+
getKey: (options) => handler.getKey(options),
|
|
54
|
+
type: handler.type,
|
|
55
|
+
hydrate: handler.hydrate,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// We only track data requests when we are server-side and we don't
|
|
60
|
+
// already have a result, as given by the cachedData (which is also the
|
|
61
|
+
// initial value for the result state).
|
|
62
|
+
const maybeTrack = useContext(TrackerContext);
|
|
63
|
+
if (result == null && Server.isServerSide()) {
|
|
64
|
+
maybeTrack?.(getMaybeInterceptedHandler(), options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// We need to update our request when the handler changes or the key
|
|
68
|
+
// to the options change, so we keep track of those.
|
|
69
|
+
// However, even if we are hydrating from cache, we still need to make the
|
|
70
|
+
// request at least once, so we do not initialize these references.
|
|
71
|
+
const handlerRef = useRef();
|
|
72
|
+
const keyRef = useRef();
|
|
73
|
+
const interceptorRef = useRef();
|
|
74
|
+
|
|
75
|
+
// This effect will ensure that we fulfill the request as desired.
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
// If we are server-side, then just skip the effect. We track requests
|
|
78
|
+
// during SSR and fulfill them outside of the React render cycle.
|
|
79
|
+
// NOTE: This shouldn't happen since effects would not run on the server
|
|
80
|
+
// but let's be defensive - I think it makes the code clearer.
|
|
81
|
+
/* istanbul ignore next */
|
|
82
|
+
if (Server.isServerSide()) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Update our refs to the current handler and key.
|
|
87
|
+
handlerRef.current = handler;
|
|
88
|
+
keyRef.current = handler.getKey(options);
|
|
89
|
+
interceptorRef.current = interceptor;
|
|
90
|
+
|
|
91
|
+
// If we're not hydrating a result, we want to make sure we set our
|
|
92
|
+
// result to null so that we're in the loading state.
|
|
93
|
+
if (cachedResult == null) {
|
|
94
|
+
// Mark ourselves as loading.
|
|
95
|
+
setResult(null);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// We aren't server-side, so let's make the request.
|
|
99
|
+
// The request handler is in control of whether that request actually
|
|
100
|
+
// happens or not.
|
|
101
|
+
let cancel = false;
|
|
102
|
+
RequestFulfillment.Default.fulfill(
|
|
103
|
+
getMaybeInterceptedHandler(),
|
|
104
|
+
options,
|
|
105
|
+
)
|
|
106
|
+
.then((updateEntry) => {
|
|
107
|
+
if (cancel) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setResult(updateEntry);
|
|
111
|
+
return;
|
|
112
|
+
})
|
|
113
|
+
.catch((e) => {
|
|
114
|
+
if (cancel) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* We should never get here as errors in fulfillment are part
|
|
119
|
+
* of the `then`, but if we do.
|
|
120
|
+
*/
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.error(
|
|
123
|
+
`Unexpected error occurred during data fulfillment: ${e}`,
|
|
124
|
+
);
|
|
125
|
+
setResult({
|
|
126
|
+
data: null,
|
|
127
|
+
error: typeof e === "string" ? e : e.message,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
cancel = true;
|
|
134
|
+
};
|
|
135
|
+
// - handler.getKey is a proxy for options
|
|
136
|
+
// - We don't want to trigger on cachedResult changing, we're
|
|
137
|
+
// just using that as a flag for render state if the other things
|
|
138
|
+
// trigger this effect.
|
|
139
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
140
|
+
}, [handler, handler.getKey(options), interceptor]);
|
|
141
|
+
|
|
142
|
+
return resultFromCacheEntry(result);
|
|
143
|
+
};
|
package/src/index.js
CHANGED
|
@@ -15,7 +15,6 @@ export type {
|
|
|
15
15
|
CacheEntry,
|
|
16
16
|
Result,
|
|
17
17
|
IRequestHandler,
|
|
18
|
-
ICache,
|
|
19
18
|
ResponseCache,
|
|
20
19
|
} from "./util/types.js";
|
|
21
20
|
|
|
@@ -61,5 +60,4 @@ export {default as RequestHandler} from "./util/request-handler.js";
|
|
|
61
60
|
export {default as TrackData} from "./components/track-data.js";
|
|
62
61
|
export {default as Data} from "./components/data.js";
|
|
63
62
|
export {default as InterceptData} from "./components/intercept-data.js";
|
|
64
|
-
export {
|
|
65
|
-
export {default as NoCache} from "./util/no-cache.js";
|
|
63
|
+
export {useData} from "./hooks/use-data.js";
|
|
@@ -47,9 +47,7 @@ describe("MemoryCache", () => {
|
|
|
47
47
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
48
48
|
getKey: () => "MY_KEY",
|
|
49
49
|
type: "MY_HANDLER",
|
|
50
|
-
shouldRefreshCache: () => false,
|
|
51
50
|
fulfillRequest: jest.fn(),
|
|
52
|
-
cache: null,
|
|
53
51
|
hydrate: true,
|
|
54
52
|
};
|
|
55
53
|
|
|
@@ -71,9 +69,7 @@ describe("MemoryCache", () => {
|
|
|
71
69
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
72
70
|
getKey: () => "MY_KEY",
|
|
73
71
|
type: "MY_HANDLER",
|
|
74
|
-
shouldRefreshCache: () => false,
|
|
75
72
|
fulfillRequest: jest.fn(),
|
|
76
|
-
cache: null,
|
|
77
73
|
hydrate: true,
|
|
78
74
|
};
|
|
79
75
|
|
|
@@ -95,9 +91,7 @@ describe("MemoryCache", () => {
|
|
|
95
91
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
96
92
|
getKey: () => "MY_KEY",
|
|
97
93
|
type: "MY_HANDLER",
|
|
98
|
-
shouldRefreshCache: () => false,
|
|
99
94
|
fulfillRequest: jest.fn(),
|
|
100
|
-
cache: null,
|
|
101
95
|
hydrate: true,
|
|
102
96
|
};
|
|
103
97
|
|
|
@@ -119,9 +113,7 @@ describe("MemoryCache", () => {
|
|
|
119
113
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
120
114
|
getKey: () => "MY_KEY",
|
|
121
115
|
type: "MY_HANDLER",
|
|
122
|
-
shouldRefreshCache: () => false,
|
|
123
116
|
fulfillRequest: jest.fn(),
|
|
124
|
-
cache: null,
|
|
125
117
|
hydrate: true,
|
|
126
118
|
};
|
|
127
119
|
|
|
@@ -142,9 +134,7 @@ describe("MemoryCache", () => {
|
|
|
142
134
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
143
135
|
getKey: () => "MY_KEY",
|
|
144
136
|
type: "MY_HANDLER",
|
|
145
|
-
shouldRefreshCache: () => false,
|
|
146
137
|
fulfillRequest: jest.fn(),
|
|
147
|
-
cache: null,
|
|
148
138
|
hydrate: true,
|
|
149
139
|
};
|
|
150
140
|
|
|
@@ -165,9 +155,7 @@ describe("MemoryCache", () => {
|
|
|
165
155
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
166
156
|
getKey: () => "MY_KEY",
|
|
167
157
|
type: "MY_HANDLER",
|
|
168
|
-
shouldRefreshCache: () => false,
|
|
169
158
|
fulfillRequest: jest.fn(),
|
|
170
|
-
cache: null,
|
|
171
159
|
hydrate: true,
|
|
172
160
|
};
|
|
173
161
|
|
|
@@ -186,9 +174,7 @@ describe("MemoryCache", () => {
|
|
|
186
174
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
187
175
|
getKey: () => "MY_KEY",
|
|
188
176
|
type: "MY_HANDLER",
|
|
189
|
-
shouldRefreshCache: () => false,
|
|
190
177
|
fulfillRequest: jest.fn(),
|
|
191
|
-
cache: null,
|
|
192
178
|
hydrate: true,
|
|
193
179
|
};
|
|
194
180
|
|
|
@@ -207,9 +193,7 @@ describe("MemoryCache", () => {
|
|
|
207
193
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
208
194
|
getKey: () => "MY_KEY",
|
|
209
195
|
type: "MY_HANDLER",
|
|
210
|
-
shouldRefreshCache: () => false,
|
|
211
196
|
fulfillRequest: jest.fn(),
|
|
212
|
-
cache: null,
|
|
213
197
|
hydrate: true,
|
|
214
198
|
};
|
|
215
199
|
|
|
@@ -230,9 +214,7 @@ describe("MemoryCache", () => {
|
|
|
230
214
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
231
215
|
getKey: () => "MY_KEY",
|
|
232
216
|
type: "MY_HANDLER",
|
|
233
|
-
shouldRefreshCache: () => false,
|
|
234
217
|
fulfillRequest: jest.fn(),
|
|
235
|
-
cache: null,
|
|
236
218
|
hydrate: true,
|
|
237
219
|
};
|
|
238
220
|
|
|
@@ -253,9 +235,7 @@ describe("MemoryCache", () => {
|
|
|
253
235
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
254
236
|
getKey: () => "MY_KEY",
|
|
255
237
|
type: "MY_HANDLER",
|
|
256
|
-
shouldRefreshCache: () => false,
|
|
257
238
|
fulfillRequest: jest.fn(),
|
|
258
|
-
cache: null,
|
|
259
239
|
hydrate: true,
|
|
260
240
|
};
|
|
261
241
|
|
|
@@ -269,6 +249,23 @@ describe("MemoryCache", () => {
|
|
|
269
249
|
});
|
|
270
250
|
|
|
271
251
|
describe("#removeAll", () => {
|
|
252
|
+
it("should return 0 if the handler subcache is absent", () => {
|
|
253
|
+
// Arrange
|
|
254
|
+
const cache = new MemoryCache();
|
|
255
|
+
const fakeHandler: IRequestHandler<string, string> = {
|
|
256
|
+
getKey: () => "MY_KEY",
|
|
257
|
+
type: "MY_HANDLER",
|
|
258
|
+
fulfillRequest: jest.fn(),
|
|
259
|
+
hydrate: true,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Act
|
|
263
|
+
const result = cache.removeAll(fakeHandler);
|
|
264
|
+
|
|
265
|
+
// Assert
|
|
266
|
+
expect(result).toBe(0);
|
|
267
|
+
});
|
|
268
|
+
|
|
272
269
|
it("should remove matching entries from handler subcache", () => {
|
|
273
270
|
const cache = new MemoryCache({
|
|
274
271
|
MY_HANDLER: {
|
|
@@ -283,32 +280,84 @@ describe("MemoryCache", () => {
|
|
|
283
280
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
284
281
|
getKey: () => "MY_KEY",
|
|
285
282
|
type: "MY_HANDLER",
|
|
286
|
-
shouldRefreshCache: () => false,
|
|
287
283
|
fulfillRequest: jest.fn(),
|
|
288
|
-
|
|
284
|
+
hydrate: true,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Act
|
|
288
|
+
cache.removeAll(fakeHandler, (k, d) => d.data === "2");
|
|
289
|
+
const result = cache.cloneData();
|
|
290
|
+
|
|
291
|
+
// Assert
|
|
292
|
+
expect(result).toStrictEqual({
|
|
293
|
+
MY_HANDLER: {
|
|
294
|
+
MY_KEY2: {data: "1"},
|
|
295
|
+
},
|
|
296
|
+
OTHER_HANDLER: {
|
|
297
|
+
MY_KEY: {data: "1"},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should return the number of items that matched the predicate and were removed", () => {
|
|
303
|
+
const cache = new MemoryCache({
|
|
304
|
+
MY_HANDLER: {
|
|
305
|
+
MY_KEY: {data: "a"},
|
|
306
|
+
MY_KEY2: {data: "b"},
|
|
307
|
+
MY_KEY3: {data: "a"},
|
|
308
|
+
},
|
|
309
|
+
OTHER_HANDLER: {
|
|
310
|
+
MY_KEY: {data: "b"},
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
const fakeHandler: IRequestHandler<string, string> = {
|
|
314
|
+
getKey: () => "MY_KEY",
|
|
315
|
+
type: "MY_HANDLER",
|
|
316
|
+
fulfillRequest: jest.fn(),
|
|
289
317
|
hydrate: true,
|
|
290
318
|
};
|
|
291
319
|
|
|
292
320
|
// Act
|
|
293
321
|
const result = cache.removeAll(
|
|
294
322
|
fakeHandler,
|
|
295
|
-
(k, d) => d.data === "
|
|
323
|
+
(k, d) => d.data === "a",
|
|
296
324
|
);
|
|
297
|
-
const after = cache.cloneData();
|
|
298
325
|
|
|
299
326
|
// Assert
|
|
300
327
|
expect(result).toBe(2);
|
|
301
|
-
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should remove the entire handler subcache if no predicate", () => {
|
|
331
|
+
const cache = new MemoryCache({
|
|
302
332
|
MY_HANDLER: {
|
|
303
|
-
|
|
333
|
+
MY_KEY: {data: "data!"},
|
|
334
|
+
MY_KEY2: {data: "data!"},
|
|
335
|
+
MY_KEY3: {data: "data!"},
|
|
304
336
|
},
|
|
305
337
|
OTHER_HANDLER: {
|
|
306
|
-
MY_KEY: {data: "
|
|
338
|
+
MY_KEY: {data: "data!"},
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
const fakeHandler: IRequestHandler<string, string> = {
|
|
342
|
+
getKey: () => "MY_KEY",
|
|
343
|
+
type: "MY_HANDLER",
|
|
344
|
+
fulfillRequest: jest.fn(),
|
|
345
|
+
hydrate: true,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Act
|
|
349
|
+
cache.removeAll(fakeHandler);
|
|
350
|
+
const result = cache.cloneData();
|
|
351
|
+
|
|
352
|
+
// Assert
|
|
353
|
+
expect(result).toStrictEqual({
|
|
354
|
+
OTHER_HANDLER: {
|
|
355
|
+
MY_KEY: {data: "data!"},
|
|
307
356
|
},
|
|
308
357
|
});
|
|
309
358
|
});
|
|
310
359
|
|
|
311
|
-
it("should
|
|
360
|
+
it("should return the number of items that were in the handler subcache if there was no predicate", () => {
|
|
312
361
|
const cache = new MemoryCache({
|
|
313
362
|
MY_HANDLER: {
|
|
314
363
|
MY_KEY: {data: "data!"},
|
|
@@ -322,26 +371,76 @@ describe("MemoryCache", () => {
|
|
|
322
371
|
const fakeHandler: IRequestHandler<string, string> = {
|
|
323
372
|
getKey: () => "MY_KEY",
|
|
324
373
|
type: "MY_HANDLER",
|
|
325
|
-
shouldRefreshCache: () => false,
|
|
326
374
|
fulfillRequest: jest.fn(),
|
|
327
|
-
cache: null,
|
|
328
375
|
hydrate: true,
|
|
329
376
|
};
|
|
330
377
|
|
|
331
378
|
// Act
|
|
332
379
|
const result = cache.removeAll(fakeHandler);
|
|
333
|
-
const after = cache.cloneData();
|
|
334
380
|
|
|
335
381
|
// Assert
|
|
336
382
|
expect(result).toBe(3);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe("#cloneData", () => {
|
|
387
|
+
it("should return a copy of the cache data", () => {});
|
|
388
|
+
|
|
389
|
+
it("should throw if there is an error during cloning", () => {
|
|
390
|
+
// Arrange
|
|
391
|
+
const cache = new MemoryCache({
|
|
392
|
+
MY_HANDLER: {
|
|
340
393
|
MY_KEY: {data: "data!"},
|
|
341
394
|
},
|
|
342
395
|
});
|
|
396
|
+
jest.spyOn(JSON, "stringify").mockImplementation(() => {
|
|
397
|
+
throw new Error("BANG!");
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Act
|
|
401
|
+
const act = () => cache.cloneData();
|
|
402
|
+
|
|
403
|
+
// Assert
|
|
404
|
+
expect(act).toThrowErrorMatchingInlineSnapshot(
|
|
405
|
+
`"An error occurred while trying to clone the cache: Error: BANG!"`,
|
|
406
|
+
);
|
|
343
407
|
});
|
|
344
408
|
});
|
|
345
409
|
|
|
346
|
-
describe("
|
|
410
|
+
describe("@inUse", () => {
|
|
411
|
+
it("should return true if the cache contains data", () => {
|
|
412
|
+
// Arrange
|
|
413
|
+
const cache = new MemoryCache({
|
|
414
|
+
MY_HANDLER: {
|
|
415
|
+
MY_KEY: {data: "data!"},
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Act
|
|
420
|
+
const result = cache.inUse;
|
|
421
|
+
|
|
422
|
+
// Assert
|
|
423
|
+
expect(result).toBeTruthy();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("should return false if the cache is empty", () => {
|
|
427
|
+
// Arrange
|
|
428
|
+
const cache = new MemoryCache({
|
|
429
|
+
MY_HANDLER: {
|
|
430
|
+
MY_KEY: {data: "data!"},
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
cache.removeAll(
|
|
434
|
+
({
|
|
435
|
+
type: "MY_HANDLER",
|
|
436
|
+
}: any),
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// Act
|
|
440
|
+
const result = cache.inUse;
|
|
441
|
+
|
|
442
|
+
// Assert
|
|
443
|
+
expect(result).toBeFalsy();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
347
446
|
});
|
|
@@ -17,32 +17,30 @@ describe("RequestFulfillment", () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
describe("#fulfill", () => {
|
|
20
|
-
it("should cache errors caused directly by handlers", async () => {
|
|
20
|
+
it("should attempt to cache errors caused directly by handlers", async () => {
|
|
21
21
|
// Arrange
|
|
22
22
|
const responseCache = new ResponseCache();
|
|
23
23
|
const requestFulfillment = new RequestFulfillment(responseCache);
|
|
24
|
+
const error = new Error("OH NO!");
|
|
24
25
|
const fakeBadHandler: IRequestHandler<any, any> = {
|
|
25
26
|
fulfillRequest: () => {
|
|
26
|
-
throw
|
|
27
|
+
throw error;
|
|
27
28
|
},
|
|
28
29
|
getKey: jest.fn().mockReturnValue("MY_KEY"),
|
|
29
|
-
shouldRefreshCache: () => false,
|
|
30
30
|
type: "MY_TYPE",
|
|
31
|
-
cache: null,
|
|
32
31
|
hydrate: true,
|
|
33
32
|
};
|
|
33
|
+
const cacheErrorSpy = jest.spyOn(responseCache, "cacheError");
|
|
34
34
|
|
|
35
35
|
// Act
|
|
36
36
|
await requestFulfillment.fulfill(fakeBadHandler, "OPTIONS");
|
|
37
37
|
|
|
38
38
|
// Assert
|
|
39
|
-
expect(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
});
|
|
39
|
+
expect(cacheErrorSpy).toHaveBeenCalledWith(
|
|
40
|
+
fakeBadHandler,
|
|
41
|
+
"OPTIONS",
|
|
42
|
+
error,
|
|
43
|
+
);
|
|
46
44
|
});
|
|
47
45
|
|
|
48
46
|
it("should cache errors occurring in promises", async () => {
|
|
@@ -53,23 +51,20 @@ describe("RequestFulfillment", () => {
|
|
|
53
51
|
fulfillRequest: () =>
|
|
54
52
|
new Promise((resolve, reject) => reject("OH NO!")),
|
|
55
53
|
getKey: (o) => o,
|
|
56
|
-
shouldRefreshCache: () => false,
|
|
57
54
|
type: "BAD_REQUEST",
|
|
58
|
-
cache: null,
|
|
59
55
|
hydrate: true,
|
|
60
56
|
};
|
|
57
|
+
const cacheErrorSpy = jest.spyOn(responseCache, "cacheError");
|
|
61
58
|
|
|
62
59
|
// Act
|
|
63
60
|
await requestFulfillment.fulfill(fakeBadRequestHandler, "OPTIONS");
|
|
64
61
|
|
|
65
62
|
// Assert
|
|
66
|
-
expect(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
});
|
|
63
|
+
expect(cacheErrorSpy).toHaveBeenCalledWith(
|
|
64
|
+
fakeBadRequestHandler,
|
|
65
|
+
"OPTIONS",
|
|
66
|
+
"OH NO!",
|
|
67
|
+
);
|
|
73
68
|
});
|
|
74
69
|
|
|
75
70
|
it("should cache data from requests", async () => {
|
|
@@ -79,24 +74,20 @@ describe("RequestFulfillment", () => {
|
|
|
79
74
|
const fakeRequestHandler: IRequestHandler<string, any> = {
|
|
80
75
|
fulfillRequest: () => Promise.resolve("DATA!"),
|
|
81
76
|
getKey: (o) => o,
|
|
82
|
-
shouldRefreshCache: () => false,
|
|
83
77
|
type: "VALID_REQUEST",
|
|
84
|
-
cache: null,
|
|
85
78
|
hydrate: true,
|
|
86
79
|
};
|
|
80
|
+
const cacheDataSpy = jest.spyOn(responseCache, "cacheData");
|
|
87
81
|
|
|
88
82
|
// Act
|
|
89
83
|
await requestFulfillment.fulfill(fakeRequestHandler, "OPTIONS");
|
|
90
|
-
const result = responseCache.cloneHydratableData();
|
|
91
84
|
|
|
92
85
|
// Assert
|
|
93
|
-
expect(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
},
|
|
99
|
-
});
|
|
86
|
+
expect(cacheDataSpy).toHaveBeenCalledWith(
|
|
87
|
+
fakeRequestHandler,
|
|
88
|
+
"OPTIONS",
|
|
89
|
+
"DATA!",
|
|
90
|
+
);
|
|
100
91
|
});
|
|
101
92
|
|
|
102
93
|
it("should return a promise of the result", async () => {
|
|
@@ -106,9 +97,7 @@ describe("RequestFulfillment", () => {
|
|
|
106
97
|
const fakeRequestHandler: IRequestHandler<string, any> = {
|
|
107
98
|
fulfillRequest: () => Promise.resolve("DATA!"),
|
|
108
99
|
getKey: (o) => o,
|
|
109
|
-
shouldRefreshCache: () => false,
|
|
110
100
|
type: "VALID_REQUEST",
|
|
111
|
-
cache: null,
|
|
112
101
|
hydrate: true,
|
|
113
102
|
};
|
|
114
103
|
|
|
@@ -131,9 +120,7 @@ describe("RequestFulfillment", () => {
|
|
|
131
120
|
const fakeRequestHandler: IRequestHandler<string, any> = {
|
|
132
121
|
fulfillRequest: () => Promise.resolve("DATA!"),
|
|
133
122
|
getKey: (o) => o,
|
|
134
|
-
shouldRefreshCache: () => false,
|
|
135
123
|
type: "VALID_REQUEST",
|
|
136
|
-
cache: null,
|
|
137
124
|
hydrate: true,
|
|
138
125
|
};
|
|
139
126
|
|
|
@@ -158,9 +145,7 @@ describe("RequestFulfillment", () => {
|
|
|
158
145
|
const fakeRequestHandler: IRequestHandler<string, any> = {
|
|
159
146
|
fulfillRequest: () => Promise.resolve("DATA!"),
|
|
160
147
|
getKey: (o) => o,
|
|
161
|
-
shouldRefreshCache: () => false,
|
|
162
148
|
type: "VALID_REQUEST",
|
|
163
|
-
cache: null,
|
|
164
149
|
hydrate: false,
|
|
165
150
|
};
|
|
166
151
|
|