jest.mock('react-dom', () => { const actual = jest.requireActual('react-dom'); const flushSync = jest.fn((...args: any[]) => actual.flushSync(...args)); return { ...actual, flushSync, default: { ...actual, flushSync } }; }); jest.mock('react', () => { const actual = jest.requireActual('react'); const useSyncExternalStore = jest.fn((...args: any[]) => actual.useSyncExternalStore(...args), ); const use = actual.use ? jest.fn((...args: any[]) => actual.use(...args)) : undefined; const wrapped = { ...actual, useSyncExternalStore, ...(use ? { use } : {}) }; return { ...wrapped, default: wrapped }; }); import { DataProvider, getDefaultManagers, useCache, useController, useLive, useSuspense, } from '@data-client/react'; import { act, fireEvent, render } from '@testing-library/react'; import { makeGetTodo, mockTodoState } from '__tests__/concurrentFixtures'; import { expectNoImmediateCommit, expectOnlyNormalCommits, resetCommits, } from '__tests__/reactCommitProbe'; import { recordConsoleErrors } from '__tests__/streamingHarness'; import React, { startTransition, Suspense, useState, useSyncExternalStore, useTransition, version, } from 'react'; const LegacyReact = version.startsWith('16') || version.startsWith('17'); const describeConcurrent = LegacyReact ? describe.skip : describe; const resolvers: Record< string, (value: { id: string; title: string }) => void > = {}; const fetchTodo = jest.fn( ({ id }: { id: string }) => new Promise<{ id: string; title: string }>(resolve => { resolvers[id] = resolve; }), ); const getTodo = makeGetTodo(fetchTodo, { pollFrequency: 60000 }); const stateA = mockTodoState(getTodo, ['A']); const noDevManagers = getDefaultManagers({ devToolsManager: null }); function TodoView({ id }: { id: string }) { const todo = useSuspense(getTodo, { id }); return
{todo.title}
; } const flushSync = jest.mocked( (require('react-dom') as typeof import('react-dom')).flushSync, ); const useSyncExternalStoreMock = jest.mocked(useSyncExternalStore); describeConcurrent('startTransition over useSuspense', () => { let consoleRecorder: ReturnType; let controller: ReturnType; function Probe() { controller = useController(); return null; } function App() { const [id, setId] = useState('A'); const [isPending, start] = useTransition(); const [text, setText] = useState(''); return (
{String(isPending)} setText(e.target.value)} /> loading

}>
); } beforeAll(() => { consoleRecorder = recordConsoleErrors(); }); beforeEach(() => { consoleRecorder.unexpected.length = 0; flushSync.mockClear(); useSyncExternalStoreMock.mockClear(); fetchTodo.mockClear(); for (const key of Object.keys(resolvers)) delete resolvers[key]; }); afterEach(() => { expect(flushSync).not.toHaveBeenCalled(); expect(useSyncExternalStoreMock).not.toHaveBeenCalled(); expect(consoleRecorder.unexpected).toEqual([]); }); afterAll(() => { consoleRecorder.restore(); }); it('startTransition over useSuspense keeps the committed UI, accepts urgent input, then commits B', async () => { const { getByTestId, queryByText } = render( , ); expect(getByTestId('todo').textContent).toBe('todo A'); act(() => { fireEvent.click(getByTestId('next')); }); expect(queryByText('loading')).toBeNull(); expect(getByTestId('todo').textContent).toBe('todo A'); expect(getByTestId('pending').textContent).toBe('true'); expect(fetchTodo).toHaveBeenCalledTimes(1); expect(fetchTodo).toHaveBeenCalledWith({ id: 'B' }); act(() => { fireEvent.change(getByTestId('input'), { target: { value: 'x' } }); }); expect((getByTestId('input') as HTMLInputElement).value).toBe('x'); expect(getByTestId('pending').textContent).toBe('true'); expect(queryByText('loading')).toBeNull(); expect(getByTestId('todo').textContent).toBe('todo A'); resetCommits(); await act(async () => { resolvers.B({ id: 'B', title: 'todo B' }); }); expect(getByTestId('todo').textContent).toBe('todo B'); expect(getByTestId('pending').textContent).toBe('false'); expectNoImmediateCommit('fetch resolution'); }); it('controller.setResponse inside startTransition commits at non-sync priority', () => { const stateB = mockTodoState(getTodo, ['B']); function ShowB() { const todo = useSuspense(getTodo, { id: 'B' }); return (
{todo.title}
); } const { getByTestId } = render( loading

}>
, ); expect(getByTestId('todo').textContent).toBe('todo B'); resetCommits(); act(() => { startTransition(() => { controller.setResponse( getTodo, { id: 'B' }, { id: 'B', title: 'todo B v2', }, ); }); }); expect(getByTestId('todo').textContent).toBe('todo B v2'); expectOnlyNormalCommits('transition setResponse'); }); it('a store write outside any event commits at default priority', async () => { const { getByTestId } = render( , ); resetCommits(); await act(async () => { await Promise.resolve(); controller.setResponse( getTodo, { id: 'A' }, { id: 'A', title: 'todo A v2', }, ); }); expect(getByTestId('todo').textContent).toBe('todo A v2'); expectOnlyNormalCommits('default-lane setResponse'); }); it('useLive / useCache consumers re-render from Context on a transition publish', () => { function Consumers() { const cached = useCache(getTodo, { id: 'A' }); const live = useLive(getTodo, { id: 'A' }); return (
{cached?.title} {live.title}
); } const { getByTestId } = render( loading

}>
, ); expect(getByTestId('cache').textContent).toBe('todo A'); expect(getByTestId('live').textContent).toBe('todo A'); resetCommits(); act(() => { startTransition(() => { controller.setResponse( getTodo, { id: 'A' }, { id: 'A', title: 'todo A v2', }, ); }); }); expect(getByTestId('cache').textContent).toBe('todo A v2'); expect(getByTestId('live').textContent).toBe('todo A v2'); expectOnlyNormalCommits('transition publish to useCache/useLive'); }); });