jest.mock('react', () => { const actual = jest.requireActual('react'); const useSyncExternalStore = jest.fn((..._args: any[]) => { throw new Error( 'useSyncExternalStore must not be called by public cache reads', ); }); const use = actual.use ? jest.fn((...args: any[]) => actual.use(...args)) : undefined; const useContext = jest.fn((...args: any[]) => actual.useContext(...args)); const wrapped = { ...actual, useSyncExternalStore, useContext, ...(use ? { use } : {}), }; return { ...wrapped, default: wrapped }; }); import { initialState } from '@data-client/core'; import { Endpoint, Entity } from '@data-client/endpoint'; import { DataProvider, getDefaultManagers, StateContext, useCache, useDLE, useError, useFetch, useLive, useQuery, useSuspense, } from '@data-client/react'; import { mockInitialState } from '@data-client/test'; import { render } from '@testing-library/react'; import React, { Suspense, version } from 'react'; import { renderToString } from 'react-dom/server'; import useCacheState from '../useCacheState'; const reactMajor = Number(version.split('.')[0]); const describeIf19 = reactMajor >= 19 ? describe : describe.skip; const describeIf18 = reactMajor === 18 ? describe : describe.skip; class Todo extends Entity { id = ''; title = ''; } const getTodo = new Endpoint( ({ id }: { id: string }) => Promise.resolve({ id, title: `todo ${id}` }), { schema: Todo, name: 'getTodo', pollFrequency: 60000 }, ); const noDevManagers = getDefaultManagers({ devToolsManager: null }); const stateA = mockInitialState([ { endpoint: getTodo, args: [{ id: 'A' }], response: { id: 'A', title: 'todo A' }, }, ]); function Consumer() { const state = useCacheState(); return {Object.keys(state.entities).join(',') || 'empty'}; } describe('useCacheState', () => { it('returns StateContext without calling useSyncExternalStore', () => { const live = { ...initialState, entities: { Todo: { '9': { id: '9' } } }, }; expect( renderToString( , ), ).toBe('Todo'); }); it('public read hooks render cache hits without useSyncExternalStore', () => { function Readers() { const suspense = useSuspense(getTodo, { id: 'A' }); const cached = useCache(getTodo, { id: 'A' }); const live = useLive(getTodo, { id: 'A' }); const queried = useQuery(Todo, { id: 'A' }); const dle = useDLE(getTodo, { id: 'A' }); const fetched = useFetch(getTodo, { id: 'A' }); const error = useError(getTodo, { id: 'A' }); return (
{suspense.title} {cached?.title} {live.title} {queried?.title} {dle.data?.title} {(fetched as any)?.value?.title ?? 'skip'} {error ? 'err' : 'ok'}
); } const { getByTestId } = render( loading

}>
, ); expect(getByTestId('suspense').textContent).toBe('todo A'); expect(getByTestId('cache').textContent).toBe('todo A'); expect(getByTestId('live').textContent).toBe('todo A'); expect(getByTestId('query').textContent).toBe('todo A'); expect(getByTestId('dle').textContent).toBe('todo A'); expect(getByTestId('fetch').textContent).toBe('todo A'); expect(getByTestId('error').textContent).toBe('ok'); }); describeIf19('reads go through React.use(StateContext) on React 19', () => { it('wrapped use received StateContext during public cache-hit render', () => { const useMock = jest.mocked(React.use); useMock.mockClear(); function Readers() { useSuspense(getTodo, { id: 'A' }); useCache(getTodo, { id: 'A' }); return null; } render( , ); expect(useMock.mock.calls.some(call => call[0] === StateContext)).toBe( true, ); }); }); describeIf18('reads go through useContext(StateContext) on React 18', () => { it('wrapped useContext received StateContext (labeled fallback)', () => { const useContextMock = jest.mocked(React.useContext); useContextMock.mockClear(); function Readers() { useSuspense(getTodo, { id: 'A' }); useCache(getTodo, { id: 'A' }); return null; } render( , ); expect( useContextMock.mock.calls.some(call => call[0] === StateContext), ).toBe(true); }); }); it('reads live state without useSyncExternalStore (React 16/17 shape)', () => { jest.resetModules(); jest.doMock('react', () => { const actual = jest.requireActual('react'); const { useSyncExternalStore, ...legacy } = actual; return { ...legacy, default: legacy }; }); try { jest.isolateModules(() => { const LegacyReactModule = require('react'); expect(LegacyReactModule.useSyncExternalStore).toBeUndefined(); const useCacheStateLegacy = require('../useCacheState').default; const { renderToString: renderString } = require('react-dom/server'); const ctx = require('../../context'); const live = { ...initialState, entities: { Todo: { '9': { id: '9' } } }, }; const ConsumerLegacy = () => { const state = useCacheStateLegacy(); return LegacyReactModule.createElement( 'i', null, Object.keys(state.entities).join(',') || 'empty', ); }; const html = renderString( LegacyReactModule.createElement( ctx.StateContext.Provider, { value: live }, LegacyReactModule.createElement(ConsumerLegacy), ), ); expect(html).toBe('Todo'); }); } finally { jest.dontMock('react'); jest.resetModules(); } }); });