@ipxjs/refract 0.12.0 → 0.13.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/package.json
CHANGED
|
@@ -91,11 +91,13 @@ function childrenToArray(children: unknown): unknown[] {
|
|
|
91
91
|
if (children === undefined || children === null) return [];
|
|
92
92
|
if (!Array.isArray(children)) return [children];
|
|
93
93
|
const out: unknown[] = [];
|
|
94
|
-
const stack = [
|
|
94
|
+
const stack: unknown[] = [children];
|
|
95
95
|
while (stack.length > 0) {
|
|
96
|
-
const child = stack.
|
|
96
|
+
const child = stack.pop();
|
|
97
97
|
if (Array.isArray(child)) {
|
|
98
|
-
|
|
98
|
+
for (let i = child.length - 1; i >= 0; i--) {
|
|
99
|
+
stack.push(child[i]);
|
|
100
|
+
}
|
|
99
101
|
continue;
|
|
100
102
|
}
|
|
101
103
|
if (child === undefined || child === null || typeof child === "boolean") {
|
|
@@ -107,6 +107,7 @@ const secretInternals: ReactSecretInternalsCompat = {
|
|
|
107
107
|
const externalClientInternals = new Set<ReactClientInternalsCompat>();
|
|
108
108
|
const externalSecretInternals = new Set<ReactSecretInternalsCompat>();
|
|
109
109
|
const dispatcherStack: (RefractHookDispatcher | null)[] = [];
|
|
110
|
+
const MAX_EXTERNAL_INTERNALS = 8;
|
|
110
111
|
|
|
111
112
|
let runtimeInitialized = false;
|
|
112
113
|
|
|
@@ -135,7 +136,7 @@ export function registerExternalReactModule(moduleValue: unknown): void {
|
|
|
135
136
|
|
|
136
137
|
const candidateClient = moduleRecord.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
137
138
|
if (candidateClient && typeof candidateClient === "object" && "H" in (candidateClient as Record<string, unknown>)) {
|
|
138
|
-
externalClientInternals
|
|
139
|
+
addBoundedInternal(externalClientInternals, candidateClient as ReactClientInternalsCompat);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
const candidateSecret = moduleRecord.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
@@ -147,12 +148,23 @@ export function registerExternalReactModule(moduleValue: unknown): void {
|
|
|
147
148
|
&& typeof dispatcherHolder.ReactCurrentDispatcher === "object"
|
|
148
149
|
&& "current" in (dispatcherHolder.ReactCurrentDispatcher as Record<string, unknown>)
|
|
149
150
|
) {
|
|
150
|
-
externalSecretInternals
|
|
151
|
+
addBoundedInternal(externalSecretInternals, candidateSecret as ReactSecretInternalsCompat);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
syncDispatcherToExternal();
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
function addBoundedInternal<T>(set: Set<T>, value: T): void {
|
|
158
|
+
if (set.has(value)) return;
|
|
159
|
+
if (set.size >= MAX_EXTERNAL_INTERNALS) {
|
|
160
|
+
const oldest = set.values().next().value as T | undefined;
|
|
161
|
+
if (oldest !== undefined) {
|
|
162
|
+
set.delete(oldest);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
set.add(value);
|
|
166
|
+
}
|
|
167
|
+
|
|
156
168
|
function beforeComponentRender(): void {
|
|
157
169
|
dispatcherStack.push(clientInternals.H);
|
|
158
170
|
setDispatcher(dispatcher);
|
|
@@ -54,8 +54,12 @@ export function renderFiber(vnode: VNode, container: Node): void {
|
|
|
54
54
|
};
|
|
55
55
|
deletions = [];
|
|
56
56
|
isRendering = true;
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
try {
|
|
58
|
+
performWork(rootFiber);
|
|
59
|
+
} finally {
|
|
60
|
+
isRendering = false;
|
|
61
|
+
currentFiber = null;
|
|
62
|
+
}
|
|
59
63
|
const committedDeletions = deletions.slice();
|
|
60
64
|
commitRoot(rootFiber);
|
|
61
65
|
clearAlternates(rootFiber);
|
|
@@ -90,6 +94,7 @@ function processWorkUnit(fiber: Fiber): boolean {
|
|
|
90
94
|
if (!tryHandleRenderError(fiber, error)) throw error;
|
|
91
95
|
} finally {
|
|
92
96
|
runAfterComponentRenderHandlers(fiber);
|
|
97
|
+
currentFiber = null;
|
|
93
98
|
}
|
|
94
99
|
} else if (isFragment) {
|
|
95
100
|
reconcileChildren(fiber, normalizeChildrenProp(fiber.props.children));
|
|
@@ -254,33 +259,57 @@ function getNextDomSibling(fiber: Fiber): Node | null {
|
|
|
254
259
|
/** Collect all DOM nodes from a component/fragment fiber's subtree */
|
|
255
260
|
function collectChildDomNodes(fiber: Fiber): Node[] {
|
|
256
261
|
const nodes: Node[] = [];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
262
|
+
const stack: Fiber[] = [];
|
|
263
|
+
const rootChildren: Fiber[] = [];
|
|
264
|
+
let child = fiber.child;
|
|
265
|
+
while (child) {
|
|
266
|
+
rootChildren.push(child);
|
|
267
|
+
child = child.sibling;
|
|
268
|
+
}
|
|
269
|
+
for (let i = rootChildren.length - 1; i >= 0; i--) {
|
|
270
|
+
stack.push(rootChildren[i]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
while (stack.length > 0) {
|
|
274
|
+
const current = stack.pop()!;
|
|
275
|
+
if (isPortalFiber(current)) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (current.dom) {
|
|
279
|
+
nodes.push(current.dom);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const children: Fiber[] = [];
|
|
283
|
+
let next = current.child;
|
|
284
|
+
while (next) {
|
|
285
|
+
children.push(next);
|
|
286
|
+
next = next.sibling;
|
|
287
|
+
}
|
|
288
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
289
|
+
stack.push(children[i]);
|
|
269
290
|
}
|
|
270
291
|
}
|
|
271
|
-
walk(fiber.child);
|
|
272
292
|
return nodes;
|
|
273
293
|
}
|
|
274
294
|
|
|
275
295
|
/** Get the first committed DOM node in a fiber subtree */
|
|
276
296
|
function getFirstCommittedDom(fiber: Fiber): Node | null {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
297
|
+
const stack: Fiber[] = [fiber];
|
|
298
|
+
while (stack.length > 0) {
|
|
299
|
+
const current = stack.pop()!;
|
|
300
|
+
if (isPortalFiber(current)) continue;
|
|
301
|
+
if (current.dom && !(current.flags & PLACEMENT)) {
|
|
302
|
+
return current.dom;
|
|
303
|
+
}
|
|
304
|
+
const children: Fiber[] = [];
|
|
305
|
+
let child = current.child;
|
|
306
|
+
while (child) {
|
|
307
|
+
children.push(child);
|
|
308
|
+
child = child.sibling;
|
|
309
|
+
}
|
|
310
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
311
|
+
stack.push(children[i]);
|
|
312
|
+
}
|
|
284
313
|
}
|
|
285
314
|
return null;
|
|
286
315
|
}
|
|
@@ -296,69 +325,74 @@ function commitRoot(rootFiber: Fiber): void {
|
|
|
296
325
|
}
|
|
297
326
|
|
|
298
327
|
function commitWork(fiber: Fiber): void {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
328
|
+
const stack: Fiber[] = [fiber];
|
|
329
|
+
while (stack.length > 0) {
|
|
330
|
+
const current = stack.pop()!;
|
|
331
|
+
if (isPortalFiber(current)) {
|
|
332
|
+
current.flags = 0;
|
|
333
|
+
if (current.sibling) stack.push(current.sibling);
|
|
334
|
+
if (current.child) stack.push(current.child);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
305
337
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// Component/fragment: move all child DOM nodes
|
|
322
|
-
const domNodes = collectChildDomNodes(fiber);
|
|
323
|
-
const before = getNextDomSibling(fiber);
|
|
324
|
-
for (const dom of domNodes) {
|
|
325
|
-
if (before) {
|
|
326
|
-
parentDom.insertBefore(dom, before);
|
|
338
|
+
let parentFiber = current.parent;
|
|
339
|
+
while (parentFiber && !parentFiber.dom) {
|
|
340
|
+
parentFiber = parentFiber.parent;
|
|
341
|
+
}
|
|
342
|
+
const parentDom = parentFiber?.dom;
|
|
343
|
+
|
|
344
|
+
if (parentDom) {
|
|
345
|
+
if (current.flags & PLACEMENT) {
|
|
346
|
+
if (current.dom) {
|
|
347
|
+
const before = getNextDomSibling(current);
|
|
348
|
+
if (before) {
|
|
349
|
+
parentDom.insertBefore(current.dom, before);
|
|
350
|
+
} else {
|
|
351
|
+
parentDom.appendChild(current.dom);
|
|
352
|
+
}
|
|
327
353
|
} else {
|
|
328
|
-
|
|
354
|
+
// Component/fragment: move all child DOM nodes
|
|
355
|
+
const domNodes = collectChildDomNodes(current);
|
|
356
|
+
const before = getNextDomSibling(current);
|
|
357
|
+
for (const dom of domNodes) {
|
|
358
|
+
if (before) {
|
|
359
|
+
parentDom.insertBefore(dom, before);
|
|
360
|
+
} else {
|
|
361
|
+
parentDom.appendChild(dom);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
} else if (current.flags & UPDATE && current.dom) {
|
|
366
|
+
if (current.type === "TEXT") {
|
|
367
|
+
const oldValue = current.alternate?.props.nodeValue;
|
|
368
|
+
if (oldValue !== current.props.nodeValue) {
|
|
369
|
+
current.dom.textContent = current.props.nodeValue as string;
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
applyProps(
|
|
373
|
+
current.dom as HTMLElement,
|
|
374
|
+
current.alternate?.props ?? {},
|
|
375
|
+
current.props,
|
|
376
|
+
);
|
|
329
377
|
}
|
|
330
378
|
}
|
|
331
379
|
}
|
|
332
|
-
} else if (fiber.flags & UPDATE && fiber.dom) {
|
|
333
|
-
if (fiber.type === "TEXT") {
|
|
334
|
-
const oldValue = fiber.alternate?.props.nodeValue;
|
|
335
|
-
if (oldValue !== fiber.props.nodeValue) {
|
|
336
|
-
fiber.dom.textContent = fiber.props.nodeValue as string;
|
|
337
|
-
}
|
|
338
|
-
} else {
|
|
339
|
-
applyProps(
|
|
340
|
-
fiber.dom as HTMLElement,
|
|
341
|
-
fiber.alternate?.props ?? {},
|
|
342
|
-
fiber.props,
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
380
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
// Handle ref prop — only on mount or when ref changes (like React)
|
|
382
|
+
if (current.dom && current.props.ref) {
|
|
383
|
+
const oldRef = current.alternate?.props.ref;
|
|
384
|
+
if (current.flags & PLACEMENT || current.props.ref !== oldRef) {
|
|
385
|
+
if (oldRef && oldRef !== current.props.ref) {
|
|
386
|
+
setRef(oldRef, null);
|
|
387
|
+
}
|
|
388
|
+
setRef(current.props.ref, current.dom);
|
|
353
389
|
}
|
|
354
|
-
setRef(fiber.props.ref, fiber.dom);
|
|
355
390
|
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
fiber.flags = 0;
|
|
359
391
|
|
|
360
|
-
|
|
361
|
-
|
|
392
|
+
current.flags = 0;
|
|
393
|
+
if (current.sibling) stack.push(current.sibling);
|
|
394
|
+
if (current.child) stack.push(current.child);
|
|
395
|
+
}
|
|
362
396
|
}
|
|
363
397
|
|
|
364
398
|
function setRef(ref: unknown, value: Node | null): void {
|
|
@@ -373,41 +407,51 @@ function setRef(ref: unknown, value: Node | null): void {
|
|
|
373
407
|
* Alternates are only needed during reconciliation; retaining them
|
|
374
408
|
* creates an ever-growing chain of old fiber trees. */
|
|
375
409
|
function clearAlternates(fiber: Fiber | null): void {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
410
|
+
if (!fiber) return;
|
|
411
|
+
const stack: Fiber[] = [fiber];
|
|
412
|
+
while (stack.length > 0) {
|
|
413
|
+
const current = stack.pop()!;
|
|
414
|
+
current.alternate = null;
|
|
415
|
+
if (current.sibling) stack.push(current.sibling);
|
|
416
|
+
if (current.child) stack.push(current.child);
|
|
380
417
|
}
|
|
381
418
|
}
|
|
382
419
|
|
|
383
420
|
function commitDeletion(fiber: Fiber): void {
|
|
384
421
|
runCleanups(fiber);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
if (isPortalFiber(fiber)) {
|
|
390
|
-
let child: Fiber | null = fiber.child;
|
|
391
|
-
while (child) {
|
|
392
|
-
commitDeletion(child);
|
|
393
|
-
child = child.sibling;
|
|
422
|
+
walkSubtree(fiber, (node) => {
|
|
423
|
+
if (node.dom && node.props.ref) {
|
|
424
|
+
setRef(node.props.ref, null);
|
|
394
425
|
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
let child: Fiber | null = fiber.child;
|
|
400
|
-
while (child) {
|
|
401
|
-
commitDeletion(child);
|
|
402
|
-
child = child.sibling;
|
|
426
|
+
});
|
|
427
|
+
walkSubtree(fiber, (node) => {
|
|
428
|
+
if (!isPortalFiber(node) && node.dom) {
|
|
429
|
+
node.dom.parentNode?.removeChild(node.dom);
|
|
403
430
|
}
|
|
404
|
-
}
|
|
431
|
+
});
|
|
405
432
|
}
|
|
406
433
|
|
|
407
434
|
function runCleanups(fiber: Fiber): void {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
435
|
+
walkSubtree(fiber, (node) => {
|
|
436
|
+
runFiberCleanupHandlers(node);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function walkSubtree(root: Fiber, visit: (fiber: Fiber) => void): void {
|
|
441
|
+
const stack: Fiber[] = [root];
|
|
442
|
+
while (stack.length > 0) {
|
|
443
|
+
const current = stack.pop()!;
|
|
444
|
+
visit(current);
|
|
445
|
+
const children: Fiber[] = [];
|
|
446
|
+
let child = current.child;
|
|
447
|
+
while (child) {
|
|
448
|
+
children.push(child);
|
|
449
|
+
child = child.sibling;
|
|
450
|
+
}
|
|
451
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
452
|
+
stack.push(children[i]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
411
455
|
}
|
|
412
456
|
|
|
413
457
|
const pendingContainers = new Set<Node>();
|
|
@@ -473,8 +517,12 @@ function flushRenders(): void {
|
|
|
473
517
|
};
|
|
474
518
|
deletions = [];
|
|
475
519
|
isRendering = true;
|
|
476
|
-
|
|
477
|
-
|
|
520
|
+
try {
|
|
521
|
+
performWork(newRoot);
|
|
522
|
+
} finally {
|
|
523
|
+
isRendering = false;
|
|
524
|
+
currentFiber = null;
|
|
525
|
+
}
|
|
478
526
|
const committedDeletions = deletions.slice();
|
|
479
527
|
commitRoot(newRoot);
|
|
480
528
|
clearAlternates(newRoot);
|
|
@@ -36,9 +36,11 @@ export function useState<T>(initial: T | (() => T)): [T, (value: T | ((prev: T)
|
|
|
36
36
|
// Create a stable setter that is reused across renders (like React)
|
|
37
37
|
if (!hook._setter) {
|
|
38
38
|
hook._setter = (value: T | ((prev: T) => T)) => {
|
|
39
|
+
if (!hook._fiber) return;
|
|
39
40
|
const action = typeof value === "function"
|
|
40
41
|
? value as (prev: T) => T
|
|
41
42
|
: () => value;
|
|
43
|
+
if (!hook.queue) hook.queue = [];
|
|
42
44
|
(hook.queue as ((prev: T) => T)[]).push(action);
|
|
43
45
|
scheduleRender(hook._fiber!);
|
|
44
46
|
};
|
|
@@ -191,6 +193,8 @@ export function useReducer<S, A, I>(
|
|
|
191
193
|
// Create stable dispatch (like React)
|
|
192
194
|
if (!hook._dispatch) {
|
|
193
195
|
hook._dispatch = (action: A) => {
|
|
196
|
+
if (!hook._fiber) return;
|
|
197
|
+
if (!hook.queue) hook.queue = [];
|
|
194
198
|
(hook.queue as A[]).push(action);
|
|
195
199
|
scheduleRender(hook._fiber!);
|
|
196
200
|
};
|
|
@@ -33,6 +33,8 @@ function cleanupFiberEffects(fiber: Fiber): void {
|
|
|
33
33
|
if (!fiber.hooks) return;
|
|
34
34
|
|
|
35
35
|
for (const hook of fiber.hooks) {
|
|
36
|
+
hook.queue = undefined;
|
|
37
|
+
hook._fiber = undefined;
|
|
36
38
|
const state = hook.state;
|
|
37
39
|
if (!state || typeof state !== "object") continue;
|
|
38
40
|
const effectState = state as { cleanup?: () => void; pending?: boolean };
|
package/tests/hooks.test.ts
CHANGED
|
@@ -71,6 +71,25 @@ describe("hooks", () => {
|
|
|
71
71
|
expect(renderCount).toBe(2);
|
|
72
72
|
expect(container.querySelector("span")!.textContent).toBe("3");
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it("ignores setState after unmount", async () => {
|
|
76
|
+
let setCount!: (v: number | ((p: number) => number)) => void;
|
|
77
|
+
let renders = 0;
|
|
78
|
+
function Counter() {
|
|
79
|
+
const [count, sc] = useState(0);
|
|
80
|
+
setCount = sc;
|
|
81
|
+
renders++;
|
|
82
|
+
return createElement("span", null, String(count));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render(createElement(Counter, null), container);
|
|
86
|
+
render(createElement("div", null, "gone"), container);
|
|
87
|
+
|
|
88
|
+
expect(() => setCount(1)).not.toThrow();
|
|
89
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
90
|
+
expect(container.textContent).toBe("gone");
|
|
91
|
+
expect(renders).toBe(1);
|
|
92
|
+
});
|
|
74
93
|
});
|
|
75
94
|
|
|
76
95
|
describe("useEffect", () => {
|
|
@@ -269,5 +288,69 @@ describe("hooks", () => {
|
|
|
269
288
|
await new Promise((r) => setTimeout(r, 10));
|
|
270
289
|
expect(container.querySelector("span")!.textContent).toBe("2");
|
|
271
290
|
});
|
|
291
|
+
|
|
292
|
+
it("ignores dispatch after unmount", async () => {
|
|
293
|
+
type Action = { type: "inc" };
|
|
294
|
+
let dispatch!: (action: Action) => void;
|
|
295
|
+
let renders = 0;
|
|
296
|
+
function Counter() {
|
|
297
|
+
const [count, d] = useReducer((state: number, action: Action) => {
|
|
298
|
+
if (action.type === "inc") return state + 1;
|
|
299
|
+
return state;
|
|
300
|
+
}, 0);
|
|
301
|
+
dispatch = d;
|
|
302
|
+
renders++;
|
|
303
|
+
return createElement("span", null, String(count));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
render(createElement(Counter, null), container);
|
|
307
|
+
render(createElement("div", null, "gone"), container);
|
|
308
|
+
|
|
309
|
+
expect(() => dispatch({ type: "inc" })).not.toThrow();
|
|
310
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
311
|
+
expect(container.textContent).toBe("gone");
|
|
312
|
+
expect(renders).toBe(1);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("cleanup scoping", () => {
|
|
317
|
+
it("only cleans up deleted subtree effects", async () => {
|
|
318
|
+
const leftCleanup = vi.fn();
|
|
319
|
+
const rightCleanup = vi.fn();
|
|
320
|
+
let setShowLeft!: (v: boolean) => void;
|
|
321
|
+
|
|
322
|
+
function Left() {
|
|
323
|
+
useEffect(() => leftCleanup, []);
|
|
324
|
+
return createElement("span", null, "left");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function Right() {
|
|
328
|
+
useEffect(() => rightCleanup, []);
|
|
329
|
+
return createElement("span", null, "right");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function App() {
|
|
333
|
+
const [showLeft, ss] = useState(true);
|
|
334
|
+
setShowLeft = ss;
|
|
335
|
+
return createElement(
|
|
336
|
+
"div",
|
|
337
|
+
null,
|
|
338
|
+
showLeft ? createElement(Left, { key: "left" }) : null,
|
|
339
|
+
createElement(Right, { key: "right" }),
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
render(createElement(App, null), container);
|
|
344
|
+
flushPassiveEffects();
|
|
345
|
+
expect(leftCleanup).toHaveBeenCalledTimes(0);
|
|
346
|
+
expect(rightCleanup).toHaveBeenCalledTimes(0);
|
|
347
|
+
|
|
348
|
+
setShowLeft(false);
|
|
349
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
350
|
+
flushPassiveEffects();
|
|
351
|
+
|
|
352
|
+
expect(leftCleanup).toHaveBeenCalledTimes(1);
|
|
353
|
+
expect(rightCleanup).toHaveBeenCalledTimes(0);
|
|
354
|
+
});
|
|
272
355
|
});
|
|
273
356
|
});
|