/** * A/B concurrency tests for pending updates in tap-scheduled sub-roots. * * Since the pending-update queue removal, dispatches apply directly into * reducer cells, and a React-driven render of a useTapRoot sub-root consumes * pending entries — including render attempts React later discards. These * tests run the same scenario in two worlds: * * react — the hooks run directly in the component (React's own update * queue semantics are the oracle) * tap — the hooks run in a useTapRoot sub-root read via * useSyncExternalStore * * and assert the committed, observable output matches at every checkpoint. * Render-attempt interleavings are deliberately NOT compared (scheduling * differs legitimately); committed state must not. */ /* oxlint-disable react/rules-of-hooks -- the world branch is fixed per test run */ import { describe, it, expect, afterEach } from "vitest"; import { StrictMode, Suspense, startTransition, use, useMemo, useReducer, useState, useSyncExternalStore, } from "react"; import { render, screen, act, cleanup } from "@testing-library/react"; import { useTapRoot } from "../../index"; import { cleanupAllResources } from "../test-utils"; afterEach(() => { cleanupAllResources(); cleanup(); }); type WorldName = "react" | "tap"; const useInWorld = (world: WorldName, useBody: () => T): T => { if (world === "react") return useBody(); const root = useTapRoot(function Sub() { return useBody(); }); return useSyncExternalStore(root.subscribe, root.getValue, root.getValue); }; /** * Two chained reducer cells; `add` dispatches to both in one event. The * return value is identity-stable per state (the useSyncExternalStore * snapshot contract for tap roots). */ const useCounters = () => { const [a, addA] = useReducer((s: number, n: number) => s + n, 0); const [b, addB] = useReducer((s: number, n: number) => s + n, 100); return useMemo( () => ({ a, b, add: (n: number) => { addA(n); addB(n * 2); }, }), [a, b], ); }; const ShouldNeverFallback = () => { throw new Error("should never fallback"); }; describe("pending updates under concurrent rendering (react vs tap)", () => { it("a forced re-render between dispatch and flush applies each update exactly once", async () => { const run = async (world: WorldName): Promise => { const checkpoints: string[] = []; let api!: ReturnType; function App() { const counters = useInWorld(world, useCounters); api = counters; const [, setTick] = useState(0); return ( <>