@preact/signals-react 1.2.2 → 1.3.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 +17 -0
- package/dist/signals.d.ts +5 -0
- package/dist/signals.js +1 -1
- package/dist/signals.js.map +1 -1
- package/dist/signals.min.js +1 -1
- package/dist/signals.min.js.map +1 -1
- package/dist/signals.mjs +1 -1
- package/dist/signals.mjs.map +1 -1
- package/dist/signals.module.js +1 -1
- package/dist/signals.module.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +165 -84
- package/src/internal.d.ts +0 -4
- package/test/index.test.tsx +180 -54
- package/test/react-router.test.tsx +49 -0
- package/test/utils.ts +67 -0
package/test/index.test.tsx
CHANGED
|
@@ -1,53 +1,81 @@
|
|
|
1
1
|
// @ts-ignore-next-line
|
|
2
2
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
signal,
|
|
6
|
+
computed,
|
|
7
|
+
useComputed,
|
|
8
|
+
useSignalEffect,
|
|
9
|
+
useSignal,
|
|
10
|
+
} from "@preact/signals-react";
|
|
11
|
+
import {
|
|
12
|
+
createElement,
|
|
13
|
+
forwardRef,
|
|
14
|
+
useMemo,
|
|
15
|
+
useReducer,
|
|
16
|
+
memo,
|
|
17
|
+
StrictMode,
|
|
18
|
+
createRef,
|
|
19
|
+
} from "react";
|
|
20
|
+
|
|
7
21
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
8
|
-
import { act } from "
|
|
22
|
+
import { createRoot, Root, act, checkHangingAct } from "./utils";
|
|
9
23
|
|
|
10
24
|
describe("@preact/signals-react", () => {
|
|
11
25
|
let scratch: HTMLDivElement;
|
|
12
26
|
let root: Root;
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
|
|
28
|
+
async function render(element: Parameters<Root["render"]>[0]) {
|
|
29
|
+
await act(() => root.render(element));
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
beforeEach(() => {
|
|
32
|
+
beforeEach(async () => {
|
|
18
33
|
scratch = document.createElement("div");
|
|
19
|
-
|
|
34
|
+
document.body.appendChild(scratch);
|
|
35
|
+
root = await createRoot(scratch);
|
|
20
36
|
});
|
|
21
37
|
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
checkHangingAct();
|
|
40
|
+
await act(() => root.unmount());
|
|
41
|
+
scratch.remove();
|
|
24
42
|
});
|
|
25
43
|
|
|
26
44
|
describe("Text bindings", () => {
|
|
27
|
-
it("should render text without signals", () => {
|
|
28
|
-
render(<span>test</span>);
|
|
45
|
+
it("should render text without signals", async () => {
|
|
46
|
+
await render(<span>test</span>);
|
|
29
47
|
const span = scratch.firstChild;
|
|
30
48
|
const text = span?.firstChild;
|
|
31
49
|
expect(text).to.have.property("data", "test");
|
|
32
50
|
});
|
|
33
51
|
|
|
34
|
-
it("should render Signals as Text", () => {
|
|
52
|
+
it("should render Signals as Text", async () => {
|
|
35
53
|
const sig = signal("test");
|
|
36
|
-
render(<span>{sig}</span>);
|
|
54
|
+
await render(<span>{sig}</span>);
|
|
37
55
|
const span = scratch.firstChild;
|
|
38
56
|
expect(span).to.have.property("firstChild").that.is.an.instanceOf(Text);
|
|
39
57
|
const text = span?.firstChild;
|
|
40
58
|
expect(text).to.have.property("data", "test");
|
|
41
59
|
});
|
|
42
60
|
|
|
43
|
-
it("should
|
|
61
|
+
it("should render computed as Text", async () => {
|
|
44
62
|
const sig = signal("test");
|
|
45
|
-
|
|
63
|
+
const comp = computed(() => `${sig} ${sig}`);
|
|
64
|
+
await render(<span>{comp}</span>);
|
|
65
|
+
const span = scratch.firstChild;
|
|
66
|
+
expect(span).to.have.property("firstChild").that.is.an.instanceOf(Text);
|
|
67
|
+
const text = span?.firstChild;
|
|
68
|
+
expect(text).to.have.property("data", "test test");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should update Signal-based Text (no parent component)", async () => {
|
|
72
|
+
const sig = signal("test");
|
|
73
|
+
await render(<span>{sig}</span>);
|
|
46
74
|
|
|
47
75
|
const text = scratch.firstChild!.firstChild!;
|
|
48
76
|
expect(text).to.have.property("data", "test");
|
|
49
77
|
|
|
50
|
-
act(() => {
|
|
78
|
+
await act(() => {
|
|
51
79
|
sig.value = "changed";
|
|
52
80
|
});
|
|
53
81
|
|
|
@@ -57,17 +85,17 @@ describe("@preact/signals-react", () => {
|
|
|
57
85
|
expect(text).to.have.property("data", "changed");
|
|
58
86
|
});
|
|
59
87
|
|
|
60
|
-
it("should update Signal-based Text (in a parent component)", () => {
|
|
88
|
+
it("should update Signal-based Text (in a parent component)", async () => {
|
|
61
89
|
const sig = signal("test");
|
|
62
90
|
function App({ x }: { x: typeof sig }) {
|
|
63
91
|
return <span>{x}</span>;
|
|
64
92
|
}
|
|
65
|
-
render(<App x={sig} />);
|
|
93
|
+
await render(<App x={sig} />);
|
|
66
94
|
|
|
67
95
|
const text = scratch.firstChild!.firstChild!;
|
|
68
96
|
expect(text).to.have.property("data", "test");
|
|
69
97
|
|
|
70
|
-
act(() => {
|
|
98
|
+
await act(() => {
|
|
71
99
|
sig.value = "changed";
|
|
72
100
|
});
|
|
73
101
|
|
|
@@ -79,7 +107,7 @@ describe("@preact/signals-react", () => {
|
|
|
79
107
|
});
|
|
80
108
|
|
|
81
109
|
describe("Component bindings", () => {
|
|
82
|
-
it("should subscribe to signals", () => {
|
|
110
|
+
it("should subscribe to signals", async () => {
|
|
83
111
|
const sig = signal("foo");
|
|
84
112
|
|
|
85
113
|
function App() {
|
|
@@ -87,16 +115,16 @@ describe("@preact/signals-react", () => {
|
|
|
87
115
|
return <p>{value}</p>;
|
|
88
116
|
}
|
|
89
117
|
|
|
90
|
-
render(<App />);
|
|
118
|
+
await render(<App />);
|
|
91
119
|
expect(scratch.textContent).to.equal("foo");
|
|
92
120
|
|
|
93
|
-
act(() => {
|
|
121
|
+
await act(() => {
|
|
94
122
|
sig.value = "bar";
|
|
95
123
|
});
|
|
96
124
|
expect(scratch.textContent).to.equal("bar");
|
|
97
125
|
});
|
|
98
126
|
|
|
99
|
-
it("should activate signal accessed in render", () => {
|
|
127
|
+
it("should activate signal accessed in render", async () => {
|
|
100
128
|
const sig = signal(null);
|
|
101
129
|
|
|
102
130
|
function App() {
|
|
@@ -111,11 +139,14 @@ describe("@preact/signals-react", () => {
|
|
|
111
139
|
return <p>{str}</p>;
|
|
112
140
|
}
|
|
113
141
|
|
|
114
|
-
|
|
115
|
-
|
|
142
|
+
try {
|
|
143
|
+
await render(<App />);
|
|
144
|
+
} catch (e: any) {
|
|
145
|
+
expect.fail(e.stack);
|
|
146
|
+
}
|
|
116
147
|
});
|
|
117
148
|
|
|
118
|
-
it("should not subscribe to child signals", () => {
|
|
149
|
+
it("should not subscribe to child signals", async () => {
|
|
119
150
|
const sig = signal("foo");
|
|
120
151
|
|
|
121
152
|
function Child() {
|
|
@@ -129,10 +160,10 @@ describe("@preact/signals-react", () => {
|
|
|
129
160
|
return <Child />;
|
|
130
161
|
}
|
|
131
162
|
|
|
132
|
-
render(<App />);
|
|
163
|
+
await render(<App />);
|
|
133
164
|
expect(scratch.textContent).to.equal("foo");
|
|
134
165
|
|
|
135
|
-
act(() => {
|
|
166
|
+
await act(() => {
|
|
136
167
|
sig.value = "bar";
|
|
137
168
|
});
|
|
138
169
|
expect(spy).to.be.calledOnce;
|
|
@@ -148,20 +179,40 @@ describe("@preact/signals-react", () => {
|
|
|
148
179
|
|
|
149
180
|
function App() {
|
|
150
181
|
sig.value;
|
|
151
|
-
return useMemo(() => <Inner
|
|
182
|
+
return useMemo(() => <Inner />, []);
|
|
152
183
|
}
|
|
153
184
|
|
|
154
|
-
render(<App />);
|
|
185
|
+
await render(<App />);
|
|
155
186
|
expect(scratch.textContent).to.equal("foo");
|
|
156
187
|
|
|
157
|
-
act(() => {
|
|
188
|
+
await act(() => {
|
|
189
|
+
sig.value = "bar";
|
|
190
|
+
});
|
|
191
|
+
expect(scratch.textContent).to.equal("bar");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should update forwardRef'ed component via signals", async () => {
|
|
195
|
+
const sig = signal("foo");
|
|
196
|
+
|
|
197
|
+
const Inner = forwardRef(() => {
|
|
198
|
+
return <p>{sig.value}</p>;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
function App() {
|
|
202
|
+
return <Inner />;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
await render(<App />);
|
|
206
|
+
expect(scratch.textContent).to.equal("foo");
|
|
207
|
+
|
|
208
|
+
await act(() => {
|
|
158
209
|
sig.value = "bar";
|
|
159
210
|
});
|
|
160
211
|
expect(scratch.textContent).to.equal("bar");
|
|
161
212
|
});
|
|
162
213
|
|
|
163
214
|
it("should consistently rerender in strict mode", async () => {
|
|
164
|
-
const sig = signal
|
|
215
|
+
const sig = signal(-1);
|
|
165
216
|
|
|
166
217
|
const Test = () => <p>{sig.value}</p>;
|
|
167
218
|
const App = () => (
|
|
@@ -170,18 +221,19 @@ describe("@preact/signals-react", () => {
|
|
|
170
221
|
</StrictMode>
|
|
171
222
|
);
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
await render(<App />);
|
|
225
|
+
expect(scratch.textContent).to.equal("-1");
|
|
175
226
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
227
|
+
for (let i = 0; i < 3; i++) {
|
|
228
|
+
await act(async () => {
|
|
229
|
+
sig.value = i;
|
|
179
230
|
});
|
|
180
|
-
expect(scratch.textContent).to.equal(
|
|
231
|
+
expect(scratch.textContent).to.equal("" + i);
|
|
181
232
|
}
|
|
182
233
|
});
|
|
234
|
+
|
|
183
235
|
it("should consistently rerender in strict mode (with memo)", async () => {
|
|
184
|
-
const sig = signal
|
|
236
|
+
const sig = signal(-1);
|
|
185
237
|
|
|
186
238
|
const Test = memo(() => <p>{sig.value}</p>);
|
|
187
239
|
const App = () => (
|
|
@@ -190,16 +242,17 @@ describe("@preact/signals-react", () => {
|
|
|
190
242
|
</StrictMode>
|
|
191
243
|
);
|
|
192
244
|
|
|
193
|
-
|
|
194
|
-
|
|
245
|
+
await render(<App />);
|
|
246
|
+
expect(scratch.textContent).to.equal("-1");
|
|
195
247
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
248
|
+
for (let i = 0; i < 3; i++) {
|
|
249
|
+
await act(async () => {
|
|
250
|
+
sig.value = i;
|
|
199
251
|
});
|
|
200
|
-
expect(scratch.textContent).to.equal(
|
|
252
|
+
expect(scratch.textContent).to.equal("" + i);
|
|
201
253
|
}
|
|
202
254
|
});
|
|
255
|
+
|
|
203
256
|
it("should render static markup of a component", async () => {
|
|
204
257
|
const count = signal(0);
|
|
205
258
|
|
|
@@ -211,16 +264,87 @@ describe("@preact/signals-react", () => {
|
|
|
211
264
|
</pre>
|
|
212
265
|
);
|
|
213
266
|
};
|
|
267
|
+
|
|
268
|
+
await render(<Test />);
|
|
269
|
+
expect(scratch.textContent).to.equal("<code>0</code><code>0</code>");
|
|
270
|
+
|
|
214
271
|
for (let i = 0; i < 3; i++) {
|
|
215
|
-
act(() => {
|
|
272
|
+
await act(async () => {
|
|
216
273
|
count.value += 1;
|
|
217
|
-
render(<Test />);
|
|
218
274
|
});
|
|
219
275
|
expect(scratch.textContent).to.equal(
|
|
220
276
|
`<code>${count.value}</code><code>${count.value}</code>`
|
|
221
277
|
);
|
|
222
278
|
}
|
|
223
279
|
});
|
|
280
|
+
|
|
281
|
+
it("should correctly render components that have useReducer()", async () => {
|
|
282
|
+
const count = signal(0);
|
|
283
|
+
|
|
284
|
+
let increment: () => void;
|
|
285
|
+
const Test = () => {
|
|
286
|
+
const [state, dispatch] = useReducer(
|
|
287
|
+
(state: number, action: number) => {
|
|
288
|
+
return state + action;
|
|
289
|
+
},
|
|
290
|
+
-2
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
increment = () => dispatch(1);
|
|
294
|
+
|
|
295
|
+
const doubled = count.value * 2;
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<pre>
|
|
299
|
+
<code>{state}</code>
|
|
300
|
+
<code>{doubled}</code>
|
|
301
|
+
</pre>
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
await render(<Test />);
|
|
306
|
+
expect(scratch.innerHTML).to.equal(
|
|
307
|
+
"<pre><code>-2</code><code>0</code></pre>"
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
for (let i = 0; i < 3; i++) {
|
|
311
|
+
await act(async () => {
|
|
312
|
+
count.value += 1;
|
|
313
|
+
});
|
|
314
|
+
expect(scratch.innerHTML).to.equal(
|
|
315
|
+
`<pre><code>-2</code><code>${count.value * 2}</code></pre>`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await act(() => {
|
|
320
|
+
increment();
|
|
321
|
+
});
|
|
322
|
+
expect(scratch.innerHTML).to.equal(
|
|
323
|
+
`<pre><code>-1</code><code>${count.value * 2}</code></pre>`
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("useSignal()", () => {
|
|
329
|
+
it("should create a signal from a primitive value", async () => {
|
|
330
|
+
function App() {
|
|
331
|
+
const count = useSignal(1);
|
|
332
|
+
return (
|
|
333
|
+
<div>
|
|
334
|
+
{count}
|
|
335
|
+
<button onClick={() => count.value++}>Increment</button>
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await render(<App />);
|
|
341
|
+
expect(scratch.textContent).to.equal("1Increment");
|
|
342
|
+
|
|
343
|
+
await act(() => {
|
|
344
|
+
scratch.querySelector("button")!.click();
|
|
345
|
+
});
|
|
346
|
+
expect(scratch.textContent).to.equal("2Increment");
|
|
347
|
+
});
|
|
224
348
|
});
|
|
225
349
|
|
|
226
350
|
describe("useSignalEffect()", () => {
|
|
@@ -245,7 +369,7 @@ describe("@preact/signals-react", () => {
|
|
|
245
369
|
);
|
|
246
370
|
}
|
|
247
371
|
|
|
248
|
-
render(<App />);
|
|
372
|
+
await render(<App />);
|
|
249
373
|
expect(scratch.textContent).to.equal("foo");
|
|
250
374
|
|
|
251
375
|
expect(spy).to.have.been.calledOnceWith(
|
|
@@ -256,7 +380,7 @@ describe("@preact/signals-react", () => {
|
|
|
256
380
|
|
|
257
381
|
spy.resetHistory();
|
|
258
382
|
|
|
259
|
-
act(() => {
|
|
383
|
+
await act(() => {
|
|
260
384
|
sig.value = "bar";
|
|
261
385
|
});
|
|
262
386
|
|
|
@@ -294,7 +418,7 @@ describe("@preact/signals-react", () => {
|
|
|
294
418
|
);
|
|
295
419
|
}
|
|
296
420
|
|
|
297
|
-
render(<App />);
|
|
421
|
+
await render(<App />);
|
|
298
422
|
|
|
299
423
|
expect(cleanup).not.to.have.been.called;
|
|
300
424
|
expect(spy).to.have.been.calledOnceWith(
|
|
@@ -304,7 +428,7 @@ describe("@preact/signals-react", () => {
|
|
|
304
428
|
);
|
|
305
429
|
spy.resetHistory();
|
|
306
430
|
|
|
307
|
-
act(() => {
|
|
431
|
+
await act(() => {
|
|
308
432
|
sig.value = "bar";
|
|
309
433
|
});
|
|
310
434
|
|
|
@@ -336,16 +460,18 @@ describe("@preact/signals-react", () => {
|
|
|
336
460
|
return <p ref={ref}>{sig.value}</p>;
|
|
337
461
|
}
|
|
338
462
|
|
|
339
|
-
render(<App />);
|
|
463
|
+
await render(<App />);
|
|
340
464
|
|
|
341
465
|
const child = scratch.firstElementChild;
|
|
342
466
|
|
|
467
|
+
expect(scratch.innerHTML).to.equal("<p>foo</p>");
|
|
343
468
|
expect(cleanup).not.to.have.been.called;
|
|
344
469
|
expect(spy).to.have.been.calledOnceWith("foo", child);
|
|
345
470
|
spy.resetHistory();
|
|
346
471
|
|
|
347
|
-
render(null);
|
|
472
|
+
await render(null);
|
|
348
473
|
|
|
474
|
+
expect(scratch.innerHTML).to.equal("");
|
|
349
475
|
expect(spy).not.to.have.been.called;
|
|
350
476
|
expect(cleanup).to.have.been.calledOnce;
|
|
351
477
|
// @note: React cleans up the ref eagerly, so it's already null by the time the callback runs.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @ts-ignore-next-line
|
|
2
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
3
|
+
|
|
4
|
+
import { signal } from "@preact/signals-react";
|
|
5
|
+
import { createElement } from "react";
|
|
6
|
+
import { Route, Routes, MemoryRouter } from "react-router-dom";
|
|
7
|
+
|
|
8
|
+
import { act, checkHangingAct, createRoot, Root } from "./utils";
|
|
9
|
+
|
|
10
|
+
describe("@preact/signals-react", () => {
|
|
11
|
+
let scratch: HTMLDivElement;
|
|
12
|
+
let root: Root;
|
|
13
|
+
async function render(element: Parameters<Root["render"]>[0]) {
|
|
14
|
+
await act(() => root.render(element));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
scratch = document.createElement("div");
|
|
19
|
+
document.body.appendChild(scratch);
|
|
20
|
+
root = await createRoot(scratch);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
checkHangingAct();
|
|
25
|
+
await act(() => root.unmount());
|
|
26
|
+
scratch.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("react-router-dom", () => {
|
|
30
|
+
it("Route component should render", async () => {
|
|
31
|
+
const name = signal("World")!;
|
|
32
|
+
|
|
33
|
+
function App() {
|
|
34
|
+
return (
|
|
35
|
+
<MemoryRouter>
|
|
36
|
+
<Routes>
|
|
37
|
+
<Route path="/page1" element={<div>Page 1</div>}></Route>
|
|
38
|
+
<Route path="*" element={<div>Hello {name}!</div>}></Route>
|
|
39
|
+
</Routes>
|
|
40
|
+
</MemoryRouter>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await render(<App />);
|
|
45
|
+
|
|
46
|
+
expect(scratch.innerHTML).to.equal("<div>Hello World!</div>");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
package/test/utils.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { act as realAct } from "react-dom/test-utils";
|
|
2
|
+
|
|
3
|
+
export interface Root {
|
|
4
|
+
render(element: JSX.Element | null): void;
|
|
5
|
+
unmount(): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// We need to use createRoot() if it's available, but it's only available in
|
|
9
|
+
// React 18. To enable local testing with React 16 & 17, we'll create a fake
|
|
10
|
+
// createRoot() that uses render() and unmountComponentAtNode() instead.
|
|
11
|
+
let createRootCache: ((container: Element) => Root) | undefined;
|
|
12
|
+
export async function createRoot(container: Element): Promise<Root> {
|
|
13
|
+
if (!createRootCache) {
|
|
14
|
+
try {
|
|
15
|
+
// @ts-expect-error ESBuild will replace this import with a require() call
|
|
16
|
+
// if it resolves react-dom/client. If it doesn't, it will leave the
|
|
17
|
+
// import untouched causing a runtime error we'll handle below.
|
|
18
|
+
const { createRoot } = await import("react-dom/client");
|
|
19
|
+
createRootCache = createRoot;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// @ts-expect-error ESBuild will replace this import with a require() call
|
|
22
|
+
// if it resolves react-dom.
|
|
23
|
+
const { render, unmountComponentAtNode } = await import("react-dom");
|
|
24
|
+
createRootCache = (container: Element) => ({
|
|
25
|
+
render(element: JSX.Element) {
|
|
26
|
+
render(element, container);
|
|
27
|
+
},
|
|
28
|
+
unmount() {
|
|
29
|
+
unmountComponentAtNode(container);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return createRootCache(container);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// When testing using react's production build, we can't use act (React
|
|
39
|
+
// explicitly throws an error in this situation). So instead we'll fake act by
|
|
40
|
+
// just waiting 10ms for React's concurrent rerendering to flush. We'll make a
|
|
41
|
+
// best effort to throw a helpful error in afterEach if we detect that act() was
|
|
42
|
+
// called but not awaited.
|
|
43
|
+
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
44
|
+
|
|
45
|
+
let acting = 0;
|
|
46
|
+
async function prodActShim(cb: () => void | Promise<void>): Promise<void> {
|
|
47
|
+
acting++;
|
|
48
|
+
try {
|
|
49
|
+
await cb();
|
|
50
|
+
await delay(10);
|
|
51
|
+
} finally {
|
|
52
|
+
acting--;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function checkHangingAct() {
|
|
57
|
+
if (acting > 0) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`It appears act() was called but not awaited. This could happen if a test threw an Error or if a test forgot to await a call to act. Make sure to await act() calls in tests.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const act =
|
|
65
|
+
process.env.NODE_ENV === "production"
|
|
66
|
+
? (prodActShim as typeof realAct)
|
|
67
|
+
: realAct;
|