Skip to content

Commit 0ef9421

Browse files
authored
feat: add test for improvement of collections in React transitions (#8209)
* feat: add tests for #8204 * chore: review comments * chore: revert to main branch * fix: react 16,17 compat
1 parent 4893475 commit 0ef9421

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

packages/react-aria-components/stories/Table.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {useAsyncList, useListData} from 'react-stately';
2121

2222
export default {
2323
title: 'React Aria Components',
24-
excludeStories: ['DndTable']
24+
excludeStories: ['DndTable', 'makePromise']
2525
};
2626

2727
const ReorderableTable = ({initialItems}: {initialItems: {id: string, name: string}[]}) => {
@@ -1010,7 +1010,7 @@ const items: Launch[] = [
10101010
{id: 3, mission_name: 'RatSat', launch_year: 2009}
10111011
];
10121012

1013-
function makePromise(items: Launch[]) {
1013+
export function makePromise(items: Launch[]) {
10141014
return new Promise(resolve => setTimeout(() => resolve(items), 1000));
10151015
}
10161016

packages/react-aria-components/test/Table.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ let {
2828
TableWithSuspense
2929
} = composeStories(stories);
3030

31+
const mockCollectionUpdate = jest.fn();
32+
33+
jest.mock('react', () => {
34+
const actual = jest.requireActual('react');
35+
36+
const useSyncExternalStore = (subscribe, ...args) => {
37+
const fn = actual.useCallback((onStoreChange) => {
38+
subscribe(() => {
39+
mockCollectionUpdate();
40+
onStoreChange();
41+
});
42+
}, [subscribe]);
43+
44+
return actual.useSyncExternalStore(fn, ...args);
45+
};
46+
47+
return actual.use ? {...actual, useSyncExternalStore} : actual;
48+
});
49+
3150
function MyColumn(props) {
3251
return (
3352
<Column {...props}>
@@ -211,6 +230,7 @@ describe('Table', () => {
211230

212231
beforeEach(() => {
213232
jest.useFakeTimers();
233+
mockCollectionUpdate.mockClear();
214234
});
215235

216236
afterEach(() => {
@@ -2274,6 +2294,8 @@ describe('Table', () => {
22742294

22752295
let promise = act(() => button.click());
22762296

2297+
expect(button).toHaveTextContent('Loading');
2298+
22772299
rows = tree.getAllByRole('row');
22782300
expect(rows).toHaveLength(3);
22792301
expect(rows[1]).toHaveTextContent('FalconSat');
@@ -2293,6 +2315,26 @@ describe('Table', () => {
22932315
expect(rows[3]).toHaveTextContent('Trailblazer');
22942316
expect(rows[4]).toHaveTextContent('RatSat');
22952317
});
2318+
2319+
it('should not render excessively in React Suspense with transitions', async () => {
2320+
// Only supported in React 19.
2321+
if (!React.use) {
2322+
return;
2323+
}
2324+
2325+
jest.useRealTimers();
2326+
2327+
let tree = await act(() => render(<TableWithSuspense reactTransition />));
2328+
2329+
await act(() => stories.makePromise([]));
2330+
2331+
expect(mockCollectionUpdate).toHaveBeenCalledTimes(1);
2332+
2333+
let button = tree.getByRole('button');
2334+
await act(() => button.click());
2335+
2336+
expect(mockCollectionUpdate).toHaveBeenCalledTimes(2);
2337+
});
22962338
});
22972339

22982340
describe('shouldSelectOnPressUp', () => {

0 commit comments

Comments
 (0)