@assistant-ui/tap 0.3.6 → 0.4.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 +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 +30 -48
- 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 +33 -19
- 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 +3 -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 +1 -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 +43 -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.js +1 -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 +8 -1
- 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 +270 -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 +29 -58
- package/src/core/context.ts +2 -2
- package/src/core/createResource.ts +46 -22
- package/src/core/env.ts +3 -0
- package/src/core/execution-context.ts +9 -0
- package/src/core/resource.ts +6 -3
- package/src/core/scheduler.ts +1 -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 +48 -38
- package/src/hooks/tap-inline-resource.ts +2 -2
- package/src/hooks/tap-memo.ts +1 -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
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
renderTest,
|
|
7
7
|
cleanupAllResources,
|
|
8
8
|
waitForNextTick,
|
|
9
|
-
|
|
9
|
+
getCommittedOutput,
|
|
10
10
|
} from "../test-utils";
|
|
11
11
|
|
|
12
12
|
describe("tapState - Basic Functionality", () => {
|
|
@@ -85,7 +85,7 @@ describe("tapState - Basic Functionality", () => {
|
|
|
85
85
|
await waitForNextTick();
|
|
86
86
|
|
|
87
87
|
// Check that state was updated
|
|
88
|
-
expect(
|
|
88
|
+
expect(getCommittedOutput(testFiber)).toEqual({
|
|
89
89
|
count: 10,
|
|
90
90
|
renderCount: 2,
|
|
91
91
|
});
|
|
@@ -136,19 +136,19 @@ describe("tapState - Basic Functionality", () => {
|
|
|
136
136
|
|
|
137
137
|
// Initial render
|
|
138
138
|
renderTest(testFiber, undefined);
|
|
139
|
-
expect(
|
|
139
|
+
expect(getCommittedOutput(testFiber)).toBe(10);
|
|
140
140
|
|
|
141
141
|
// Functional update
|
|
142
142
|
setCountFn!((prev) => prev * 2);
|
|
143
143
|
|
|
144
144
|
await waitForNextTick();
|
|
145
|
-
expect(
|
|
145
|
+
expect(getCommittedOutput(testFiber)).toBe(20);
|
|
146
146
|
|
|
147
147
|
// Another functional update
|
|
148
148
|
setCountFn!((prev) => prev + 5);
|
|
149
149
|
|
|
150
150
|
await waitForNextTick();
|
|
151
|
-
expect(
|
|
151
|
+
expect(getCommittedOutput(testFiber)).toBe(25);
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -192,18 +192,18 @@ describe("tapState - Basic Functionality", () => {
|
|
|
192
192
|
|
|
193
193
|
// Initial render
|
|
194
194
|
renderTest(testFiber, undefined);
|
|
195
|
-
expect(
|
|
195
|
+
expect(getCommittedOutput(testFiber)).toEqual({ a: "a", b: "b", c: "c" });
|
|
196
196
|
|
|
197
197
|
// Update only B
|
|
198
198
|
setters.setB("B");
|
|
199
199
|
await waitForNextTick();
|
|
200
|
-
expect(
|
|
200
|
+
expect(getCommittedOutput(testFiber)).toEqual({ a: "a", b: "B", c: "c" });
|
|
201
201
|
|
|
202
202
|
// Update A and C
|
|
203
203
|
setters.setA("A");
|
|
204
204
|
setters.setC("C");
|
|
205
205
|
await waitForNextTick();
|
|
206
|
-
expect(
|
|
206
|
+
expect(getCommittedOutput(testFiber)).toEqual({ a: "A", b: "B", c: "C" });
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
209
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/correctness/useExhaustiveDependencies: tests */
|
|
1
2
|
import { describe, it, expect, vi } from "vitest";
|
|
2
3
|
import { tapEffect } from "../../hooks/tap-effect";
|
|
3
4
|
import { tapState } from "../../hooks/tap-state";
|
|
@@ -81,9 +82,13 @@ describe("Errors - Effect Errors", () => {
|
|
|
81
82
|
return null;
|
|
82
83
|
});
|
|
83
84
|
|
|
84
|
-
// Should throw
|
|
85
|
-
expect(() =>
|
|
86
|
-
|
|
85
|
+
// Should throw aggregate error
|
|
86
|
+
expect(() =>
|
|
87
|
+
renderTest(resource, undefined),
|
|
88
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
89
|
+
[AggregateError: Errors during commit]
|
|
90
|
+
`);
|
|
91
|
+
expect(goodEffect).toHaveBeenCalledTimes(1);
|
|
87
92
|
});
|
|
88
93
|
|
|
89
94
|
it("should continue cleanup on unmount despite errors", () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/correctness/useExhaustiveDependencies: tests */
|
|
1
2
|
import { describe, it, expect, vi } from "vitest";
|
|
2
3
|
import { tapEffect } from "../../hooks/tap-effect";
|
|
3
4
|
import { tapState } from "../../hooks/tap-state";
|
|
@@ -232,9 +233,9 @@ describe("Lifecycle - Dependencies", () => {
|
|
|
232
233
|
|
|
233
234
|
// Change to no deps
|
|
234
235
|
useDeps = false;
|
|
235
|
-
const ctx = renderResourceFiber(resource, undefined);
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
// Error now throws during render (fail-fast validation)
|
|
238
|
+
expect(() => renderResourceFiber(resource, undefined)).toThrow(
|
|
238
239
|
"tapEffect called with and without dependencies across re-renders",
|
|
239
240
|
);
|
|
240
241
|
});
|
|
@@ -54,7 +54,7 @@ describe("Lifecycle - Mount/Unmount", () => {
|
|
|
54
54
|
renderTest(resource, undefined);
|
|
55
55
|
unmountResource(resource);
|
|
56
56
|
|
|
57
|
-
expect(order).toEqual([
|
|
57
|
+
expect(order).toEqual([1, 2, 3]);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it("should preserve state across re-renders", () => {
|
|
@@ -150,7 +150,7 @@ describe("Lifecycle - Mount/Unmount", () => {
|
|
|
150
150
|
|
|
151
151
|
// Unmount
|
|
152
152
|
unmountResourceFiber(resource);
|
|
153
|
-
expect(log).toEqual(["cleanup-
|
|
153
|
+
expect(log).toEqual(["cleanup-1", "cleanup-2"]);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it("should handle cleanup errors gracefully", () => {
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen, act } from "@testing-library/react";
|
|
3
|
+
import { Suspense, startTransition, use, useState } from "react";
|
|
4
|
+
import { resource } from "../../core/resource";
|
|
5
|
+
import { useResource } from "../../react/use-resource";
|
|
6
|
+
import { tapState } from "../../hooks/tap-state";
|
|
7
|
+
|
|
8
|
+
const ShouldNeverFallback = () => {
|
|
9
|
+
throw new Error("should never fallback");
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe("Concurrent Mode with useResource", () => {
|
|
13
|
+
// TODO: tapState updates are not rolled back when React discards a concurrent render
|
|
14
|
+
// This requires architectural changes to make tapState updates "tentative" until React commits
|
|
15
|
+
// For now, tapState behaves like external state (Zustand, Jotai) which has the same limitation
|
|
16
|
+
it.skip("should not commit tapState updates when render is discarded", async () => {
|
|
17
|
+
const TestResource = resource(() => {
|
|
18
|
+
return tapState(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let resolve: (value: number) => void;
|
|
22
|
+
|
|
23
|
+
const suspendPromise = new Promise<number>((r) => {
|
|
24
|
+
resolve = r;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function Suspender() {
|
|
28
|
+
const result = use(suspendPromise);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
const [load, setLoading] = useResource(TestResource());
|
|
34
|
+
const [message, setMessage] = useState("none");
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<button data-testid="hello-btn" onClick={() => setMessage("hello")} />
|
|
39
|
+
<div data-testid="message">{message}</div>
|
|
40
|
+
<div data-testid="load">{load ? "true" : "false"}</div>
|
|
41
|
+
|
|
42
|
+
<button
|
|
43
|
+
data-testid="suspend-btn"
|
|
44
|
+
onClick={() => {
|
|
45
|
+
startTransition(() => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
});
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
<Suspense fallback={<ShouldNeverFallback />}>
|
|
51
|
+
<div data-testid="value">{load ? <Suspender /> : "none"}</div>
|
|
52
|
+
</Suspense>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render(<App />);
|
|
58
|
+
expect(screen.getByTestId("message").textContent).toBe("none");
|
|
59
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
60
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
61
|
+
|
|
62
|
+
await act(async () => screen.getByTestId("suspend-btn").click());
|
|
63
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
64
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
65
|
+
|
|
66
|
+
await act(async () => screen.getByTestId("hello-btn").click());
|
|
67
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
68
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
69
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
70
|
+
|
|
71
|
+
await act(async () => resolve!(10));
|
|
72
|
+
|
|
73
|
+
expect(screen.getByTestId("value").textContent).toBe("10");
|
|
74
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("react should not commit tapState updates when render is discarded", async () => {
|
|
78
|
+
let resolve: (value: number) => void;
|
|
79
|
+
|
|
80
|
+
const suspendPromise = new Promise<number>((r) => {
|
|
81
|
+
resolve = r;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function Suspender() {
|
|
85
|
+
const result = use(suspendPromise);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function App() {
|
|
90
|
+
const [load, setLoading] = useState(false);
|
|
91
|
+
const [message, setMessage] = useState("none");
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<button data-testid="hello-btn" onClick={() => setMessage("hello")} />
|
|
96
|
+
<div data-testid="message">{message}</div>
|
|
97
|
+
<div data-testid="load">{load ? "true" : "false"}</div>
|
|
98
|
+
|
|
99
|
+
<button
|
|
100
|
+
data-testid="suspend-btn"
|
|
101
|
+
onClick={() => {
|
|
102
|
+
startTransition(() => {
|
|
103
|
+
setLoading(true);
|
|
104
|
+
});
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
<Suspense fallback={<ShouldNeverFallback />}>
|
|
108
|
+
<div data-testid="value">{load ? <Suspender /> : "none"}</div>
|
|
109
|
+
</Suspense>
|
|
110
|
+
</>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render(<App />);
|
|
115
|
+
expect(screen.getByTestId("message").textContent).toBe("none");
|
|
116
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
117
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
118
|
+
|
|
119
|
+
await act(async () => screen.getByTestId("suspend-btn").click());
|
|
120
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
121
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
122
|
+
|
|
123
|
+
await act(async () => screen.getByTestId("hello-btn").click());
|
|
124
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
125
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
126
|
+
expect(screen.getByTestId("load").textContent).toBe("false"); // no tearing
|
|
127
|
+
|
|
128
|
+
await act(async () => resolve!(10));
|
|
129
|
+
|
|
130
|
+
expect(screen.getByTestId("value").textContent).toBe("10");
|
|
131
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should keep old UI during startTransition when resource suspends", async () => {
|
|
135
|
+
let resolve: () => void;
|
|
136
|
+
let shouldSuspend = false;
|
|
137
|
+
|
|
138
|
+
const TestResource = resource((props: { id: number }) => {
|
|
139
|
+
if (shouldSuspend) {
|
|
140
|
+
throw new Promise<void>((r) => {
|
|
141
|
+
resolve = r;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return { value: `content-${props.id}` };
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function Inner({ id }: { id: number }) {
|
|
148
|
+
const result = useResource(TestResource({ id }));
|
|
149
|
+
return <div data-testid="result">{result.value}</div>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function App() {
|
|
153
|
+
const [id, setId] = useState(1);
|
|
154
|
+
return (
|
|
155
|
+
<div>
|
|
156
|
+
<button
|
|
157
|
+
data-testid="btn"
|
|
158
|
+
onClick={() => {
|
|
159
|
+
shouldSuspend = true;
|
|
160
|
+
startTransition(() => setId(2));
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
<Suspense fallback={<div data-testid="fallback">Loading</div>}>
|
|
164
|
+
<Inner id={id} />
|
|
165
|
+
</Suspense>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
render(<App />);
|
|
171
|
+
expect(screen.getByTestId("result").textContent).toBe("content-1");
|
|
172
|
+
|
|
173
|
+
// Click triggers transition that suspends
|
|
174
|
+
act(() => screen.getByTestId("btn").click());
|
|
175
|
+
|
|
176
|
+
// Old UI preserved during transition
|
|
177
|
+
expect(screen.getByTestId("result").textContent).toBe("content-1");
|
|
178
|
+
|
|
179
|
+
// Resolve suspension
|
|
180
|
+
shouldSuspend = false;
|
|
181
|
+
await act(async () => resolve());
|
|
182
|
+
|
|
183
|
+
// New UI shown
|
|
184
|
+
expect(screen.getByTestId("result").textContent).toBe("content-2");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("react test", async () => {
|
|
188
|
+
let resolve: (value: number) => void;
|
|
189
|
+
|
|
190
|
+
const suspendPromise = new Promise<number>((r) => {
|
|
191
|
+
resolve = r;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
function Suspender() {
|
|
195
|
+
const result = use(suspendPromise);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function App() {
|
|
200
|
+
const [load, setLoading] = useState(false);
|
|
201
|
+
const [message, setMessage] = useState("none");
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<>
|
|
205
|
+
<button data-testid="hello-btn" onClick={() => setMessage("hello")} />
|
|
206
|
+
<div data-testid="message">{message}</div>
|
|
207
|
+
<div data-testid="load">{load ? "true" : "false"}</div>
|
|
208
|
+
|
|
209
|
+
<button
|
|
210
|
+
data-testid="suspend-btn"
|
|
211
|
+
onClick={() => {
|
|
212
|
+
startTransition(() => {
|
|
213
|
+
setLoading(true);
|
|
214
|
+
});
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
<Suspense fallback={<ShouldNeverFallback />}>
|
|
218
|
+
<div data-testid="value">{load ? <Suspender /> : "none"}</div>
|
|
219
|
+
</Suspense>
|
|
220
|
+
</>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
render(<App />);
|
|
225
|
+
expect(screen.getByTestId("message").textContent).toBe("none");
|
|
226
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
227
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
228
|
+
|
|
229
|
+
await act(async () => screen.getByTestId("suspend-btn").click());
|
|
230
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
231
|
+
expect(screen.getByTestId("load").textContent).toBe("false");
|
|
232
|
+
|
|
233
|
+
await act(async () => screen.getByTestId("hello-btn").click());
|
|
234
|
+
expect(screen.getByTestId("value").textContent).toBe("none");
|
|
235
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
236
|
+
expect(screen.getByTestId("load").textContent).toBe("false"); // no tearing
|
|
237
|
+
|
|
238
|
+
await act(async () => resolve!(10));
|
|
239
|
+
|
|
240
|
+
expect(screen.getByTestId("value").textContent).toBe("10");
|
|
241
|
+
expect(screen.getByTestId("message").textContent).toBe("hello");
|
|
242
|
+
});
|
|
243
|
+
});
|