From a51a35cce7d25568f82e8670be510969de035b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 9 May 2025 02:55:33 +0200 Subject: [PATCH 1/4] feat: add tests for #8204 --- .../stories/Table.stories.tsx | 2 +- .../react-aria-components/test/Table.test.js | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 28f110b5482..67a5bb3e119 100644 --- a/packages/react-aria-components/stories/Table.stories.tsx +++ b/packages/react-aria-components/stories/Table.stories.tsx @@ -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..d1557ccaea7 100644 --- a/packages/react-aria-components/test/Table.test.js +++ b/packages/react-aria-components/test/Table.test.js @@ -14,6 +14,7 @@ import {act, fireEvent, installPointerEvent, mockClickDefault, pointerMap, rende import {Button, Cell, Checkbox, Collection, Column, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Label, Modal, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, Tag, TagGroup, TagList, useDragAndDrop, useTableOptions, Virtualizer} from '../'; import {composeStories} from '@storybook/react'; import {DataTransfer, DragEvent} from '@react-aria/dnd/test/mocks'; +import * as module from '../src/Table'; import React, {useMemo, useRef, useState} from 'react'; import {resizingTests} from '@react-aria/table/test/tableResizingTests'; import {setInteractionModality} from '@react-aria/interactions'; @@ -2148,6 +2149,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 +2170,35 @@ 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(); + const spy = jest.spyOn(module.Table, 'render'); + + let tree = await act(() => render()); + + await act(() => stories.makePromise([])); + + expect(spy).toHaveBeenCalledTimes(2); + + let button = tree.getByRole('button'); + await act(() => button.click()); + + expect(spy).toHaveBeenCalledTimes(6); + expect(button).toHaveTextContent('Loading'); + + await act(() => stories.makePromise([])); + + button = tree.getByRole('button'); + expect(button).toHaveTextContent('Load more'); + + expect(spy).toHaveBeenCalledTimes(10); + }); }); describe('shouldSelectOnPressUp', () => { From 86ff05a23f560a4e407f5d1b65051f4fe7882f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 9 May 2025 06:41:22 +0200 Subject: [PATCH 2/4] chore: review comments --- package.json | 6 ++-- packages/dev/docs/package.json | 2 ++ .../stories/Table.stories.tsx | 2 +- .../react-aria-components/test/Table.test.js | 34 +++++++++++------- yarn.lock | 36 +++++++++++-------- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 599528be75d..11aa2888b10 100644 --- a/package.json +++ b/package.json @@ -188,9 +188,9 @@ "postcss-import": "^15.1.0", "prop-types": "^15.6.0", "raf": "^3.4.0", - "react": "^18.2.0", + "react": "19.2.0-canary-38ef6550-20250508", "react-axe": "^3.0.2", - "react-dom": "^18.2.0", + "react-dom": "19.2.0-canary-38ef6550-20250508", "react-frame-component": "^5.0.0", "react-test-renderer": "^18.3.1", "recast": "^0.23", @@ -235,6 +235,8 @@ "ast-types": "0.16.1", "svgo": "^3", "@testing-library/user-event": "patch:@testing-library/user-event@npm%3A14.6.1#~/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch", + "react": "19.2.0-canary-38ef6550-20250508", + "react-dom": "19.2.0-canary-38ef6550-20250508", "@types/node@npm:*": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch", "@types/node@npm:^18.0.0": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch", "@types/node@npm:>= 8": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch" diff --git a/packages/dev/docs/package.json b/packages/dev/docs/package.json index 57660f16ca0..51cf2855301 100644 --- a/packages/dev/docs/package.json +++ b/packages/dev/docs/package.json @@ -31,6 +31,8 @@ "highlight.js": "9.18.1", "markdown-to-jsx": "^6.11.0", "quicklink": "^2.3.0", + "react": "19.2.0-canary-38ef6550-20250508", + "react-dom": "19.2.0-canary-38ef6550-20250508", "react-lowlight": "^2.0.0" }, "peerDependencies": { diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 67a5bb3e119..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}[]}) => { diff --git a/packages/react-aria-components/test/Table.test.js b/packages/react-aria-components/test/Table.test.js index d1557ccaea7..c8cf296dfcf 100644 --- a/packages/react-aria-components/test/Table.test.js +++ b/packages/react-aria-components/test/Table.test.js @@ -14,7 +14,6 @@ import {act, fireEvent, installPointerEvent, mockClickDefault, pointerMap, rende import {Button, Cell, Checkbox, Collection, Column, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Label, Modal, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, Tag, TagGroup, TagList, useDragAndDrop, useTableOptions, Virtualizer} from '../'; import {composeStories} from '@storybook/react'; import {DataTransfer, DragEvent} from '@react-aria/dnd/test/mocks'; -import * as module from '../src/Table'; import React, {useMemo, useRef, useState} from 'react'; import {resizingTests} from '@react-aria/table/test/tableResizingTests'; import {setInteractionModality} from '@react-aria/interactions'; @@ -30,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, useSyncExternalStore}; +}); + function MyColumn(props) { return ( @@ -213,6 +231,7 @@ describe('Table', () => { beforeEach(() => { jest.useFakeTimers(); + mockCollectionUpdate.mockClear(); }); afterEach(() => { @@ -2178,26 +2197,17 @@ describe('Table', () => { } jest.useRealTimers(); - const spy = jest.spyOn(module.Table, 'render'); let tree = await act(() => render()); await act(() => stories.makePromise([])); - expect(spy).toHaveBeenCalledTimes(2); + expect(mockCollectionUpdate).toHaveBeenCalledTimes(1); let button = tree.getByRole('button'); await act(() => button.click()); - expect(spy).toHaveBeenCalledTimes(6); - expect(button).toHaveTextContent('Loading'); - - await act(() => stories.makePromise([])); - - button = tree.getByRole('button'); - expect(button).toHaveTextContent('Load more'); - - expect(spy).toHaveBeenCalledTimes(10); + expect(mockCollectionUpdate).toHaveBeenCalledTimes(2); }); }); diff --git a/yarn.lock b/yarn.lock index c5a874f85cb..267e5685bf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7448,6 +7448,8 @@ __metadata: highlight.js: "npm:9.18.1" markdown-to-jsx: "npm:^6.11.0" quicklink: "npm:^2.3.0" + react: "npm:19.2.0-canary-38ef6550-20250508" + react-dom: "npm:19.2.0-canary-38ef6550-20250508" react-lowlight: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -27459,15 +27461,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0, react-dom@npm:^18.2.0": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:19.2.0-canary-38ef6550-20250508": + version: 19.2.0-canary-38ef6550-20250508 + resolution: "react-dom@npm:19.2.0-canary-38ef6550-20250508" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:0.27.0-canary-38ef6550-20250508" peerDependencies: - react: ^18.3.1 - checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + react: 19.2.0-canary-38ef6550-20250508 + checksum: 10c0/566916b5845fbeb3b55415231a3f3fc8c3e8fcdfe4a271c20acc28469c1c8bb2d2c084fbc2b5842231935db97523fe28c73fb82740f9b7a9a1385eef65ffa725 languageName: node linkType: hard @@ -27716,9 +27717,9 @@ __metadata: postcss-import: "npm:^15.1.0" prop-types: "npm:^15.6.0" raf: "npm:^3.4.0" - react: "npm:^18.2.0" + react: "npm:19.2.0-canary-38ef6550-20250508" react-axe: "npm:^3.0.2" - react-dom: "npm:^18.2.0" + react-dom: "npm:19.2.0-canary-38ef6550-20250508" react-frame-component: "npm:^5.0.0" react-test-renderer: "npm:^18.3.1" recast: "npm:^0.23" @@ -27826,12 +27827,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.2.0": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 +"react@npm:19.2.0-canary-38ef6550-20250508": + version: 19.2.0-canary-38ef6550-20250508 + resolution: "react@npm:19.2.0-canary-38ef6550-20250508" + checksum: 10c0/df2e0415836b4ccf49271583c19d3d26bacc9a3b8af524247bf7a033f2b46b214fbc98a3c6600c6d1f99581a36bb458a5f4dda023889e154e6ba7085c524baba languageName: node linkType: hard @@ -28859,6 +28858,13 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:0.27.0-canary-38ef6550-20250508": + version: 0.27.0-canary-38ef6550-20250508 + resolution: "scheduler@npm:0.27.0-canary-38ef6550-20250508" + checksum: 10c0/cc9c686ff2817b53dcd12f02019e1c8cd03e709d34aa62a52af09796505941b9a27f33e6feb7bb87f44ed1108c430f0edf1cb4cb06419c2b35517ea8c1fa7574 + languageName: node + linkType: hard + "scheduler@npm:^0.23.2": version: 0.23.2 resolution: "scheduler@npm:0.23.2" From ce0173ebe5a8e8c22613a2c6e1655915064d8d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 9 May 2025 06:45:40 +0200 Subject: [PATCH 3/4] chore: revert to main branch --- package.json | 6 ++---- packages/dev/docs/package.json | 2 -- yarn.lock | 36 ++++++++++++++-------------------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 11aa2888b10..599528be75d 100644 --- a/package.json +++ b/package.json @@ -188,9 +188,9 @@ "postcss-import": "^15.1.0", "prop-types": "^15.6.0", "raf": "^3.4.0", - "react": "19.2.0-canary-38ef6550-20250508", + "react": "^18.2.0", "react-axe": "^3.0.2", - "react-dom": "19.2.0-canary-38ef6550-20250508", + "react-dom": "^18.2.0", "react-frame-component": "^5.0.0", "react-test-renderer": "^18.3.1", "recast": "^0.23", @@ -235,8 +235,6 @@ "ast-types": "0.16.1", "svgo": "^3", "@testing-library/user-event": "patch:@testing-library/user-event@npm%3A14.6.1#~/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch", - "react": "19.2.0-canary-38ef6550-20250508", - "react-dom": "19.2.0-canary-38ef6550-20250508", "@types/node@npm:*": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch", "@types/node@npm:^18.0.0": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch", "@types/node@npm:>= 8": "patch:@types/node@npm%3A20.14.13#~/.yarn/patches/@types-node-npm-20.14.13-41f92d384c.patch" diff --git a/packages/dev/docs/package.json b/packages/dev/docs/package.json index 51cf2855301..57660f16ca0 100644 --- a/packages/dev/docs/package.json +++ b/packages/dev/docs/package.json @@ -31,8 +31,6 @@ "highlight.js": "9.18.1", "markdown-to-jsx": "^6.11.0", "quicklink": "^2.3.0", - "react": "19.2.0-canary-38ef6550-20250508", - "react-dom": "19.2.0-canary-38ef6550-20250508", "react-lowlight": "^2.0.0" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 267e5685bf6..c5a874f85cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7448,8 +7448,6 @@ __metadata: highlight.js: "npm:9.18.1" markdown-to-jsx: "npm:^6.11.0" quicklink: "npm:^2.3.0" - react: "npm:19.2.0-canary-38ef6550-20250508" - react-dom: "npm:19.2.0-canary-38ef6550-20250508" react-lowlight: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -27461,14 +27459,15 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:19.2.0-canary-38ef6550-20250508": - version: 19.2.0-canary-38ef6550-20250508 - resolution: "react-dom@npm:19.2.0-canary-38ef6550-20250508" +"react-dom@npm:^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0, react-dom@npm:^18.2.0": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" dependencies: - scheduler: "npm:0.27.0-canary-38ef6550-20250508" + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" peerDependencies: - react: 19.2.0-canary-38ef6550-20250508 - checksum: 10c0/566916b5845fbeb3b55415231a3f3fc8c3e8fcdfe4a271c20acc28469c1c8bb2d2c084fbc2b5842231935db97523fe28c73fb82740f9b7a9a1385eef65ffa725 + react: ^18.3.1 + checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 languageName: node linkType: hard @@ -27717,9 +27716,9 @@ __metadata: postcss-import: "npm:^15.1.0" prop-types: "npm:^15.6.0" raf: "npm:^3.4.0" - react: "npm:19.2.0-canary-38ef6550-20250508" + react: "npm:^18.2.0" react-axe: "npm:^3.0.2" - react-dom: "npm:19.2.0-canary-38ef6550-20250508" + react-dom: "npm:^18.2.0" react-frame-component: "npm:^5.0.0" react-test-renderer: "npm:^18.3.1" recast: "npm:^0.23" @@ -27827,10 +27826,12 @@ __metadata: languageName: node linkType: hard -"react@npm:19.2.0-canary-38ef6550-20250508": - version: 19.2.0-canary-38ef6550-20250508 - resolution: "react@npm:19.2.0-canary-38ef6550-20250508" - checksum: 10c0/df2e0415836b4ccf49271583c19d3d26bacc9a3b8af524247bf7a033f2b46b214fbc98a3c6600c6d1f99581a36bb458a5f4dda023889e154e6ba7085c524baba +"react@npm:^18.2.0": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 languageName: node linkType: hard @@ -28858,13 +28859,6 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.27.0-canary-38ef6550-20250508": - version: 0.27.0-canary-38ef6550-20250508 - resolution: "scheduler@npm:0.27.0-canary-38ef6550-20250508" - checksum: 10c0/cc9c686ff2817b53dcd12f02019e1c8cd03e709d34aa62a52af09796505941b9a27f33e6feb7bb87f44ed1108c430f0edf1cb4cb06419c2b35517ea8c1fa7574 - languageName: node - linkType: hard - "scheduler@npm:^0.23.2": version: 0.23.2 resolution: "scheduler@npm:0.23.2" From 6f1997bde080923313c71e4d636d99b950e32a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 9 May 2025 06:53:03 +0200 Subject: [PATCH 4/4] fix: react 16,17 compat --- packages/react-aria-components/test/Table.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-aria-components/test/Table.test.js b/packages/react-aria-components/test/Table.test.js index c8cf296dfcf..2bbe1dc1fc4 100644 --- a/packages/react-aria-components/test/Table.test.js +++ b/packages/react-aria-components/test/Table.test.js @@ -45,7 +45,7 @@ jest.mock('react', () => { return actual.useSyncExternalStore(fn, ...args); }; - return {...actual, useSyncExternalStore}; + return actual.use ? {...actual, useSyncExternalStore} : actual; }); function MyColumn(props) {