@assistant-ui/tap 0.3.6 → 0.4.2
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/README.md +24 -23
- package/dist/core/ResourceFiber.d.ts +1 -1
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +15 -8
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/commit.d.ts +1 -1
- package/dist/core/commit.d.ts.map +1 -1
- package/dist/core/commit.js +40 -50
- package/dist/core/commit.js.map +1 -1
- package/dist/core/context.d.ts +2 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +2 -2
- package/dist/core/context.js.map +1 -1
- package/dist/core/createResource.d.ts +3 -2
- package/dist/core/createResource.d.ts.map +1 -1
- package/dist/core/createResource.js +48 -22
- package/dist/core/createResource.js.map +1 -1
- package/dist/core/env.d.ts +2 -0
- package/dist/core/env.d.ts.map +1 -0
- package/dist/core/env.js +3 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/execution-context.d.ts +1 -0
- package/dist/core/execution-context.d.ts.map +1 -1
- package/dist/core/execution-context.js +8 -0
- package/dist/core/execution-context.js.map +1 -1
- package/dist/core/resource.d.ts +4 -3
- package/dist/core/resource.d.ts.map +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +4 -1
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +22 -21
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/withKey.d.ts +3 -0
- package/dist/core/withKey.d.ts.map +1 -0
- package/dist/core/withKey.js +4 -0
- package/dist/core/withKey.js.map +1 -0
- package/dist/hooks/tap-callback.d.ts.map +1 -1
- package/dist/hooks/tap-callback.js +1 -0
- package/dist/hooks/tap-callback.js.map +1 -1
- package/dist/hooks/tap-const.d.ts +2 -0
- package/dist/hooks/tap-const.d.ts.map +1 -0
- package/dist/hooks/tap-const.js +6 -0
- package/dist/hooks/tap-const.js.map +1 -0
- package/dist/hooks/tap-effect-event.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.js +11 -0
- package/dist/hooks/tap-effect-event.js.map +1 -1
- package/dist/hooks/tap-effect.d.ts.map +1 -1
- package/dist/hooks/tap-effect.js +46 -31
- package/dist/hooks/tap-effect.js.map +1 -1
- package/dist/hooks/tap-inline-resource.d.ts +2 -2
- package/dist/hooks/tap-inline-resource.d.ts.map +1 -1
- package/dist/hooks/tap-memo.d.ts.map +1 -1
- package/dist/hooks/tap-memo.js +9 -1
- package/dist/hooks/tap-memo.js.map +1 -1
- package/dist/hooks/tap-resource.d.ts +3 -3
- package/dist/hooks/tap-resource.d.ts.map +1 -1
- package/dist/hooks/tap-resource.js +17 -9
- package/dist/hooks/tap-resource.js.map +1 -1
- package/dist/hooks/tap-resources.d.ts +2 -10
- package/dist/hooks/tap-resources.d.ts.map +1 -1
- package/dist/hooks/tap-resources.js +74 -43
- package/dist/hooks/tap-resources.js.map +1 -1
- package/dist/hooks/tap-state.d.ts.map +1 -1
- package/dist/hooks/tap-state.js +37 -24
- package/dist/hooks/tap-state.js.map +1 -1
- package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -0
- package/dist/hooks/utils/depsShallowEqual.js.map +1 -0
- package/dist/hooks/utils/tapHook.d.ts +6 -0
- package/dist/hooks/utils/tapHook.d.ts.map +1 -0
- package/dist/hooks/utils/tapHook.js +24 -0
- package/dist/hooks/utils/tapHook.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -2
- package/dist/react/use-resource.d.ts.map +1 -1
- package/dist/react/use-resource.js +24 -10
- package/dist/react/use-resource.js.map +1 -1
- package/package.json +10 -3
- package/src/__tests__/basic/resourceHandle.test.ts +4 -4
- package/src/__tests__/basic/tapEffect.basic.test.ts +3 -2
- package/src/__tests__/basic/tapResources.basic.test.ts +84 -64
- package/src/__tests__/basic/tapState.basic.test.ts +8 -8
- package/src/__tests__/errors/errors.effect-errors.test.ts +8 -3
- package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +3 -2
- package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +2 -2
- package/src/__tests__/react/concurrent-mode.test.tsx +243 -0
- package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +709 -0
- package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +392 -0
- package/src/__tests__/strictmode/strictmode.test.ts +274 -0
- package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +723 -0
- package/src/__tests__/test-utils.ts +8 -6
- package/src/core/ResourceFiber.ts +21 -11
- package/src/core/commit.ts +37 -57
- package/src/core/context.ts +2 -2
- package/src/core/createResource.ts +64 -25
- package/src/core/env.ts +3 -0
- package/src/core/execution-context.ts +9 -0
- package/src/core/resource.ts +9 -3
- package/src/core/scheduler.ts +4 -1
- package/src/core/types.ts +25 -26
- package/src/core/withKey.ts +8 -0
- package/src/hooks/tap-callback.ts +1 -0
- package/src/hooks/tap-const.ts +6 -0
- package/src/hooks/tap-effect-event.ts +15 -0
- package/src/hooks/tap-effect.ts +51 -38
- package/src/hooks/tap-inline-resource.ts +2 -2
- package/src/hooks/tap-memo.ts +10 -1
- package/src/hooks/tap-resource.ts +24 -20
- package/src/hooks/tap-resources.ts +86 -63
- package/src/hooks/tap-state.ts +49 -26
- package/src/hooks/utils/tapHook.ts +35 -0
- package/src/index.ts +8 -3
- package/src/react/use-resource.ts +27 -16
- package/dist/hooks/depsShallowEqual.d.ts.map +0 -1
- package/dist/hooks/depsShallowEqual.js.map +0 -1
- /package/dist/hooks/{depsShallowEqual.d.ts → utils/depsShallowEqual.d.ts} +0 -0
- /package/dist/hooks/{depsShallowEqual.js → utils/depsShallowEqual.js} +0 -0
- /package/src/hooks/{depsShallowEqual.ts → utils/depsShallowEqual.ts} +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests to verify when React strict mode causes double-rendering
|
|
3
|
+
* for different sources of setState calls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import { render, fireEvent, waitFor } from "@testing-library/react";
|
|
8
|
+
import { StrictMode, useState, useEffect } from "react";
|
|
9
|
+
|
|
10
|
+
describe("React Strict Mode - Rerender Sources", () => {
|
|
11
|
+
describe("Source 1: Initial render", () => {
|
|
12
|
+
it("should double-render on initial mount", () => {
|
|
13
|
+
const events: string[] = [];
|
|
14
|
+
|
|
15
|
+
function TestComponent() {
|
|
16
|
+
const [count] = useState(0);
|
|
17
|
+
events.push(`render count=${count}`);
|
|
18
|
+
return <div>{count}</div>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render(
|
|
22
|
+
<StrictMode>
|
|
23
|
+
<TestComponent />
|
|
24
|
+
</StrictMode>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("Source 2: setState in render", () => {
|
|
32
|
+
it("should handle setState during render", () => {
|
|
33
|
+
const events: string[] = [];
|
|
34
|
+
|
|
35
|
+
function TestComponent() {
|
|
36
|
+
const [count, setCount] = useState(0);
|
|
37
|
+
events.push(`render count=${count}`);
|
|
38
|
+
|
|
39
|
+
// setState during render (this pattern sets state once during initial render)
|
|
40
|
+
if (count === 0) {
|
|
41
|
+
setCount(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return <div>{count}</div>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render(
|
|
48
|
+
<StrictMode>
|
|
49
|
+
<TestComponent />
|
|
50
|
+
</StrictMode>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// ACTUAL: setState during render only renders once with old value,
|
|
54
|
+
// then double-renders with new value
|
|
55
|
+
expect(events).toEqual([
|
|
56
|
+
"render count=0",
|
|
57
|
+
"render count=1",
|
|
58
|
+
"render count=1",
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("Source 3: setState in useEffect", () => {
|
|
64
|
+
it("should double-render after setState in useEffect", () => {
|
|
65
|
+
const events: string[] = [];
|
|
66
|
+
|
|
67
|
+
function TestComponent() {
|
|
68
|
+
const [count, setCount] = useState(0);
|
|
69
|
+
events.push(`render count=${count}`);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
events.push(`effect count=${count}`);
|
|
73
|
+
if (count === 0) {
|
|
74
|
+
setCount(1);
|
|
75
|
+
}
|
|
76
|
+
return () => {
|
|
77
|
+
events.push(`cleanup count=${count}`);
|
|
78
|
+
};
|
|
79
|
+
}, [count]);
|
|
80
|
+
|
|
81
|
+
return <div>{count}</div>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
render(
|
|
85
|
+
<StrictMode>
|
|
86
|
+
<TestComponent />
|
|
87
|
+
</StrictMode>,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(events).toEqual([
|
|
91
|
+
"render count=0",
|
|
92
|
+
"render count=0",
|
|
93
|
+
"effect count=0",
|
|
94
|
+
"cleanup count=0",
|
|
95
|
+
"effect count=0",
|
|
96
|
+
"render count=1",
|
|
97
|
+
"render count=1",
|
|
98
|
+
"cleanup count=0",
|
|
99
|
+
"effect count=1",
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("Source 4: setState in event handler", () => {
|
|
105
|
+
it("should ALSO double-render after setState in event handler (React 19)", () => {
|
|
106
|
+
const events: string[] = [];
|
|
107
|
+
|
|
108
|
+
function TestComponent() {
|
|
109
|
+
const [count, setCount] = useState(0);
|
|
110
|
+
events.push(`render count=${count}`);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<button
|
|
114
|
+
onClick={() => {
|
|
115
|
+
events.push("click");
|
|
116
|
+
setCount(count + 1);
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
{count}
|
|
120
|
+
</button>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { getByRole } = render(
|
|
125
|
+
<StrictMode>
|
|
126
|
+
<TestComponent />
|
|
127
|
+
</StrictMode>,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Initial render is double
|
|
131
|
+
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
132
|
+
|
|
133
|
+
events.length = 0; // Clear events
|
|
134
|
+
|
|
135
|
+
// Click the button
|
|
136
|
+
fireEvent.click(getByRole("button"));
|
|
137
|
+
|
|
138
|
+
// ACTUAL: In React 19 strict mode, ALL renders are doubled!
|
|
139
|
+
// Even renders triggered by event handlers!
|
|
140
|
+
expect(events).toEqual(["click", "render count=1", "render count=1"]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should double-render on ALL event handler clicks (React 19)", () => {
|
|
144
|
+
const events: string[] = [];
|
|
145
|
+
|
|
146
|
+
function TestComponent() {
|
|
147
|
+
const [count, setCount] = useState(0);
|
|
148
|
+
events.push(`render count=${count}`);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<button
|
|
152
|
+
onClick={() => {
|
|
153
|
+
events.push("click");
|
|
154
|
+
setCount((c) => c + 1);
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{count}
|
|
158
|
+
</button>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { getByRole } = render(
|
|
163
|
+
<StrictMode>
|
|
164
|
+
<TestComponent />
|
|
165
|
+
</StrictMode>,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
events.length = 0; // Clear initial renders
|
|
169
|
+
|
|
170
|
+
// Multiple clicks
|
|
171
|
+
fireEvent.click(getByRole("button"));
|
|
172
|
+
fireEvent.click(getByRole("button"));
|
|
173
|
+
fireEvent.click(getByRole("button"));
|
|
174
|
+
|
|
175
|
+
// ACTUAL: Each click causes DOUBLE render in React 19 strict mode
|
|
176
|
+
expect(events).toEqual([
|
|
177
|
+
"click",
|
|
178
|
+
"render count=1",
|
|
179
|
+
"render count=1",
|
|
180
|
+
"click",
|
|
181
|
+
"render count=2",
|
|
182
|
+
"render count=2",
|
|
183
|
+
"click",
|
|
184
|
+
"render count=3",
|
|
185
|
+
"render count=3",
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("Source 5: setState in setTimeout", () => {
|
|
191
|
+
it("should double-render AND double-call setTimeout callback (React 19)", async () => {
|
|
192
|
+
const events: string[] = [];
|
|
193
|
+
|
|
194
|
+
function TestComponent() {
|
|
195
|
+
const [count, setCount] = useState(0);
|
|
196
|
+
events.push(`render count=${count}`);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (count === 0) {
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
events.push("setTimeout");
|
|
202
|
+
setCount(1);
|
|
203
|
+
}, 10);
|
|
204
|
+
}
|
|
205
|
+
}, [count]);
|
|
206
|
+
|
|
207
|
+
return <div>{count}</div>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
render(
|
|
211
|
+
<StrictMode>
|
|
212
|
+
<TestComponent />
|
|
213
|
+
</StrictMode>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Wait for setTimeout
|
|
217
|
+
await waitFor(() => {
|
|
218
|
+
expect(events).toContain("setTimeout");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ACTUAL: setTimeout callback runs TWICE and renders are DOUBLED
|
|
222
|
+
expect(events).toEqual([
|
|
223
|
+
"render count=0",
|
|
224
|
+
"render count=0",
|
|
225
|
+
"setTimeout",
|
|
226
|
+
"setTimeout",
|
|
227
|
+
"render count=1",
|
|
228
|
+
"render count=1",
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("Source 6: setState in Promise/async", () => {
|
|
234
|
+
it("should double-render AND double-call Promise callback (React 19)", async () => {
|
|
235
|
+
const events: string[] = [];
|
|
236
|
+
|
|
237
|
+
function TestComponent() {
|
|
238
|
+
const [count, setCount] = useState(0);
|
|
239
|
+
events.push(`render count=${count}`);
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (count === 0) {
|
|
243
|
+
Promise.resolve().then(() => {
|
|
244
|
+
events.push("promise");
|
|
245
|
+
setCount(1);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}, [count]);
|
|
249
|
+
|
|
250
|
+
return <div>{count}</div>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
render(
|
|
254
|
+
<StrictMode>
|
|
255
|
+
<TestComponent />
|
|
256
|
+
</StrictMode>,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Wait for promise
|
|
260
|
+
await waitFor(() => {
|
|
261
|
+
expect(events).toContain("promise");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ACTUAL: Promise callback runs TWICE and renders are DOUBLED
|
|
265
|
+
expect(events).toEqual([
|
|
266
|
+
"render count=0",
|
|
267
|
+
"render count=0",
|
|
268
|
+
"promise",
|
|
269
|
+
"promise",
|
|
270
|
+
"render count=1",
|
|
271
|
+
"render count=1",
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("Source 7: Multiple setState calls", () => {
|
|
277
|
+
it("should batch multiple setState calls in event handlers (single render)", () => {
|
|
278
|
+
const events: string[] = [];
|
|
279
|
+
|
|
280
|
+
function TestComponent() {
|
|
281
|
+
const [count1, setCount1] = useState(0);
|
|
282
|
+
const [count2, setCount2] = useState(0);
|
|
283
|
+
events.push(`render count1=${count1} count2=${count2}`);
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<button
|
|
287
|
+
onClick={() => {
|
|
288
|
+
events.push("click");
|
|
289
|
+
setCount1(1);
|
|
290
|
+
setCount2(2);
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
Click
|
|
294
|
+
</button>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const { getByRole } = render(
|
|
299
|
+
<StrictMode>
|
|
300
|
+
<TestComponent />
|
|
301
|
+
</StrictMode>,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
events.length = 0; // Clear initial renders
|
|
305
|
+
|
|
306
|
+
fireEvent.click(getByRole("button"));
|
|
307
|
+
|
|
308
|
+
// ACTUAL: Both setState calls batched, but render is DOUBLED
|
|
309
|
+
expect(events).toEqual([
|
|
310
|
+
"click",
|
|
311
|
+
"render count1=1 count2=2",
|
|
312
|
+
"render count1=1 count2=2",
|
|
313
|
+
]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should batch multiple setState calls in useEffect (single double-render)", () => {
|
|
317
|
+
const events: string[] = [];
|
|
318
|
+
|
|
319
|
+
function TestComponent() {
|
|
320
|
+
const [count1, setCount1] = useState(0);
|
|
321
|
+
const [count2, setCount2] = useState(0);
|
|
322
|
+
events.push(`render count1=${count1} count2=${count2}`);
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
if (count1 === 0 && count2 === 0) {
|
|
326
|
+
setCount1(1);
|
|
327
|
+
setCount2(2);
|
|
328
|
+
}
|
|
329
|
+
}, [count1, count2]);
|
|
330
|
+
|
|
331
|
+
return <div>Test</div>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
render(
|
|
335
|
+
<StrictMode>
|
|
336
|
+
<TestComponent />
|
|
337
|
+
</StrictMode>,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Initial double-render, then batched setState causes another double-render
|
|
341
|
+
expect(events).toEqual([
|
|
342
|
+
"render count1=0 count2=0",
|
|
343
|
+
"render count1=0 count2=0",
|
|
344
|
+
"render count1=1 count2=2",
|
|
345
|
+
"render count1=1 count2=2",
|
|
346
|
+
]);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("Source 8: setState in useLayoutEffect", () => {
|
|
351
|
+
it("should double-render after setState in useLayoutEffect", () => {
|
|
352
|
+
const events: string[] = [];
|
|
353
|
+
const { useLayoutEffect } = require("react");
|
|
354
|
+
|
|
355
|
+
function TestComponent() {
|
|
356
|
+
const [count, setCount] = useState(0);
|
|
357
|
+
events.push(`render count=${count}`);
|
|
358
|
+
|
|
359
|
+
useLayoutEffect(() => {
|
|
360
|
+
events.push(`layoutEffect count=${count}`);
|
|
361
|
+
if (count === 0) {
|
|
362
|
+
setCount(1);
|
|
363
|
+
}
|
|
364
|
+
return () => {
|
|
365
|
+
events.push(`layoutCleanup count=${count}`);
|
|
366
|
+
};
|
|
367
|
+
}, [count]);
|
|
368
|
+
|
|
369
|
+
return <div>{count}</div>;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
render(
|
|
373
|
+
<StrictMode>
|
|
374
|
+
<TestComponent />
|
|
375
|
+
</StrictMode>,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// useLayoutEffect runs synchronously after render, before paint
|
|
379
|
+
expect(events).toEqual([
|
|
380
|
+
"render count=0",
|
|
381
|
+
"render count=0",
|
|
382
|
+
"layoutEffect count=0",
|
|
383
|
+
"layoutCleanup count=0",
|
|
384
|
+
"layoutEffect count=0",
|
|
385
|
+
"render count=1",
|
|
386
|
+
"render count=1",
|
|
387
|
+
"layoutCleanup count=0",
|
|
388
|
+
"layoutEffect count=1",
|
|
389
|
+
]);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resource } from "../../core/resource";
|
|
3
|
+
import { isDevelopment } from "../../core/env";
|
|
4
|
+
import { tapRef } from "../../hooks/tap-ref";
|
|
5
|
+
import { tapState } from "../../hooks/tap-state";
|
|
6
|
+
import { tapEffect } from "../../hooks/tap-effect";
|
|
7
|
+
import { tapResource } from "../../hooks/tap-resource";
|
|
8
|
+
import { createResource } from "../../core/createResource";
|
|
9
|
+
import { withKey } from "../../core/withKey";
|
|
10
|
+
|
|
11
|
+
describe("Strict Mode", () => {
|
|
12
|
+
it("should be in development", () => {
|
|
13
|
+
expect(isDevelopment).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should double-render on first render", () => {
|
|
17
|
+
let renderCount = 0;
|
|
18
|
+
|
|
19
|
+
const TestResource = resource(() => {
|
|
20
|
+
renderCount++;
|
|
21
|
+
return { renderCount };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const handle = createResource(TestResource(), { devStrictMode: true });
|
|
25
|
+
const output = handle.getValue();
|
|
26
|
+
|
|
27
|
+
expect(renderCount).toBe(2);
|
|
28
|
+
expect(output.renderCount).toBe(2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should double-call hook fns", () => {
|
|
32
|
+
let renderCount = 0;
|
|
33
|
+
|
|
34
|
+
const TestResource = resource(() => {
|
|
35
|
+
const ref = tapRef(0);
|
|
36
|
+
const [count] = tapState(() => {
|
|
37
|
+
renderCount++;
|
|
38
|
+
return ++ref.current;
|
|
39
|
+
});
|
|
40
|
+
const [count2] = tapState(() => {
|
|
41
|
+
renderCount++;
|
|
42
|
+
return ++ref.current;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(count).toBe(1);
|
|
46
|
+
expect(count2).toBe(3);
|
|
47
|
+
expect(ref.current).toBe(4);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
createResource(TestResource(), { devStrictMode: true });
|
|
51
|
+
|
|
52
|
+
expect(renderCount).toBe(4);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should double-commit effects", () => {
|
|
56
|
+
const events: string[] = [];
|
|
57
|
+
const TestResource = resource(() => {
|
|
58
|
+
const ref = tapRef(0);
|
|
59
|
+
ref.current++;
|
|
60
|
+
const count = ref.current;
|
|
61
|
+
|
|
62
|
+
tapEffect(() => {
|
|
63
|
+
events.push("mount-1");
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
events.push("unmount-1");
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
tapEffect(() => {
|
|
71
|
+
events.push("mount-2");
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
events.push("unmount-2");
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
tapEffect(() => {
|
|
79
|
+
expect(count).toBe(2);
|
|
80
|
+
|
|
81
|
+
events.push("mount-3");
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
events.push("unmount-3");
|
|
85
|
+
};
|
|
86
|
+
}, [count]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
createResource(TestResource(), { devStrictMode: true });
|
|
90
|
+
|
|
91
|
+
expect(events).toEqual([
|
|
92
|
+
"mount-1",
|
|
93
|
+
"mount-2",
|
|
94
|
+
"mount-3",
|
|
95
|
+
"unmount-1",
|
|
96
|
+
"unmount-2",
|
|
97
|
+
"unmount-3",
|
|
98
|
+
"mount-1",
|
|
99
|
+
"mount-2",
|
|
100
|
+
"mount-3",
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should double-render on child render", () => {
|
|
105
|
+
let renderCount = 0;
|
|
106
|
+
|
|
107
|
+
const TestChildResource = resource(() => {
|
|
108
|
+
renderCount++;
|
|
109
|
+
return { renderCount };
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const TestResource = resource(() => {
|
|
113
|
+
return tapResource(TestChildResource());
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const handle = createResource(TestResource(), { devStrictMode: true });
|
|
117
|
+
const output = handle.getValue();
|
|
118
|
+
|
|
119
|
+
expect(renderCount).toBe(2);
|
|
120
|
+
expect(output.renderCount).toBe(2);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should double-mount before handling state updates", () => {
|
|
124
|
+
const events: string[] = [];
|
|
125
|
+
const TestResource = resource(() => {
|
|
126
|
+
const [id, setId] = tapState(0);
|
|
127
|
+
events.push(`render-${id}`);
|
|
128
|
+
tapEffect(() => {
|
|
129
|
+
events.push(`mount-${id}`);
|
|
130
|
+
setId(1);
|
|
131
|
+
return () => {
|
|
132
|
+
events.push(`unmount-${id}`);
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
createResource(TestResource(), {
|
|
138
|
+
mount: true,
|
|
139
|
+
devStrictMode: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(events).toEqual([
|
|
143
|
+
"render-0",
|
|
144
|
+
"render-0",
|
|
145
|
+
"mount-0",
|
|
146
|
+
"unmount-0",
|
|
147
|
+
"mount-0",
|
|
148
|
+
"render-1",
|
|
149
|
+
"render-1",
|
|
150
|
+
"unmount-0",
|
|
151
|
+
"mount-1",
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should double-render on child render change", () => {
|
|
156
|
+
let renderCount = 0;
|
|
157
|
+
let fnCount = 0;
|
|
158
|
+
let mountCount = 0;
|
|
159
|
+
let unmountCount = 0;
|
|
160
|
+
|
|
161
|
+
const incrementRenderCount = () => {
|
|
162
|
+
renderCount++;
|
|
163
|
+
return renderCount;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const TestChildResource = resource(() => {
|
|
167
|
+
const [fnState] = tapState(() => {
|
|
168
|
+
fnCount++;
|
|
169
|
+
return fnCount;
|
|
170
|
+
});
|
|
171
|
+
const count = incrementRenderCount();
|
|
172
|
+
tapEffect(() => {
|
|
173
|
+
expect(fnState % 2).toBe(1);
|
|
174
|
+
expect(count).toBe(fnState + 1);
|
|
175
|
+
|
|
176
|
+
mountCount++;
|
|
177
|
+
return () => {
|
|
178
|
+
unmountCount++;
|
|
179
|
+
};
|
|
180
|
+
}, [fnState, count]);
|
|
181
|
+
return { renderCount, fnCount, fnState };
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const TestResource = resource(() => {
|
|
185
|
+
const [id, setId] = tapState(0);
|
|
186
|
+
tapEffect(() => {
|
|
187
|
+
setId(1);
|
|
188
|
+
});
|
|
189
|
+
return tapResource(withKey(id, TestChildResource()));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const handle = createResource(TestResource(), {
|
|
193
|
+
mount: true,
|
|
194
|
+
devStrictMode: true,
|
|
195
|
+
});
|
|
196
|
+
const output = handle.getValue();
|
|
197
|
+
|
|
198
|
+
expect(renderCount).toBe(4);
|
|
199
|
+
expect(fnCount).toBe(4);
|
|
200
|
+
expect(output.renderCount).toBe(4);
|
|
201
|
+
expect(output.fnCount).toBe(4);
|
|
202
|
+
expect(output.fnState).toBe(3);
|
|
203
|
+
expect(mountCount).toBe(4);
|
|
204
|
+
expect(unmountCount).toBe(3);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should double-render on child render change", () => {
|
|
208
|
+
let renderCount = 0;
|
|
209
|
+
const events: string[] = [];
|
|
210
|
+
const TestChildResource = resource(() => {
|
|
211
|
+
renderCount++;
|
|
212
|
+
events.push(`render-${renderCount}`);
|
|
213
|
+
|
|
214
|
+
tapState(() => {
|
|
215
|
+
return events.push(`fn-${renderCount}`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const count = renderCount;
|
|
219
|
+
tapEffect(() => {
|
|
220
|
+
events.push(`mount-${count}`);
|
|
221
|
+
return () => {
|
|
222
|
+
events.push(`unmount-${count}`);
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const TestResource = resource(() => {
|
|
228
|
+
const [id, setId] = tapState(0);
|
|
229
|
+
events.push(`outer-render-${id}`);
|
|
230
|
+
tapEffect(() => {
|
|
231
|
+
events.push(`outer-mount-${id}`);
|
|
232
|
+
setId(1);
|
|
233
|
+
|
|
234
|
+
return () => {
|
|
235
|
+
events.push(`outer-unmount-${id}`);
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
return tapResource(withKey(id, TestChildResource()));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
createResource(TestResource(), {
|
|
242
|
+
mount: true,
|
|
243
|
+
devStrictMode: true,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(events).toEqual([
|
|
247
|
+
"outer-render-0",
|
|
248
|
+
"render-1",
|
|
249
|
+
"fn-1",
|
|
250
|
+
"fn-1",
|
|
251
|
+
"outer-render-0",
|
|
252
|
+
"render-2",
|
|
253
|
+
"outer-mount-0",
|
|
254
|
+
"mount-2",
|
|
255
|
+
"outer-unmount-0",
|
|
256
|
+
"unmount-2",
|
|
257
|
+
"outer-mount-0",
|
|
258
|
+
"mount-2",
|
|
259
|
+
"outer-render-1",
|
|
260
|
+
"render-3",
|
|
261
|
+
"fn-3",
|
|
262
|
+
"fn-3",
|
|
263
|
+
"outer-render-1",
|
|
264
|
+
"render-4",
|
|
265
|
+
"outer-unmount-0",
|
|
266
|
+
"outer-mount-1",
|
|
267
|
+
"unmount-2",
|
|
268
|
+
"mount-4",
|
|
269
|
+
"unmount-4",
|
|
270
|
+
"mount-4",
|
|
271
|
+
]);
|
|
272
|
+
// expect(renderCount).toBe(4);
|
|
273
|
+
});
|
|
274
|
+
});
|