diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 28f110b5482..3feb787f895 100644 --- a/packages/react-aria-components/stories/Table.stories.tsx +++ b/packages/react-aria-components/stories/Table.stories.tsx @@ -22,7 +22,7 @@ import {useLoadMore} from '@react-aria/utils'; export default { title: 'React Aria Components', - excludeStories: ['DndTable'] + excludeStories: ['DndTable', 'makePromise'] }; const ReorderableTable = ({initialItems}: {initialItems: {id: string, name: string}[]}) => { @@ -1019,7 +1019,7 @@ const items: Launch[] = [ {id: 3, mission_name: 'RatSat', launch_year: 2009} ]; -function makePromise(items: Launch[]) { +export function makePromise(items: Launch[]) { return new Promise(resolve => setTimeout(() => resolve(items), 1000)); } diff --git a/packages/react-aria-components/test/Table.test.js b/packages/react-aria-components/test/Table.test.js index 31a2f7bed0f..2bbe1dc1fc4 100644 --- a/packages/react-aria-components/test/Table.test.js +++ b/packages/react-aria-components/test/Table.test.js @@ -29,6 +29,25 @@ let { TableWithSuspense } = composeStories(stories); +const mockCollectionUpdate = jest.fn(); + +jest.mock('react', () => { + const actual = jest.requireActual('react'); + + const useSyncExternalStore = (subscribe, ...args) => { + const fn = actual.useCallback((onStoreChange) => { + subscribe(() => { + mockCollectionUpdate(); + onStoreChange(); + }); + }, [subscribe]); + + return actual.useSyncExternalStore(fn, ...args); + }; + + return actual.use ? {...actual, useSyncExternalStore} : actual; +}); + function MyColumn(props) { return ( @@ -212,6 +231,7 @@ describe('Table', () => { beforeEach(() => { jest.useFakeTimers(); + mockCollectionUpdate.mockClear(); }); afterEach(() => { @@ -2148,6 +2168,8 @@ describe('Table', () => { let promise = act(() => button.click()); + expect(button).toHaveTextContent('Loading'); + rows = tree.getAllByRole('row'); expect(rows).toHaveLength(3); expect(rows[1]).toHaveTextContent('FalconSat'); @@ -2167,6 +2189,26 @@ describe('Table', () => { expect(rows[3]).toHaveTextContent('Trailblazer'); expect(rows[4]).toHaveTextContent('RatSat'); }); + + it('should not render excessively in React Suspense with transitions', async () => { + // Only supported in React 19. + if (!React.use) { + return; + } + + jest.useRealTimers(); + + let tree = await act(() => render()); + + await act(() => stories.makePromise([])); + + expect(mockCollectionUpdate).toHaveBeenCalledTimes(1); + + let button = tree.getByRole('button'); + await act(() => button.click()); + + expect(mockCollectionUpdate).toHaveBeenCalledTimes(2); + }); }); describe('shouldSelectOnPressUp', () => {