@khanacademy/wonder-blocks-data 3.1.2 → 5.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 +408 -349
- package/dist/index.js +568 -467
- package/docs.md +17 -35
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
- package/src/__tests__/generated-snapshot.test.js +60 -126
- package/src/components/__tests__/data.test.js +373 -313
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +139 -21
- package/src/components/data.md +38 -69
- package/src/components/gql-router.js +1 -1
- package/src/components/intercept-context.js +6 -3
- package/src/components/intercept-requests.js +69 -0
- package/src/components/intercept-requests.md +54 -0
- package/src/components/track-data.md +9 -23
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +217 -0
- package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
- package/src/hooks/use-gql.js +39 -31
- package/src/hooks/use-request-interception.js +54 -0
- package/src/hooks/use-server-effect.js +45 -0
- package/src/hooks/use-shared-cache.js +106 -0
- package/src/index.js +17 -20
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/request-fulfillment.test.js +42 -85
- package/src/util/__tests__/request-tracking.test.js +72 -191
- package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
- package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
- package/src/util/__tests__/ssr-cache.test.js +639 -0
- package/src/util/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/components/__tests__/intercept-data.test.js +0 -87
- package/src/components/intercept-data.js +0 -77
- package/src/components/intercept-data.md +0 -65
- package/src/hooks/__tests__/use-data.test.js +0 -826
- package/src/hooks/use-data.js +0 -143
- package/src/util/__tests__/memory-cache.test.js +0 -446
- package/src/util/__tests__/request-handler.test.js +0 -121
- package/src/util/__tests__/response-cache.test.js +0 -879
- package/src/util/memory-cache.js +0 -187
- package/src/util/request-handler.js +0 -42
- package/src/util/request-handler.md +0 -51
- package/src/util/response-cache.js +0 -213
package/src/hooks/use-data.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import MemoryCache from "../memory-cache.js";
|
|
3
|
-
|
|
4
|
-
import type {IRequestHandler} from "../types.js";
|
|
5
|
-
|
|
6
|
-
describe("MemoryCache", () => {
|
|
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("#constructor", () => {
|
|
20
|
-
it("should throw if the cloning fails", () => {
|
|
21
|
-
// Arrange
|
|
22
|
-
jest.spyOn(JSON, "stringify").mockImplementation(() => {
|
|
23
|
-
throw new Error("BANG!");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Act
|
|
27
|
-
const underTest = () =>
|
|
28
|
-
new MemoryCache({
|
|
29
|
-
BAD: {
|
|
30
|
-
BAD: {data: "FOOD"},
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Assert
|
|
35
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
36
|
-
`"An error occurred trying to initialize from a response cache snapshot: Error: BANG!"`,
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should deep clone the passed source data", () => {
|
|
41
|
-
// Arrange
|
|
42
|
-
const sourceData = {
|
|
43
|
-
MY_HANDLER: {
|
|
44
|
-
MY_KEY: {data: "THE_DATA"},
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
48
|
-
getKey: () => "MY_KEY",
|
|
49
|
-
type: "MY_HANDLER",
|
|
50
|
-
fulfillRequest: jest.fn(),
|
|
51
|
-
hydrate: true,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Act
|
|
55
|
-
const cache = new MemoryCache(sourceData);
|
|
56
|
-
// Try to mutate the cache.
|
|
57
|
-
sourceData["MY_HANDLER"]["MY_KEY"] = {data: "SOME_NEW_DATA"};
|
|
58
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
59
|
-
|
|
60
|
-
// Assert
|
|
61
|
-
expect(result).toStrictEqual({data: "THE_DATA"});
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("#store", () => {
|
|
66
|
-
it("should store the entry in the cache", () => {
|
|
67
|
-
// Arrange
|
|
68
|
-
const cache = new MemoryCache();
|
|
69
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
70
|
-
getKey: () => "MY_KEY",
|
|
71
|
-
type: "MY_HANDLER",
|
|
72
|
-
fulfillRequest: jest.fn(),
|
|
73
|
-
hydrate: true,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Act
|
|
77
|
-
cache.store<string, string>(fakeHandler, "options", {data: "data"});
|
|
78
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
79
|
-
|
|
80
|
-
// Assert
|
|
81
|
-
expect(result).toStrictEqual({data: "data"});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("should replace the entry in the handler subcache", () => {
|
|
85
|
-
// Arrange
|
|
86
|
-
const cache = new MemoryCache({
|
|
87
|
-
MY_HANDLER: {
|
|
88
|
-
MY_KEY: {error: "Oh no!"},
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
92
|
-
getKey: () => "MY_KEY",
|
|
93
|
-
type: "MY_HANDLER",
|
|
94
|
-
fulfillRequest: jest.fn(),
|
|
95
|
-
hydrate: true,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Act
|
|
99
|
-
cache.store<string, string>(fakeHandler, "options", {
|
|
100
|
-
data: "other_data",
|
|
101
|
-
});
|
|
102
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
103
|
-
|
|
104
|
-
// Assert
|
|
105
|
-
expect(result).toStrictEqual({data: "other_data"});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("#retrieve", () => {
|
|
110
|
-
it("should return null if the handler subcache is absent", () => {
|
|
111
|
-
// Arrange
|
|
112
|
-
const cache = new MemoryCache();
|
|
113
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
114
|
-
getKey: () => "MY_KEY",
|
|
115
|
-
type: "MY_HANDLER",
|
|
116
|
-
fulfillRequest: jest.fn(),
|
|
117
|
-
hydrate: true,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Act
|
|
121
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
122
|
-
|
|
123
|
-
// Assert
|
|
124
|
-
expect(result).toBeNull();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("should return null if the request key is absent from the subcache", () => {
|
|
128
|
-
// Arrange
|
|
129
|
-
const cache = new MemoryCache({
|
|
130
|
-
MY_HANDLER: {
|
|
131
|
-
SOME_OTHER_KEY: {data: "data we don't want"},
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
135
|
-
getKey: () => "MY_KEY",
|
|
136
|
-
type: "MY_HANDLER",
|
|
137
|
-
fulfillRequest: jest.fn(),
|
|
138
|
-
hydrate: true,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Act
|
|
142
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
143
|
-
|
|
144
|
-
// Assert
|
|
145
|
-
expect(result).toBeNull();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("should return the entry if it exists", () => {
|
|
149
|
-
// Arrange
|
|
150
|
-
const cache = new MemoryCache({
|
|
151
|
-
MY_HANDLER: {
|
|
152
|
-
MY_KEY: {data: "data!"},
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
156
|
-
getKey: () => "MY_KEY",
|
|
157
|
-
type: "MY_HANDLER",
|
|
158
|
-
fulfillRequest: jest.fn(),
|
|
159
|
-
hydrate: true,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// Act
|
|
163
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
164
|
-
|
|
165
|
-
// Assert
|
|
166
|
-
expect(result).toStrictEqual({data: "data!"});
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe("#remove", () => {
|
|
171
|
-
it("should return false if the handler subcache does not exist", () => {
|
|
172
|
-
// Arrange
|
|
173
|
-
const cache = new MemoryCache();
|
|
174
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
175
|
-
getKey: () => "MY_KEY",
|
|
176
|
-
type: "MY_HANDLER",
|
|
177
|
-
fulfillRequest: jest.fn(),
|
|
178
|
-
hydrate: true,
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Act
|
|
182
|
-
const result = cache.remove(fakeHandler, "options");
|
|
183
|
-
|
|
184
|
-
// Assert
|
|
185
|
-
expect(result).toBeFalsy();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("should return false if the item does not exist in the subcache", () => {
|
|
189
|
-
// Arrange
|
|
190
|
-
const cache = new MemoryCache({
|
|
191
|
-
MY_HANDLER: {},
|
|
192
|
-
});
|
|
193
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
194
|
-
getKey: () => "MY_KEY",
|
|
195
|
-
type: "MY_HANDLER",
|
|
196
|
-
fulfillRequest: jest.fn(),
|
|
197
|
-
hydrate: true,
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Act
|
|
201
|
-
const result = cache.remove(fakeHandler, "options");
|
|
202
|
-
|
|
203
|
-
// Assert
|
|
204
|
-
expect(result).toBeFalsy();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("should return true if the entry was removed", () => {
|
|
208
|
-
// Arrange
|
|
209
|
-
const cache = new MemoryCache({
|
|
210
|
-
MY_HANDLER: {
|
|
211
|
-
MY_KEY: {data: "data!"},
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
215
|
-
getKey: () => "MY_KEY",
|
|
216
|
-
type: "MY_HANDLER",
|
|
217
|
-
fulfillRequest: jest.fn(),
|
|
218
|
-
hydrate: true,
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// Act
|
|
222
|
-
const result = cache.remove(fakeHandler, "options");
|
|
223
|
-
|
|
224
|
-
// Assert
|
|
225
|
-
expect(result).toBeTruthy();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("should remove the entry", () => {
|
|
229
|
-
// Arrange
|
|
230
|
-
const cache = new MemoryCache({
|
|
231
|
-
MY_HANDLER: {
|
|
232
|
-
MY_KEY: {data: "data!"},
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
236
|
-
getKey: () => "MY_KEY",
|
|
237
|
-
type: "MY_HANDLER",
|
|
238
|
-
fulfillRequest: jest.fn(),
|
|
239
|
-
hydrate: true,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Act
|
|
243
|
-
cache.remove(fakeHandler, "options");
|
|
244
|
-
const result = cache.retrieve(fakeHandler, "options");
|
|
245
|
-
|
|
246
|
-
// Assert
|
|
247
|
-
expect(result).toBeNull();
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
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
|
-
|
|
269
|
-
it("should remove matching entries from handler subcache", () => {
|
|
270
|
-
const cache = new MemoryCache({
|
|
271
|
-
MY_HANDLER: {
|
|
272
|
-
MY_KEY: {data: "2"},
|
|
273
|
-
MY_KEY2: {data: "1"},
|
|
274
|
-
MY_KEY3: {data: "2"},
|
|
275
|
-
},
|
|
276
|
-
OTHER_HANDLER: {
|
|
277
|
-
MY_KEY: {data: "1"},
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
281
|
-
getKey: () => "MY_KEY",
|
|
282
|
-
type: "MY_HANDLER",
|
|
283
|
-
fulfillRequest: jest.fn(),
|
|
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(),
|
|
317
|
-
hydrate: true,
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// Act
|
|
321
|
-
const result = cache.removeAll(
|
|
322
|
-
fakeHandler,
|
|
323
|
-
(k, d) => d.data === "a",
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
// Assert
|
|
327
|
-
expect(result).toBe(2);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it("should remove the entire handler subcache if no predicate", () => {
|
|
331
|
-
const cache = new MemoryCache({
|
|
332
|
-
MY_HANDLER: {
|
|
333
|
-
MY_KEY: {data: "data!"},
|
|
334
|
-
MY_KEY2: {data: "data!"},
|
|
335
|
-
MY_KEY3: {data: "data!"},
|
|
336
|
-
},
|
|
337
|
-
OTHER_HANDLER: {
|
|
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!"},
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it("should return the number of items that were in the handler subcache if there was no predicate", () => {
|
|
361
|
-
const cache = new MemoryCache({
|
|
362
|
-
MY_HANDLER: {
|
|
363
|
-
MY_KEY: {data: "data!"},
|
|
364
|
-
MY_KEY2: {data: "data!"},
|
|
365
|
-
MY_KEY3: {data: "data!"},
|
|
366
|
-
},
|
|
367
|
-
OTHER_HANDLER: {
|
|
368
|
-
MY_KEY: {data: "data!"},
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
const fakeHandler: IRequestHandler<string, string> = {
|
|
372
|
-
getKey: () => "MY_KEY",
|
|
373
|
-
type: "MY_HANDLER",
|
|
374
|
-
fulfillRequest: jest.fn(),
|
|
375
|
-
hydrate: true,
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
// Act
|
|
379
|
-
const result = cache.removeAll(fakeHandler);
|
|
380
|
-
|
|
381
|
-
// Assert
|
|
382
|
-
expect(result).toBe(3);
|
|
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: {
|
|
393
|
-
MY_KEY: {data: "data!"},
|
|
394
|
-
},
|
|
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
|
-
);
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
|
|
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
|
-
});
|
|
446
|
-
});
|