@assistant-ui/tap 0.4.5 → 0.5.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/README.md +20 -17
- package/dist/core/ResourceFiber.d.ts +2 -2
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +11 -9
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/createResourceRoot.d.ts +6 -0
- package/dist/core/createResourceRoot.d.ts.map +1 -0
- package/dist/core/createResourceRoot.js +32 -0
- package/dist/core/createResourceRoot.js.map +1 -0
- package/dist/core/helpers/callResourceFn.d.ts.map +1 -0
- package/dist/core/helpers/callResourceFn.js.map +1 -0
- package/dist/core/helpers/commit.d.ts +4 -0
- package/dist/core/helpers/commit.d.ts.map +1 -0
- package/dist/core/{commit.js → helpers/commit.js} +2 -2
- package/dist/core/helpers/commit.js.map +1 -0
- package/dist/core/helpers/env.d.ts.map +1 -0
- package/dist/core/helpers/env.js +3 -0
- package/dist/core/helpers/env.js.map +1 -0
- package/dist/core/{execution-context.d.ts → helpers/execution-context.d.ts} +1 -1
- package/dist/core/helpers/execution-context.d.ts.map +1 -0
- package/dist/core/helpers/execution-context.js.map +1 -0
- package/dist/core/helpers/root.d.ts +8 -0
- package/dist/core/helpers/root.d.ts.map +1 -0
- package/dist/core/helpers/root.js +52 -0
- package/dist/core/helpers/root.js.map +1 -0
- package/dist/core/resource.js +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +12 -1
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +25 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.js +3 -2
- package/dist/hooks/tap-effect-event.js.map +1 -1
- package/dist/hooks/tap-memo.d.ts.map +1 -1
- package/dist/hooks/tap-memo.js +16 -17
- package/dist/hooks/tap-memo.js.map +1 -1
- package/dist/hooks/tap-reducer.d.ts +7 -0
- package/dist/hooks/tap-reducer.d.ts.map +1 -0
- package/dist/hooks/tap-reducer.js +87 -0
- package/dist/hooks/tap-reducer.js.map +1 -0
- package/dist/hooks/tap-resource.js +9 -9
- package/dist/hooks/tap-resource.js.map +1 -1
- package/dist/hooks/tap-resources.d.ts.map +1 -1
- package/dist/hooks/tap-resources.js +11 -11
- 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 +6 -63
- package/dist/hooks/tap-state.js.map +1 -1
- package/dist/hooks/utils/tapHook.d.ts +1 -1
- package/dist/hooks/utils/tapHook.d.ts.map +1 -1
- package/dist/hooks/utils/tapHook.js +2 -2
- package/dist/hooks/utils/tapHook.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -1
- package/dist/react/use-resource.d.ts.map +1 -1
- package/dist/react/use-resource.js +14 -8
- package/dist/react/use-resource.js.map +1 -1
- package/dist/{tapSubscribableResource.d.ts → tapResourceRoot.d.ts} +3 -3
- package/dist/tapResourceRoot.d.ts.map +1 -0
- package/dist/tapResourceRoot.js +80 -0
- package/dist/tapResourceRoot.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/basic/resourceHandle.test.ts +17 -14
- package/src/__tests__/basic/tapReducer.basic.test.ts +200 -0
- package/src/__tests__/react/concurrent-mode.test.tsx +1 -4
- package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +215 -2
- package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +77 -0
- package/src/__tests__/strictmode/strictmode.test.ts +82 -21
- package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +67 -110
- package/src/__tests__/test-utils.ts +5 -1
- package/src/core/ResourceFiber.ts +22 -10
- package/src/core/createResourceRoot.ts +53 -0
- package/src/core/{callResourceFn.ts → helpers/callResourceFn.ts} +1 -1
- package/src/core/{commit.ts → helpers/commit.ts} +3 -3
- package/src/core/helpers/env.ts +3 -0
- package/src/core/{execution-context.ts → helpers/execution-context.ts} +1 -1
- package/src/core/helpers/root.ts +67 -0
- package/src/core/resource.ts +1 -1
- package/src/core/scheduler.ts +13 -1
- package/src/core/types.ts +27 -7
- package/src/hooks/tap-effect-event.ts +3 -2
- package/src/hooks/tap-memo.ts +24 -19
- package/src/hooks/tap-reducer.ts +148 -0
- package/src/hooks/tap-resource.ts +9 -9
- package/src/hooks/tap-resources.ts +23 -10
- package/src/hooks/tap-state.ts +11 -88
- package/src/hooks/utils/tapHook.ts +3 -3
- package/src/index.ts +3 -3
- package/src/react/use-resource.ts +24 -11
- package/src/tapResourceRoot.ts +131 -0
- package/dist/core/callResourceFn.d.ts.map +0 -1
- package/dist/core/callResourceFn.js.map +0 -1
- package/dist/core/commit.d.ts +0 -4
- package/dist/core/commit.d.ts.map +0 -1
- package/dist/core/commit.js.map +0 -1
- package/dist/core/createResource.d.ts +0 -15
- package/dist/core/createResource.d.ts.map +0 -1
- package/dist/core/createResource.js +0 -101
- package/dist/core/createResource.js.map +0 -1
- package/dist/core/env.d.ts.map +0 -1
- package/dist/core/env.js +0 -4
- package/dist/core/env.js.map +0 -1
- package/dist/core/execution-context.d.ts.map +0 -1
- package/dist/core/execution-context.js.map +0 -1
- package/dist/hooks/tap-inline-resource.d.ts +0 -3
- package/dist/hooks/tap-inline-resource.d.ts.map +0 -1
- package/dist/hooks/tap-inline-resource.js +0 -5
- package/dist/hooks/tap-inline-resource.js.map +0 -1
- package/dist/tapSubscribableResource.d.ts.map +0 -1
- package/dist/tapSubscribableResource.js +0 -60
- package/dist/tapSubscribableResource.js.map +0 -1
- package/src/core/createResource.ts +0 -155
- package/src/core/env.ts +0 -4
- package/src/hooks/tap-inline-resource.ts +0 -8
- package/src/tapSubscribableResource.ts +0 -101
- /package/dist/core/{callResourceFn.d.ts → helpers/callResourceFn.d.ts} +0 -0
- /package/dist/core/{callResourceFn.js → helpers/callResourceFn.js} +0 -0
- /package/dist/core/{env.d.ts → helpers/env.d.ts} +0 -0
- /package/dist/core/{execution-context.js → helpers/execution-context.js} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { resource } from "../../core/resource";
|
|
3
|
-
import { isDevelopment } from "../../core/env";
|
|
3
|
+
import { isDevelopment } from "../../core/helpers/env";
|
|
4
4
|
import { tapRef } from "../../hooks/tap-ref";
|
|
5
5
|
import { tapState } from "../../hooks/tap-state";
|
|
6
6
|
import { tapEffect } from "../../hooks/tap-effect";
|
|
7
|
+
import { tapMemo } from "../../hooks/tap-memo";
|
|
7
8
|
import { tapResource } from "../../hooks/tap-resource";
|
|
8
|
-
import {
|
|
9
|
+
import { createResourceRoot } from "../../core/createResourceRoot";
|
|
9
10
|
import { withKey } from "../../core/withKey";
|
|
10
11
|
|
|
11
12
|
describe("Strict Mode", () => {
|
|
@@ -13,6 +14,68 @@ describe("Strict Mode", () => {
|
|
|
13
14
|
expect(isDevelopment).toBe(true);
|
|
14
15
|
});
|
|
15
16
|
|
|
17
|
+
it("should persist tapMemo cache across strict mode double render", () => {
|
|
18
|
+
const events: string[] = [];
|
|
19
|
+
let outerCount = 0;
|
|
20
|
+
let memoCount = 0;
|
|
21
|
+
|
|
22
|
+
const TestResource = resource(() => {
|
|
23
|
+
const idx = outerCount++;
|
|
24
|
+
events.push(`outer-${idx}`);
|
|
25
|
+
|
|
26
|
+
tapMemo(() => {
|
|
27
|
+
events.push(`memo-${memoCount++}`);
|
|
28
|
+
return {};
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
events.push(`outerend-${idx}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const root = createResourceRoot();
|
|
35
|
+
root.render(TestResource());
|
|
36
|
+
|
|
37
|
+
console.log("Events:", events);
|
|
38
|
+
|
|
39
|
+
// tapMemo factory runs twice during first render (strict mode double-call)
|
|
40
|
+
// but should NOT run during second render (cache should persist)
|
|
41
|
+
expect(events).toEqual([
|
|
42
|
+
"outer-0",
|
|
43
|
+
"memo-0",
|
|
44
|
+
"memo-1",
|
|
45
|
+
"outerend-0",
|
|
46
|
+
"outer-1",
|
|
47
|
+
// no memo call here — cache should be reused
|
|
48
|
+
"outerend-1",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should double-invoke tapMemo factory and use the first result", () => {
|
|
53
|
+
const events: string[] = [];
|
|
54
|
+
let memoCallCount = 0;
|
|
55
|
+
|
|
56
|
+
const TestResource = resource(() => {
|
|
57
|
+
const memoValue = tapMemo(() => {
|
|
58
|
+
memoCallCount++;
|
|
59
|
+
events.push(`memo-${memoCallCount}`);
|
|
60
|
+
return memoCallCount;
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
events.push(`render memoValue=${memoValue}`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const root = createResourceRoot();
|
|
67
|
+
root.render(TestResource());
|
|
68
|
+
|
|
69
|
+
// Matches React useMemo behavior: factory is double-invoked,
|
|
70
|
+
// first result is kept
|
|
71
|
+
expect(events).toEqual([
|
|
72
|
+
"memo-1",
|
|
73
|
+
"memo-2",
|
|
74
|
+
"render memoValue=1",
|
|
75
|
+
"render memoValue=1",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
16
79
|
it("should double-render on first render", () => {
|
|
17
80
|
let renderCount = 0;
|
|
18
81
|
|
|
@@ -21,8 +84,9 @@ describe("Strict Mode", () => {
|
|
|
21
84
|
return { renderCount };
|
|
22
85
|
});
|
|
23
86
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
87
|
+
const root = createResourceRoot();
|
|
88
|
+
const sub = root.render(TestResource());
|
|
89
|
+
const output = sub.getValue();
|
|
26
90
|
|
|
27
91
|
expect(renderCount).toBe(2);
|
|
28
92
|
expect(output.renderCount).toBe(2);
|
|
@@ -47,7 +111,8 @@ describe("Strict Mode", () => {
|
|
|
47
111
|
expect(ref.current).toBe(4);
|
|
48
112
|
});
|
|
49
113
|
|
|
50
|
-
|
|
114
|
+
const root = createResourceRoot();
|
|
115
|
+
root.render(TestResource());
|
|
51
116
|
|
|
52
117
|
expect(renderCount).toBe(4);
|
|
53
118
|
});
|
|
@@ -86,7 +151,8 @@ describe("Strict Mode", () => {
|
|
|
86
151
|
}, [count]);
|
|
87
152
|
});
|
|
88
153
|
|
|
89
|
-
|
|
154
|
+
const root = createResourceRoot();
|
|
155
|
+
root.render(TestResource());
|
|
90
156
|
|
|
91
157
|
expect(events).toEqual([
|
|
92
158
|
"mount-1",
|
|
@@ -113,8 +179,9 @@ describe("Strict Mode", () => {
|
|
|
113
179
|
return tapResource(TestChildResource());
|
|
114
180
|
});
|
|
115
181
|
|
|
116
|
-
const
|
|
117
|
-
const
|
|
182
|
+
const root = createResourceRoot();
|
|
183
|
+
const sub = root.render(TestResource());
|
|
184
|
+
const output = sub.getValue();
|
|
118
185
|
|
|
119
186
|
expect(renderCount).toBe(2);
|
|
120
187
|
expect(output.renderCount).toBe(2);
|
|
@@ -134,10 +201,8 @@ describe("Strict Mode", () => {
|
|
|
134
201
|
});
|
|
135
202
|
});
|
|
136
203
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
devStrictMode: true,
|
|
140
|
-
});
|
|
204
|
+
const root = createResourceRoot();
|
|
205
|
+
root.render(TestResource());
|
|
141
206
|
|
|
142
207
|
expect(events).toEqual([
|
|
143
208
|
"render-0",
|
|
@@ -189,11 +254,9 @@ describe("Strict Mode", () => {
|
|
|
189
254
|
return tapResource(withKey(id, TestChildResource()));
|
|
190
255
|
});
|
|
191
256
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
});
|
|
196
|
-
const output = handle.getValue();
|
|
257
|
+
const root = createResourceRoot();
|
|
258
|
+
const sub = root.render(TestResource());
|
|
259
|
+
const output = sub.getValue();
|
|
197
260
|
|
|
198
261
|
expect(renderCount).toBe(4);
|
|
199
262
|
expect(fnCount).toBe(4);
|
|
@@ -238,10 +301,8 @@ describe("Strict Mode", () => {
|
|
|
238
301
|
return tapResource(withKey(id, TestChildResource()));
|
|
239
302
|
});
|
|
240
303
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
devStrictMode: true,
|
|
244
|
-
});
|
|
304
|
+
const root = createResourceRoot();
|
|
305
|
+
root.render(TestResource());
|
|
245
306
|
|
|
246
307
|
expect(events).toEqual([
|
|
247
308
|
"outer-render-0",
|
|
@@ -7,52 +7,12 @@ import { describe, it, expect } from "vitest";
|
|
|
7
7
|
import { resource } from "../../core/resource";
|
|
8
8
|
import { tapState } from "../../hooks/tap-state";
|
|
9
9
|
import { tapEffect } from "../../hooks/tap-effect";
|
|
10
|
-
import {
|
|
10
|
+
import { createResourceRoot } from "../../core/createResourceRoot";
|
|
11
11
|
import { flushResourcesSync } from "../../core/scheduler";
|
|
12
12
|
|
|
13
13
|
describe("Tap Strict Mode - Rerender Sources", () => {
|
|
14
|
-
describe("
|
|
15
|
-
it("should
|
|
16
|
-
let updaterInvocations = 0;
|
|
17
|
-
const events: string[] = [];
|
|
18
|
-
|
|
19
|
-
const TestResource = resource(() => {
|
|
20
|
-
const [count, setCount] = tapState(0);
|
|
21
|
-
events.push(`render count=${count}`);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
count,
|
|
25
|
-
increment: () => {
|
|
26
|
-
events.push("setState called");
|
|
27
|
-
setCount((prevCount) => {
|
|
28
|
-
updaterInvocations++;
|
|
29
|
-
events.push(
|
|
30
|
-
`updater invocation #${updaterInvocations} with prevCount=${prevCount}`,
|
|
31
|
-
);
|
|
32
|
-
return prevCount + 1;
|
|
33
|
-
});
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const handle = createResource(TestResource(), { devStrictMode: true });
|
|
39
|
-
|
|
40
|
-
events.length = 0;
|
|
41
|
-
updaterInvocations = 0;
|
|
42
|
-
|
|
43
|
-
flushResourcesSync(() => {
|
|
44
|
-
handle.getValue().increment();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
console.log("Updater invocations:", updaterInvocations);
|
|
48
|
-
console.log("Events:", events);
|
|
49
|
-
console.log(
|
|
50
|
-
"Expected: updater called twice (React behavior), actual:",
|
|
51
|
-
updaterInvocations,
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it.skip("should use the same return value logic as React when updater returns different values", () => {
|
|
14
|
+
describe("Callback invocation count", () => {
|
|
15
|
+
it("should use the first return value when updater returns different values", () => {
|
|
56
16
|
const events: string[] = [];
|
|
57
17
|
let updaterCallCount = 0;
|
|
58
18
|
|
|
@@ -80,28 +40,26 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
80
40
|
return { count };
|
|
81
41
|
});
|
|
82
42
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
mount: true,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
console.log("Tap updater call count:", updaterCallCount);
|
|
89
|
-
console.log("Tap events:", events);
|
|
43
|
+
const root = createResourceRoot();
|
|
44
|
+
root.render(TestResource());
|
|
90
45
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
46
|
+
// Tap behavior: updater called 4 times, uses FIRST return value per dispatch
|
|
47
|
+
// Effect #1 dispatch: updater(0) → 100 (kept)
|
|
48
|
+
// Effect #1 cleanup, Effect #2 mount
|
|
49
|
+
// Effect #2 dispatch: updater(0) → 200 (kept... but wait, prev=100 from effect #1)
|
|
50
|
+
// Updater double-invoke happens per-dispatch (matching React ordering)
|
|
93
51
|
expect(updaterCallCount).toBe(4);
|
|
94
52
|
expect(events).toEqual([
|
|
95
53
|
"render count=0",
|
|
96
54
|
"render count=0",
|
|
97
55
|
"effect mount",
|
|
98
|
-
"updater call #1 with prev=0",
|
|
56
|
+
"updater call #1 with prev=0",
|
|
99
57
|
"effect cleanup",
|
|
100
58
|
"effect mount",
|
|
101
|
-
"updater call #2 with prev=0",
|
|
102
|
-
"updater call #3 with prev=100",
|
|
103
|
-
"updater call #4 with prev=100",
|
|
104
|
-
"render count=200",
|
|
59
|
+
"updater call #2 with prev=0",
|
|
60
|
+
"updater call #3 with prev=100",
|
|
61
|
+
"updater call #4 with prev=100",
|
|
62
|
+
"render count=200",
|
|
105
63
|
"render count=200",
|
|
106
64
|
]);
|
|
107
65
|
});
|
|
@@ -117,7 +75,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
117
75
|
return { count };
|
|
118
76
|
});
|
|
119
77
|
|
|
120
|
-
|
|
78
|
+
const root = createResourceRoot();
|
|
79
|
+
root.render(TestResource());
|
|
121
80
|
|
|
122
81
|
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
123
82
|
});
|
|
@@ -144,7 +103,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
144
103
|
return { count };
|
|
145
104
|
});
|
|
146
105
|
|
|
147
|
-
|
|
106
|
+
const root = createResourceRoot();
|
|
107
|
+
root.render(TestResource());
|
|
148
108
|
|
|
149
109
|
expect(events).toEqual([
|
|
150
110
|
"render count=0",
|
|
@@ -177,7 +137,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
177
137
|
};
|
|
178
138
|
});
|
|
179
139
|
|
|
180
|
-
const
|
|
140
|
+
const root = createResourceRoot();
|
|
141
|
+
const sub = root.render(TestResource());
|
|
181
142
|
|
|
182
143
|
// Initial render is double
|
|
183
144
|
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
@@ -186,7 +147,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
186
147
|
|
|
187
148
|
// Call the method inside flushResourcesSync (like clicking a button)
|
|
188
149
|
flushResourcesSync(() => {
|
|
189
|
-
|
|
150
|
+
sub.getValue().increment();
|
|
190
151
|
});
|
|
191
152
|
|
|
192
153
|
// flushResourcesSync setState should ALSO double-render (matching React 19)
|
|
@@ -209,19 +170,20 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
209
170
|
};
|
|
210
171
|
});
|
|
211
172
|
|
|
212
|
-
const
|
|
173
|
+
const root = createResourceRoot();
|
|
174
|
+
const sub = root.render(TestResource());
|
|
213
175
|
|
|
214
176
|
events.length = 0; // Clear initial renders
|
|
215
177
|
|
|
216
178
|
// Multiple flushResourcesSync calls (like multiple button clicks)
|
|
217
179
|
flushResourcesSync(() => {
|
|
218
|
-
|
|
180
|
+
sub.getValue().increment();
|
|
219
181
|
});
|
|
220
182
|
flushResourcesSync(() => {
|
|
221
|
-
|
|
183
|
+
sub.getValue().increment();
|
|
222
184
|
});
|
|
223
185
|
flushResourcesSync(() => {
|
|
224
|
-
|
|
186
|
+
sub.getValue().increment();
|
|
225
187
|
});
|
|
226
188
|
|
|
227
189
|
// Each call should cause double render
|
|
@@ -240,7 +202,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
240
202
|
});
|
|
241
203
|
|
|
242
204
|
describe("Source 4: setState in setTimeout", () => {
|
|
243
|
-
it
|
|
205
|
+
it("should double-render AND double-call setTimeout callback", async () => {
|
|
244
206
|
const events: string[] = [];
|
|
245
207
|
|
|
246
208
|
const TestResource = resource(() => {
|
|
@@ -259,7 +221,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
259
221
|
return { count };
|
|
260
222
|
});
|
|
261
223
|
|
|
262
|
-
|
|
224
|
+
const root = createResourceRoot();
|
|
225
|
+
root.render(TestResource());
|
|
263
226
|
|
|
264
227
|
// Wait for setTimeout
|
|
265
228
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
@@ -296,7 +259,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
296
259
|
return { count };
|
|
297
260
|
});
|
|
298
261
|
|
|
299
|
-
|
|
262
|
+
const root = createResourceRoot();
|
|
263
|
+
root.render(TestResource());
|
|
300
264
|
|
|
301
265
|
// Wait for promise
|
|
302
266
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
@@ -331,12 +295,13 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
331
295
|
};
|
|
332
296
|
});
|
|
333
297
|
|
|
334
|
-
const
|
|
298
|
+
const root = createResourceRoot();
|
|
299
|
+
const sub = root.render(TestResource());
|
|
335
300
|
|
|
336
301
|
events.length = 0; // Clear initial renders
|
|
337
302
|
|
|
338
303
|
flushResourcesSync(() => {
|
|
339
|
-
|
|
304
|
+
sub.getValue().updateBoth();
|
|
340
305
|
});
|
|
341
306
|
|
|
342
307
|
// Both setState calls batched, but render is DOUBLED
|
|
@@ -365,7 +330,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
365
330
|
return {};
|
|
366
331
|
});
|
|
367
332
|
|
|
368
|
-
|
|
333
|
+
const root = createResourceRoot();
|
|
334
|
+
root.render(TestResource());
|
|
369
335
|
|
|
370
336
|
// Initial double-render, then batched setState causes another double-render
|
|
371
337
|
expect(events).toEqual([
|
|
@@ -391,7 +357,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
391
357
|
};
|
|
392
358
|
});
|
|
393
359
|
|
|
394
|
-
|
|
360
|
+
const root = createResourceRoot();
|
|
361
|
+
root.render(TestResource());
|
|
395
362
|
|
|
396
363
|
// Resource renders should be doubled
|
|
397
364
|
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
@@ -418,12 +385,13 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
418
385
|
};
|
|
419
386
|
});
|
|
420
387
|
|
|
421
|
-
const
|
|
388
|
+
const root = createResourceRoot();
|
|
389
|
+
const sub = root.render(TestResource());
|
|
422
390
|
|
|
423
391
|
events.length = 0; // Clear initial renders
|
|
424
392
|
|
|
425
393
|
flushResourcesSync(() => {
|
|
426
|
-
|
|
394
|
+
sub.getValue().increment();
|
|
427
395
|
});
|
|
428
396
|
|
|
429
397
|
// React behavior: Updater function is called TWICE in strict mode
|
|
@@ -438,7 +406,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
438
406
|
});
|
|
439
407
|
|
|
440
408
|
describe("Source 9: Complex effect patterns", () => {
|
|
441
|
-
it
|
|
409
|
+
it("should handle effect with dependencies and setState", () => {
|
|
442
410
|
const events: string[] = [];
|
|
443
411
|
|
|
444
412
|
const TestResource = resource(() => {
|
|
@@ -460,33 +428,26 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
460
428
|
};
|
|
461
429
|
});
|
|
462
430
|
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
mount: true,
|
|
466
|
-
});
|
|
431
|
+
const root = createResourceRoot();
|
|
432
|
+
const sub = root.render(TestResource());
|
|
467
433
|
|
|
468
|
-
//
|
|
469
|
-
// it triggers additional render cycles
|
|
434
|
+
// setDoubled(0*2) = setDoubled(0) is a no-op, so no extra render
|
|
470
435
|
expect(events).toEqual([
|
|
471
436
|
"render count=0 doubled=0",
|
|
472
437
|
"render count=0 doubled=0",
|
|
473
438
|
"effect count=0",
|
|
474
439
|
"cleanup count=0",
|
|
475
440
|
"effect count=0",
|
|
476
|
-
"render count=0 doubled=0",
|
|
477
|
-
"render count=0 doubled=0",
|
|
478
|
-
"cleanup count=0",
|
|
479
|
-
"effect count=0",
|
|
480
441
|
]);
|
|
481
442
|
|
|
482
443
|
events.length = 0;
|
|
483
444
|
|
|
484
445
|
// Trigger increment via flushResourcesSync
|
|
485
446
|
flushResourcesSync(() => {
|
|
486
|
-
|
|
447
|
+
sub.getValue().increment();
|
|
487
448
|
});
|
|
488
449
|
|
|
489
|
-
//
|
|
450
|
+
// Double-render with new count, effect sets doubled=2, triggers another double-render
|
|
490
451
|
expect(events).toEqual([
|
|
491
452
|
"render count=1 doubled=0",
|
|
492
453
|
"render count=1 doubled=0",
|
|
@@ -494,8 +455,6 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
494
455
|
"effect count=1",
|
|
495
456
|
"render count=1 doubled=2",
|
|
496
457
|
"render count=1 doubled=2",
|
|
497
|
-
"cleanup count=1",
|
|
498
|
-
"effect count=1",
|
|
499
458
|
]);
|
|
500
459
|
});
|
|
501
460
|
});
|
|
@@ -517,7 +476,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
517
476
|
return { value };
|
|
518
477
|
});
|
|
519
478
|
|
|
520
|
-
|
|
479
|
+
const root = createResourceRoot();
|
|
480
|
+
root.render(TestResource());
|
|
521
481
|
|
|
522
482
|
// tapState initializer should be called twice, first value kept
|
|
523
483
|
expect(events).toEqual([
|
|
@@ -544,17 +504,19 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
544
504
|
});
|
|
545
505
|
|
|
546
506
|
// Create first instance
|
|
547
|
-
const
|
|
507
|
+
const root1 = createResourceRoot();
|
|
508
|
+
root1.render(TestResource());
|
|
548
509
|
|
|
549
510
|
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
550
511
|
|
|
551
512
|
events.length = 0;
|
|
552
513
|
|
|
553
514
|
// Unmount
|
|
554
|
-
|
|
515
|
+
root1.unmount();
|
|
555
516
|
|
|
556
517
|
// Create second instance
|
|
557
|
-
const
|
|
518
|
+
const root2 = createResourceRoot();
|
|
519
|
+
const sub2 = root2.render(TestResource());
|
|
558
520
|
|
|
559
521
|
// Should still double-render
|
|
560
522
|
expect(events).toEqual(["render count=0", "render count=0"]);
|
|
@@ -563,7 +525,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
563
525
|
|
|
564
526
|
// Method calls via flushResourcesSync should still double-render
|
|
565
527
|
flushResourcesSync(() => {
|
|
566
|
-
|
|
528
|
+
sub2.getValue().increment();
|
|
567
529
|
});
|
|
568
530
|
|
|
569
531
|
expect(events).toEqual(["render count=1", "render count=1"]);
|
|
@@ -600,10 +562,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
600
562
|
return { count };
|
|
601
563
|
});
|
|
602
564
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
mount: true,
|
|
606
|
-
});
|
|
565
|
+
const root = createResourceRoot();
|
|
566
|
+
root.render(TestResource());
|
|
607
567
|
|
|
608
568
|
// Expected: setState(1) from effect #1 should be applied
|
|
609
569
|
// even though effect #1 was cleaned up
|
|
@@ -649,10 +609,8 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
649
609
|
return { count };
|
|
650
610
|
});
|
|
651
611
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
mount: true,
|
|
655
|
-
});
|
|
612
|
+
const root = createResourceRoot();
|
|
613
|
+
root.render(TestResource());
|
|
656
614
|
|
|
657
615
|
// Expected: Only setState(2) should be applied (last one wins)
|
|
658
616
|
expect(events).toEqual([
|
|
@@ -668,7 +626,7 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
668
626
|
]);
|
|
669
627
|
});
|
|
670
628
|
|
|
671
|
-
it
|
|
629
|
+
it("should handle updater functions from both effect mounts", () => {
|
|
672
630
|
const events: string[] = [];
|
|
673
631
|
let effectRunCount = 0;
|
|
674
632
|
|
|
@@ -696,14 +654,13 @@ describe("Tap Strict Mode - Rerender Sources", () => {
|
|
|
696
654
|
return { count };
|
|
697
655
|
});
|
|
698
656
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
mount: true,
|
|
702
|
-
});
|
|
657
|
+
const root = createResourceRoot();
|
|
658
|
+
root.render(TestResource());
|
|
703
659
|
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
// Effect #
|
|
660
|
+
// Tap behavior: Both updaters are queued and executed, first value kept per dispatch
|
|
661
|
+
// Updater double-invoke happens per-dispatch (matching React ordering)
|
|
662
|
+
// Effect #1: updater(0) => 0 + 1 = 1 (kept)
|
|
663
|
+
// Effect #2: updater(0) => 0 + 2 = 2... but prev=1 from effect #1
|
|
707
664
|
// Final: 3
|
|
708
665
|
expect(events).toEqual([
|
|
709
666
|
"render count=0",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createResourceFiberRoot } from "../core/helpers/root";
|
|
1
2
|
import { resource } from "../core/resource";
|
|
2
3
|
import {
|
|
3
4
|
createResourceFiber,
|
|
@@ -30,7 +31,10 @@ export function createTestResource<R, P>(fn: (props: P) => R) {
|
|
|
30
31
|
}
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
const fiber = createResourceFiber(
|
|
34
|
+
const fiber = createResourceFiber(
|
|
35
|
+
resource(fn),
|
|
36
|
+
createResourceFiberRoot(rerenderCallback),
|
|
37
|
+
);
|
|
34
38
|
return fiber;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
ResourceFiber,
|
|
3
|
+
RenderResult,
|
|
4
|
+
Resource,
|
|
5
|
+
ResourceFiberRoot,
|
|
6
|
+
} from "./types";
|
|
7
|
+
import { commitAllEffects, cleanupAllEffects } from "./helpers/commit";
|
|
8
|
+
import {
|
|
9
|
+
getDevStrictMode,
|
|
10
|
+
withResourceFiber,
|
|
11
|
+
} from "./helpers/execution-context";
|
|
12
|
+
import { callResourceFn } from "./helpers/callResourceFn";
|
|
13
|
+
import { isDevelopment } from "./helpers/env";
|
|
6
14
|
|
|
7
15
|
export function createResourceFiber<R, P>(
|
|
8
16
|
type: Resource<R, P>,
|
|
9
|
-
|
|
17
|
+
root: ResourceFiberRoot,
|
|
18
|
+
markDirty: (() => void) | undefined = undefined,
|
|
10
19
|
strictMode: "root" | "child" | null = getDevStrictMode(false),
|
|
11
20
|
): ResourceFiber<R, P> {
|
|
12
21
|
return {
|
|
13
22
|
type,
|
|
14
|
-
|
|
23
|
+
root,
|
|
24
|
+
markDirty,
|
|
15
25
|
devStrictMode: strictMode,
|
|
16
26
|
cells: [],
|
|
17
27
|
currentIndex: 0,
|
|
@@ -35,7 +45,7 @@ export function renderResourceFiber<R, P>(
|
|
|
35
45
|
props: P,
|
|
36
46
|
): RenderResult {
|
|
37
47
|
const result = {
|
|
38
|
-
|
|
48
|
+
effectTasks: [],
|
|
39
49
|
props,
|
|
40
50
|
output: undefined as R | undefined,
|
|
41
51
|
};
|
|
@@ -59,10 +69,12 @@ export function commitResourceFiber<R, P>(
|
|
|
59
69
|
fiber.isMounted = true;
|
|
60
70
|
|
|
61
71
|
if (isDevelopment && fiber.isNeverMounted && fiber.devStrictMode === "root") {
|
|
62
|
-
|
|
72
|
+
fiber.isNeverMounted = false;
|
|
73
|
+
|
|
74
|
+
commitAllEffects(result);
|
|
63
75
|
cleanupAllEffects(fiber);
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
fiber.isNeverMounted = false;
|
|
67
|
-
|
|
79
|
+
commitAllEffects(result);
|
|
68
80
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ResourceElement } from "./types";
|
|
2
|
+
import {
|
|
3
|
+
createResourceFiber,
|
|
4
|
+
unmountResourceFiber,
|
|
5
|
+
renderResourceFiber,
|
|
6
|
+
commitResourceFiber,
|
|
7
|
+
} from "./ResourceFiber";
|
|
8
|
+
import { tapResourceRoot } from "../tapResourceRoot";
|
|
9
|
+
import { resource } from "./resource";
|
|
10
|
+
import { isDevelopment } from "./helpers/env";
|
|
11
|
+
import { flushResourcesSync, UpdateScheduler } from "./scheduler";
|
|
12
|
+
import { createResourceFiberRoot } from "./helpers/root";
|
|
13
|
+
|
|
14
|
+
const SubscribableResource = resource(tapResourceRoot);
|
|
15
|
+
|
|
16
|
+
export const createResourceRoot = () => {
|
|
17
|
+
const fiber = createResourceFiber<
|
|
18
|
+
tapResourceRoot.SubscribableResource<any>,
|
|
19
|
+
ResourceElement<any>
|
|
20
|
+
>(
|
|
21
|
+
SubscribableResource,
|
|
22
|
+
createResourceFiberRoot((callback) => {
|
|
23
|
+
new UpdateScheduler(() => {
|
|
24
|
+
if (callback()) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Unexpected rerender of createResourceRoot outer fiber",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}).markDirty();
|
|
31
|
+
}),
|
|
32
|
+
undefined,
|
|
33
|
+
isDevelopment ? "root" : null,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
render: <R, P>(element: ResourceElement<R, P>) => {
|
|
38
|
+
// In strict mode, render twice to detect side effects
|
|
39
|
+
if (isDevelopment && fiber.devStrictMode === "root") {
|
|
40
|
+
void renderResourceFiber(fiber, element);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const render = renderResourceFiber(fiber, element);
|
|
44
|
+
|
|
45
|
+
flushResourcesSync(() => commitResourceFiber(fiber, render));
|
|
46
|
+
|
|
47
|
+
return render.output;
|
|
48
|
+
},
|
|
49
|
+
unmount: () => {
|
|
50
|
+
unmountResourceFiber(fiber);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|