@khanacademy/wonder-blocks-data 3.1.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/es/index.js +375 -335
- package/dist/index.js +527 -461
- package/docs.md +17 -35
- package/package.json +3 -3
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
- package/src/__tests__/generated-snapshot.test.js +56 -122
- package/src/components/__tests__/data.test.js +372 -297
- package/src/components/__tests__/intercept-data.test.js +6 -30
- package/src/components/data.js +153 -21
- package/src/components/data.md +38 -69
- package/src/components/gql-router.js +1 -1
- package/src/components/intercept-context.js +6 -2
- package/src/components/intercept-data.js +40 -51
- package/src/components/intercept-data.md +13 -27
- package/src/components/track-data.md +9 -23
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -0
- package/src/hooks/__tests__/use-server-effect.test.js +217 -0
- package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
- package/src/hooks/use-gql.js +39 -31
- package/src/hooks/use-server-effect.js +45 -0
- package/src/hooks/use-shared-cache.js +106 -0
- package/src/index.js +15 -19
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/request-fulfillment.test.js +42 -85
- package/src/util/__tests__/request-tracking.test.js +72 -191
- package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
- package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
- package/src/util/__tests__/ssr-cache.test.js +639 -0
- package/src/util/gql-types.js +5 -10
- package/src/util/request-fulfillment.js +36 -44
- package/src/util/request-tracking.js +62 -75
- package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
- package/src/util/scoped-in-memory-cache.js +149 -0
- package/src/util/ssr-cache.js +206 -0
- package/src/util/types.js +43 -108
- package/src/hooks/__tests__/use-data.test.js +0 -826
- package/src/hooks/use-data.js +0 -143
- package/src/util/__tests__/memory-cache.test.js +0 -446
- package/src/util/__tests__/request-handler.test.js +0 -121
- package/src/util/__tests__/response-cache.test.js +0 -879
- package/src/util/memory-cache.js +0 -187
- package/src/util/request-handler.js +0 -42
- package/src/util/request-handler.md +0 -51
- package/src/util/response-cache.js +0 -213
|
@@ -2,18 +2,18 @@ When you want to generate tests that check the loading state and
|
|
|
2
2
|
subsequent loaded state are working correctly for your uses of `Data` you can
|
|
3
3
|
use the `InterceptData` component.
|
|
4
4
|
|
|
5
|
-
This component takes
|
|
6
|
-
|
|
5
|
+
This component takes three props; children to be rendered, the handler of the
|
|
6
|
+
to fulfill the request, and the id of the request that is being intercepted.
|
|
7
7
|
|
|
8
8
|
Note that this component is expected to be used only within test cases and
|
|
9
9
|
usually only as a single instance. In flight requests for a given handler
|
|
10
10
|
type can be shared and as such, using `InterceptData` alongside non-intercepted
|
|
11
|
-
`Data` components with the same
|
|
11
|
+
`Data` components with the same id can have indeterminate outcomes.
|
|
12
12
|
|
|
13
|
-
The `
|
|
13
|
+
The `handler` intercept function has the form:
|
|
14
14
|
|
|
15
15
|
```js static
|
|
16
|
-
(
|
|
16
|
+
() => ?Promise<?TData>;
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
If this method returns `null`, the default behavior occurs. This
|
|
@@ -23,40 +23,26 @@ means that a request will be made for data via the handler assigned to the
|
|
|
23
23
|
```jsx
|
|
24
24
|
import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
|
|
25
25
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
26
|
-
import {InterceptData, Data
|
|
26
|
+
import {InterceptData, Data} from "@khanacademy/wonder-blocks-data";
|
|
27
27
|
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
28
28
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
29
29
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
constructor() {
|
|
33
|
-
super("INTERCEPT_DATA_HANDLER1");
|
|
34
|
-
}
|
|
31
|
+
const myHandler = () => Promise.reject(new Error("You should not see this!"));
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
return Promise.reject(new Error("You should not see this!"));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
33
|
+
const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
const fulfillRequestInterceptor = function(options) {
|
|
43
|
-
if (options === "DATA") {
|
|
44
|
-
return Promise.resolve("INTERCEPTED DATA!");
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
<InterceptData handler={handler} fulfillRequest={fulfillRequestInterceptor}>
|
|
35
|
+
<InterceptData handler={interceptHandler} requestId="INTERCEPT_EXAMPLE">
|
|
50
36
|
<View>
|
|
51
37
|
<Body>This received intercepted data!</Body>
|
|
52
|
-
<Data handler={
|
|
53
|
-
{(
|
|
54
|
-
if (
|
|
38
|
+
<Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
|
|
39
|
+
{(result) => {
|
|
40
|
+
if (result.status !== "success") {
|
|
55
41
|
return "If you see this, the example is broken!";
|
|
56
42
|
}
|
|
57
43
|
|
|
58
44
|
return (
|
|
59
|
-
<BodyMonospace>{data}</BodyMonospace>
|
|
45
|
+
<BodyMonospace>{result.data}</BodyMonospace>
|
|
60
46
|
);
|
|
61
47
|
}}
|
|
62
48
|
</Data>
|
|
@@ -65,19 +65,11 @@ import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
|
65
65
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
66
66
|
import Button from "@khanacademy/wonder-blocks-button";
|
|
67
67
|
import {Server, View} from "@khanacademy/wonder-blocks-core";
|
|
68
|
-
import {Data, TrackData,
|
|
68
|
+
import {Data, TrackData, fulfillAllDataRequests} from "@khanacademy/wonder-blocks-data";
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
fulfillRequest(options) {
|
|
76
|
-
return new Promise((resolve, reject) =>
|
|
77
|
-
setTimeout(() => resolve("DATA!"), 3000),
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
70
|
+
const myPretendHandler = () => new Promise((resolve, reject) =>
|
|
71
|
+
setTimeout(() => resolve("DATA!"), 3000),
|
|
72
|
+
);
|
|
81
73
|
|
|
82
74
|
class Example extends React.Component {
|
|
83
75
|
constructor() {
|
|
@@ -87,7 +79,6 @@ class Example extends React.Component {
|
|
|
87
79
|
* for the scope of this component.
|
|
88
80
|
*/
|
|
89
81
|
this.state = {};
|
|
90
|
-
this._handler = new MyPretendHandler();
|
|
91
82
|
}
|
|
92
83
|
|
|
93
84
|
static getDerivedStateFromError(error) {
|
|
@@ -133,15 +124,16 @@ class Example extends React.Component {
|
|
|
133
124
|
const data = this.state.data
|
|
134
125
|
? JSON.stringify(this.state.data, undefined, " ")
|
|
135
126
|
: "Data requested...";
|
|
127
|
+
|
|
136
128
|
return (
|
|
137
129
|
<React.Fragment>
|
|
138
130
|
<Strut size={Spacing.small_12} />
|
|
139
131
|
<TrackData>
|
|
140
|
-
<Data handler={
|
|
141
|
-
{(
|
|
132
|
+
<Data handler={myPretendHandler} requestId="TRACK_DATA_EXAMPLE">
|
|
133
|
+
{(result) => (
|
|
142
134
|
<View>
|
|
143
|
-
<BodyMonospace>{`Loading: ${loading}`}</BodyMonospace>
|
|
144
|
-
<BodyMonospace>{`Data: ${JSON.stringify(data)}`}</BodyMonospace>
|
|
135
|
+
<BodyMonospace>{`Loading: ${result.status === "loading"}`}</BodyMonospace>
|
|
136
|
+
<BodyMonospace>{`Data: ${JSON.stringify(result.data)}`}</BodyMonospace>
|
|
145
137
|
</View>
|
|
146
138
|
)}
|
|
147
139
|
</Data>
|
|
@@ -162,12 +154,6 @@ class Example extends React.Component {
|
|
|
162
154
|
rendered tree.
|
|
163
155
|
</Body>
|
|
164
156
|
<Strut size={Spacing.small_12} />
|
|
165
|
-
<Body>
|
|
166
|
-
If you click to remount after the data appears, we'll
|
|
167
|
-
rerender with the now cached data, and above should
|
|
168
|
-
update accordingly.
|
|
169
|
-
</Body>
|
|
170
|
-
<Strut size={Spacing.small_12} />
|
|
171
157
|
<BodyMonospace>{data}</BodyMonospace>
|
|
172
158
|
</View>
|
|
173
159
|
</React.Fragment>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`#useSharedCache should throw if the id is 1`] = `[InvalidInputError: id must be a non-empty string]`;
|
|
4
|
+
|
|
5
|
+
exports[`#useSharedCache should throw if the id is [Function anonymous] 1`] = `[InvalidInputError: id must be a non-empty string]`;
|
|
6
|
+
|
|
7
|
+
exports[`#useSharedCache should throw if the id is 5 1`] = `[InvalidInputError: id must be a non-empty string]`;
|
|
8
|
+
|
|
9
|
+
exports[`#useSharedCache should throw if the id is null 1`] = `[InvalidInputError: id must be a non-empty string]`;
|
|
10
|
+
|
|
11
|
+
exports[`#useSharedCache should throw if the scope is 1`] = `[InvalidInputError: scope must be a non-empty string]`;
|
|
12
|
+
|
|
13
|
+
exports[`#useSharedCache should throw if the scope is [Function anonymous] 1`] = `[InvalidInputError: scope must be a non-empty string]`;
|
|
14
|
+
|
|
15
|
+
exports[`#useSharedCache should throw if the scope is 5 1`] = `[InvalidInputError: scope must be a non-empty string]`;
|
|
16
|
+
|
|
17
|
+
exports[`#useSharedCache should throw if the scope is null 1`] = `[InvalidInputError: scope must be a non-empty string]`;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {renderHook as clientRenderHook} from "@testing-library/react-hooks";
|
|
3
|
+
import {renderHook as serverRenderHook} from "@testing-library/react-hooks/server";
|
|
4
|
+
|
|
5
|
+
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
|
|
7
|
+
import TrackData from "../../components/track-data.js";
|
|
8
|
+
import {RequestFulfillment} from "../../util/request-fulfillment.js";
|
|
9
|
+
import {SsrCache} from "../../util/ssr-cache.js";
|
|
10
|
+
import {RequestTracker} from "../../util/request-tracking.js";
|
|
11
|
+
|
|
12
|
+
import {useServerEffect} from "../use-server-effect.js";
|
|
13
|
+
|
|
14
|
+
describe("#useServerEffect", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
const responseCache = new SsrCache();
|
|
17
|
+
jest.spyOn(SsrCache, "Default", "get").mockReturnValue(responseCache);
|
|
18
|
+
jest.spyOn(RequestFulfillment, "Default", "get").mockReturnValue(
|
|
19
|
+
new RequestFulfillment(responseCache),
|
|
20
|
+
);
|
|
21
|
+
jest.spyOn(RequestTracker, "Default", "get").mockReturnValue(
|
|
22
|
+
new RequestTracker(responseCache),
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
jest.resetAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("when server-side", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should return null if no cached result", () => {
|
|
36
|
+
// Arrange
|
|
37
|
+
const fakeHandler = jest.fn();
|
|
38
|
+
|
|
39
|
+
// Act
|
|
40
|
+
const {
|
|
41
|
+
result: {current: result},
|
|
42
|
+
} = serverRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
43
|
+
|
|
44
|
+
// Assert
|
|
45
|
+
expect(result).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should not directly request fulfillment", () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
const fakeHandler = jest.fn();
|
|
51
|
+
const fulfillRequestSpy = jest.spyOn(
|
|
52
|
+
RequestFulfillment.Default,
|
|
53
|
+
"fulfill",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
serverRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(fulfillRequestSpy).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should track the request", () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const fakeHandler = jest.fn();
|
|
66
|
+
const trackDataRequestSpy = jest.spyOn(
|
|
67
|
+
RequestTracker.Default,
|
|
68
|
+
"trackDataRequest",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Act
|
|
72
|
+
serverRenderHook(() => useServerEffect("ID", fakeHandler), {
|
|
73
|
+
wrapper: TrackData,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(trackDataRequestSpy).toHaveBeenCalledWith(
|
|
78
|
+
"ID",
|
|
79
|
+
fakeHandler,
|
|
80
|
+
true,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should return data cached result", () => {
|
|
85
|
+
// Arrange
|
|
86
|
+
const fakeHandler = jest.fn();
|
|
87
|
+
jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
|
|
88
|
+
data: "DATA",
|
|
89
|
+
error: null,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const {
|
|
94
|
+
result: {current: result},
|
|
95
|
+
} = serverRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
96
|
+
|
|
97
|
+
// Assert
|
|
98
|
+
expect(result).toEqual({data: "DATA", error: null});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return error cached result", () => {
|
|
102
|
+
// Arrange
|
|
103
|
+
const fakeHandler = jest.fn();
|
|
104
|
+
jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
|
|
105
|
+
data: null,
|
|
106
|
+
error: "ERROR",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Act
|
|
110
|
+
const {
|
|
111
|
+
result: {current: result},
|
|
112
|
+
} = serverRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
113
|
+
|
|
114
|
+
// Assert
|
|
115
|
+
expect(result).toEqual({
|
|
116
|
+
data: null,
|
|
117
|
+
error: "ERROR",
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("when client-side", () => {
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
jest.spyOn(Server, "isServerSide").mockReturnValue(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should return null if no cached result", () => {
|
|
128
|
+
// Arrange
|
|
129
|
+
const fakeHandler = jest.fn();
|
|
130
|
+
|
|
131
|
+
// Act
|
|
132
|
+
const {
|
|
133
|
+
result: {current: result},
|
|
134
|
+
} = clientRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
135
|
+
|
|
136
|
+
// Assert
|
|
137
|
+
expect(result).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should return data cached result", () => {
|
|
141
|
+
// Arrange
|
|
142
|
+
const fakeHandler = jest.fn();
|
|
143
|
+
jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
|
|
144
|
+
data: "DATA",
|
|
145
|
+
error: null,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Act
|
|
149
|
+
const {
|
|
150
|
+
result: {current: result},
|
|
151
|
+
} = clientRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(result).toEqual({data: "DATA", error: null});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should return error cached result", () => {
|
|
158
|
+
// Arrange
|
|
159
|
+
const fakeHandler = jest.fn();
|
|
160
|
+
jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
|
|
161
|
+
data: null,
|
|
162
|
+
error: "ERROR",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
const {
|
|
167
|
+
result: {current: result},
|
|
168
|
+
} = clientRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
169
|
+
|
|
170
|
+
// Assert
|
|
171
|
+
expect(result).toEqual({
|
|
172
|
+
data: null,
|
|
173
|
+
error: "ERROR",
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should not track the request", () => {
|
|
178
|
+
// Arrange
|
|
179
|
+
const fakeHandler = jest.fn().mockReturnValue(
|
|
180
|
+
new Promise(() => {
|
|
181
|
+
/*prevent act() warning*/
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
const trackDataRequestSpy = jest.spyOn(
|
|
185
|
+
RequestTracker.Default,
|
|
186
|
+
"trackDataRequest",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Act
|
|
190
|
+
clientRenderHook(() => useServerEffect("ID", fakeHandler), {
|
|
191
|
+
wrapper: TrackData,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Assert
|
|
195
|
+
expect(trackDataRequestSpy).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should not request fulfillment", () => {
|
|
199
|
+
// Arrange
|
|
200
|
+
const fakeHandler = jest.fn().mockReturnValue(
|
|
201
|
+
new Promise(() => {
|
|
202
|
+
/*prevent act() warning*/
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
const fulfillRequestSpy = jest.spyOn(
|
|
206
|
+
RequestFulfillment.Default,
|
|
207
|
+
"fulfill",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Act
|
|
211
|
+
clientRenderHook(() => useServerEffect("ID", fakeHandler));
|
|
212
|
+
|
|
213
|
+
// Assert
|
|
214
|
+
expect(fulfillRequestSpy).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {renderHook as clientRenderHook} from "@testing-library/react-hooks";
|
|
3
|
+
|
|
4
|
+
import {useSharedCache, clearSharedCache} from "../use-shared-cache.js";
|
|
5
|
+
|
|
6
|
+
describe("#useSharedCache", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
clearSharedCache();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it.each`
|
|
12
|
+
id
|
|
13
|
+
${null}
|
|
14
|
+
${""}
|
|
15
|
+
${5}
|
|
16
|
+
${() => "BOO"}
|
|
17
|
+
`("should throw if the id is $id", ({id}) => {
|
|
18
|
+
// Arrange
|
|
19
|
+
|
|
20
|
+
// Act
|
|
21
|
+
const {result} = clientRenderHook(() => useSharedCache(id, "scope"));
|
|
22
|
+
|
|
23
|
+
// Assert
|
|
24
|
+
expect(result.error).toMatchSnapshot();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it.each`
|
|
28
|
+
scope
|
|
29
|
+
${null}
|
|
30
|
+
${""}
|
|
31
|
+
${5}
|
|
32
|
+
${() => "BOO"}
|
|
33
|
+
`("should throw if the scope is $scope", ({scope}) => {
|
|
34
|
+
// Arrange
|
|
35
|
+
|
|
36
|
+
// Act
|
|
37
|
+
const {result} = clientRenderHook(() => useSharedCache("id", scope));
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(result.error).toMatchSnapshot();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should return a tuple of two items", () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
|
|
46
|
+
// Act
|
|
47
|
+
const {
|
|
48
|
+
result: {current: result},
|
|
49
|
+
} = clientRenderHook(() => useSharedCache("id", "scope"));
|
|
50
|
+
|
|
51
|
+
// Assert
|
|
52
|
+
expect(result).toBeArrayOfSize(2);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("tuple[0] - currentValue", () => {
|
|
56
|
+
it("should be null if nothing is cached", () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
const {
|
|
61
|
+
result: {current: result},
|
|
62
|
+
} = clientRenderHook(() => useSharedCache("id", "scope"));
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(result[0]).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should match initialValue when provided as a non-function", () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
|
|
71
|
+
// Act
|
|
72
|
+
const {
|
|
73
|
+
result: {current: result},
|
|
74
|
+
} = clientRenderHook(() =>
|
|
75
|
+
useSharedCache("id", "scope", "INITIAL VALUE"),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result[0]).toBe("INITIAL VALUE");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should match the return of initialValue when provided as non-function", () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
|
|
85
|
+
// Act
|
|
86
|
+
const {
|
|
87
|
+
result: {current: result},
|
|
88
|
+
} = clientRenderHook(() =>
|
|
89
|
+
useSharedCache("id", "scope", () => "INITIAL VALUE"),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Assert
|
|
93
|
+
expect(result[0]).toBe("INITIAL VALUE");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("tuple[1] - setValue", () => {
|
|
98
|
+
it("should be a function", () => {
|
|
99
|
+
// Arrange
|
|
100
|
+
|
|
101
|
+
// Act
|
|
102
|
+
const {
|
|
103
|
+
result: {current: result},
|
|
104
|
+
} = clientRenderHook(() => useSharedCache("id", "scope"));
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result[1]).toBeFunction();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should be the same function if the id and scope remain the same", () => {
|
|
111
|
+
// Arrange
|
|
112
|
+
const wrapper = clientRenderHook(
|
|
113
|
+
({id, scope}) => useSharedCache(id, scope),
|
|
114
|
+
{initialProps: {id: "id", scope: "scope"}},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Act
|
|
118
|
+
wrapper.rerender({
|
|
119
|
+
id: "id",
|
|
120
|
+
scope: "scope",
|
|
121
|
+
});
|
|
122
|
+
const result1 = wrapper.result.all[wrapper.result.all.length - 2];
|
|
123
|
+
const result2 = wrapper.result.current;
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
// $FlowIgnore[prop-missing]
|
|
127
|
+
expect(result1[1]).toBe(result2[1]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should be a new function if the id changes", () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const wrapper = clientRenderHook(
|
|
133
|
+
({id}) => useSharedCache(id, "scope"),
|
|
134
|
+
{
|
|
135
|
+
initialProps: {id: "id"},
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
wrapper.rerender({id: "new-id"});
|
|
141
|
+
const result1 = wrapper.result.all[wrapper.result.all.length - 2];
|
|
142
|
+
const result2 = wrapper.result.current;
|
|
143
|
+
|
|
144
|
+
// Assert
|
|
145
|
+
// $FlowIgnore[prop-missing]
|
|
146
|
+
expect(result1[1]).not.toBe(result2[1]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should be a new function if the scope changes", () => {
|
|
150
|
+
// Arrange
|
|
151
|
+
const wrapper = clientRenderHook(
|
|
152
|
+
({scope}) => useSharedCache("id", scope),
|
|
153
|
+
{
|
|
154
|
+
initialProps: {scope: "scope"},
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Act
|
|
159
|
+
wrapper.rerender({scope: "new-scope"});
|
|
160
|
+
const result1 = wrapper.result.all[wrapper.result.all.length - 2];
|
|
161
|
+
const result2 = wrapper.result.current;
|
|
162
|
+
|
|
163
|
+
// Assert
|
|
164
|
+
// $FlowIgnore[prop-missing]
|
|
165
|
+
expect(result1[1]).not.toBe(result2[1]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should set the value in the cache", () => {
|
|
169
|
+
// Arrange
|
|
170
|
+
const wrapper = clientRenderHook(() =>
|
|
171
|
+
useSharedCache("id", "scope"),
|
|
172
|
+
);
|
|
173
|
+
const setValue = wrapper.result.current[1];
|
|
174
|
+
|
|
175
|
+
// Act
|
|
176
|
+
setValue("CACHED_VALUE");
|
|
177
|
+
// Rerender so the hook retrieves this new value.
|
|
178
|
+
wrapper.rerender();
|
|
179
|
+
const result = wrapper.result.current[0];
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(result).toBe("CACHED_VALUE");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it.each`
|
|
186
|
+
value
|
|
187
|
+
${undefined}
|
|
188
|
+
${null}
|
|
189
|
+
`("should purge the value from the cache if $value", ({value}) => {
|
|
190
|
+
// Arrange
|
|
191
|
+
const wrapper = clientRenderHook(() =>
|
|
192
|
+
useSharedCache("id", "scope"),
|
|
193
|
+
);
|
|
194
|
+
const setValue = wrapper.result.current[1];
|
|
195
|
+
setValue("CACHED_VALUE");
|
|
196
|
+
|
|
197
|
+
// Act
|
|
198
|
+
// Rerender so the result has the cached value.
|
|
199
|
+
wrapper.rerender();
|
|
200
|
+
setValue(value);
|
|
201
|
+
// Rerender so the hook retrieves this new value.
|
|
202
|
+
wrapper.rerender();
|
|
203
|
+
const result = wrapper.result.current[0];
|
|
204
|
+
|
|
205
|
+
// Assert
|
|
206
|
+
expect(result).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should share cache across all uses", () => {
|
|
211
|
+
// Arrange
|
|
212
|
+
const hook1 = clientRenderHook(() => useSharedCache("id", "scope"));
|
|
213
|
+
const hook2 = clientRenderHook(() => useSharedCache("id", "scope"));
|
|
214
|
+
hook1.result.current[1]("VALUE_1");
|
|
215
|
+
|
|
216
|
+
// Act
|
|
217
|
+
hook2.rerender();
|
|
218
|
+
const result = hook2.result.current[0];
|
|
219
|
+
|
|
220
|
+
// Assert
|
|
221
|
+
expect(result).toBe("VALUE_1");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it.each`
|
|
225
|
+
id
|
|
226
|
+
${"id1"}
|
|
227
|
+
${"id2"}
|
|
228
|
+
`("should not share cache if scope is different", ({id}) => {
|
|
229
|
+
// Arrange
|
|
230
|
+
const hook1 = clientRenderHook(() => useSharedCache("id1", "scope1"));
|
|
231
|
+
const hook2 = clientRenderHook(() => useSharedCache(id, "scope2"));
|
|
232
|
+
hook1.result.current[1]("VALUE_1");
|
|
233
|
+
|
|
234
|
+
// Act
|
|
235
|
+
hook2.rerender();
|
|
236
|
+
const result = hook2.result.current[0];
|
|
237
|
+
|
|
238
|
+
// Assert
|
|
239
|
+
expect(result).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it.each`
|
|
243
|
+
scope
|
|
244
|
+
${"scope1"}
|
|
245
|
+
${"scope2"}
|
|
246
|
+
`("should not share cache if id is different", ({scope}) => {
|
|
247
|
+
// Arrange
|
|
248
|
+
const hook1 = clientRenderHook(() => useSharedCache("id1", "scope1"));
|
|
249
|
+
const hook2 = clientRenderHook(() => useSharedCache("id2", scope));
|
|
250
|
+
hook1.result.current[1]("VALUE_1");
|
|
251
|
+
|
|
252
|
+
// Act
|
|
253
|
+
hook2.rerender();
|
|
254
|
+
const result = hook2.result.current[0];
|
|
255
|
+
|
|
256
|
+
// Assert
|
|
257
|
+
expect(result).toBeNull();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("#clearSharedCache", () => {
|
|
262
|
+
beforeEach(() => {
|
|
263
|
+
clearSharedCache();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should clear the entire cache if no scope given", () => {
|
|
267
|
+
// Arrange
|
|
268
|
+
const hook1 = clientRenderHook(() => useSharedCache("id1", "scope1"));
|
|
269
|
+
const hook2 = clientRenderHook(() => useSharedCache("id2", "scope2"));
|
|
270
|
+
hook1.result.current[1]("VALUE_1");
|
|
271
|
+
hook2.result.current[1]("VALUE_2");
|
|
272
|
+
// Make sure both hook results include the updated value.
|
|
273
|
+
hook1.rerender();
|
|
274
|
+
hook2.rerender();
|
|
275
|
+
|
|
276
|
+
// Act
|
|
277
|
+
clearSharedCache();
|
|
278
|
+
// Make sure we refresh the hook results.
|
|
279
|
+
hook1.rerender();
|
|
280
|
+
hook2.rerender();
|
|
281
|
+
|
|
282
|
+
// Assert
|
|
283
|
+
expect(hook1.result.current[0]).toBeNull();
|
|
284
|
+
expect(hook2.result.current[0]).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should clear the given scope only", () => {
|
|
288
|
+
// Arrange
|
|
289
|
+
const hook1 = clientRenderHook(() => useSharedCache("id1", "scope1"));
|
|
290
|
+
const hook2 = clientRenderHook(() => useSharedCache("id2", "scope2"));
|
|
291
|
+
hook1.result.current[1]("VALUE_1");
|
|
292
|
+
hook2.result.current[1]("VALUE_2");
|
|
293
|
+
// Make sure both hook results include the updated value.
|
|
294
|
+
hook1.rerender();
|
|
295
|
+
hook2.rerender();
|
|
296
|
+
|
|
297
|
+
// Act
|
|
298
|
+
clearSharedCache("scope2");
|
|
299
|
+
// Make sure we refresh the hook results.
|
|
300
|
+
hook1.rerender();
|
|
301
|
+
hook2.rerender();
|
|
302
|
+
|
|
303
|
+
// Assert
|
|
304
|
+
expect(hook1.result.current[0]).toBe("VALUE_1");
|
|
305
|
+
expect(hook2.result.current[0]).toBeNull();
|
|
306
|
+
});
|
|
307
|
+
});
|