Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/@react-aria/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,15 @@ export {triggerLongPress} from './events';
export {installMouseEvent, installPointerEvent} from './testSetup';
export {pointerMap} from './userEventMaps';
export {User} from './user';
// TODO: had to export these for the docs, but not sure why I didn't have to do
// so for the v3 docs?
export {ComboBoxTester} from './combobox';
export {GridListTester} from './gridlist';
export {ListBoxTester} from './listbox';
export {MenuTester} from './menu';
export {SelectTester} from './select';
export {TableTester} from './table';
export {TabsTester} from './tabs';
export {TreeTester} from './tree';
Comment on lines +17 to +26
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why but something like import comboboxUtils from 'docs:@react-aria/test-utils/src/combobox.ts'; works in the old RAC docs but I had to do the above to get access to the tester types in the new docs


export type {UserOpts} from './types';
89 changes: 88 additions & 1 deletion packages/dev/s2-docs/pages/react-aria/Table.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {InstallCommand} from '../../src/InstallCommand';
import {Layout} from '../../src/Layout';
export default Layout;

Expand All @@ -6,6 +7,7 @@ import vanillaDocs from 'docs:vanilla-starter/Table';
import '../../tailwind/tailwind.css';
import Anatomy from 'react-aria-components/docs/TableAnatomy.svg';
import {InlineAlert, Heading, Content} from '@react-spectrum/s2'
import testUtilDocs from 'docs:@react-aria/test-utils';

export const tags = ['data', 'grid'];

Expand Down Expand Up @@ -90,7 +92,7 @@ export const tags = ['data', 'grid'];

## Content

`Table` follows the [Collection Components API](collections.html?component=Table), accepting both static and dynamic collections.
`Table` follows the [Collection Components API](collections.html?component=Table), accepting both static and dynamic collections.
In this example, both the columns and the rows are provided to the table via a render function, enabling the user to hide and show columns and add additional rows.

```tsx render
Expand Down Expand Up @@ -718,3 +720,88 @@ function ReorderableTable() {
### TableLoadMoreItem

<PropTable component={docs.exports.TableLoadMoreItem} links={docs.links} showDescription />


## Testing

TODO make this a subpage


Comment on lines +723 to +729
Copy link
Member Author

@LFDanLu LFDanLu Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is what I imagine the content of the subpage would look like if we want to go that route (I suppose it would need to link out to a separate page then? Do we just want it to appear ONLY in the ToC and then link to the page when clicked? Or should there still be a link in the actual docs body content?) The content is roughly standalone, except linking to the testing page for general stuff like handling timers/long press. Open to opinions on what should live in the sub page vs the general testing page OR if one or the other shouldn't exist in favor of the other.

Table is representative of the more lengthy testing sub pages that may exist, for a component that doesn't have a test util tied to it we'd probably only have General setup or simply just link back to the main testing page if at all. Open to opinions for this as well

### General setup

Table features long press interactions on its rows depending on the row actions provided and if the user is interacting with the table on
a touch device. Please see the following sections in the general testing documentation for more information on how to handle these
behaviors in your test suite.

[Timers](testing.html#timers)

[Long press](testing.html#simulating-user-long-press)

### Test utils

`@react-aria/test-utils` offers common table interaction utilities which you may find helpful when writing tests. To install, simply
add it to your dev dependencies via your preferred package manager.

<InstallCommand pkg="@react-aria/test-utils" flags="--dev" />

<InlineAlert variant="notice">
<Heading>Requirements</Heading>
<Content>Please note that this library uses [@testing-library/react@16](https://www.npmjs.com/package/@testing-library/react) and [@testing-library/user-event@14](https://www.npmjs.com/package/@testing-library/user-event). This means that you need to be on React 18+ in order for these utilities to work.</Content>
</InlineAlert>

Once installed, you can access the `User` that `@react-aria/test-utils` provides in your test file as shown below. This user only needs to be initialized once and then can be used to generate
the `Table` tester in your test cases. This gives you access to `Table` specific utilities that you can then call within your test to query for specific subcomponents or simulate common interactions.
The example test case below shows how you might go about setting up the `Table` tester, use it to simulate row selection, and verify the table's state after each interaction.

```ts
// Table.test.ts
import {render, within} from '@testing-library/react';
import {User} from '@react-aria/test-utils';

let testUtilUser = new User({interactionType: 'mouse', advanceTimer: jest.advanceTimersByTime});
// ...

it('Table can toggle row selection', async function () {
// Render your test component/app and initialize the table tester
let {getByTestId} = render(
<Table data-testid="test-table" selectionMode="multiple">
...
</Table>
);
let tableTester = testUtilUser.createTester('Table', {root: getByTestId('test-table')});
expect(tableTester.selectedRows).toHaveLength(0);

await tableTester.toggleSelectAll();
expect(tableTester.selectedRows).toHaveLength(10);

await tableTester.toggleRowSelection({row: 2});
expect(tableTester.selectedRows).toHaveLength(9);
let checkbox = within(tableTester.rows[2]).getByRole('checkbox');
expect(checkbox).not.toBeChecked();

await tableTester.toggleSelectAll();
expect(tableTester.selectedRows).toHaveLength(10);
expect(checkbox).toBeChecked();

await tableTester.toggleSelectAll();
expect(tableTester.selectedRows).toHaveLength(0);
});
```

See below for the full definition of the `User` and the `Table` tester.

<ClassAPI links={testUtilDocs.links} class={testUtilDocs.exports.User} />
<ClassAPI links={testUtilDocs.links} class={testUtilDocs.exports.TableTester} />

### Testing FAQ
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit general but I imagine that the sub page should be mostly self contained for the most part. If the FAQ expands to have more than test util specific stuff, then we can re-evaluate


> When using the test utils, what if a certain interaction errors or doesn't seem to result in the expected state?

In cases like this, first double check your test setup and make sure that your test is rendering your table in its expected
state before the test util interaction call. If everything looks correct, you can always fall back to simulating interactions manually,
and using the test util to query your table's state post interaction.

> The tester doesn't offer a specific interaction flow, what should I do?

Whenever the table tester queries its rows/cells/etc or triggers a user flow, it does so against the current state of the table. Therefore the table test can be used alongside
whatever simulated user flow you add.
Loading