Skip to content

Commit a6874ac

Browse files
committed
add sample subpage content
1 parent 92c58fb commit a6874ac

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed

packages/dev/s2-docs/pages/react-aria/Table.mdx

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {InstallCommand} from '../../src/InstallCommand';
12
import {Layout} from '../../src/Layout';
23
export default Layout;
34

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

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

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

9193
## Content
9294

93-
`Table` follows the [Collection Components API](collections.html?component=Table), accepting both static and dynamic collections.
95+
`Table` follows the [Collection Components API](collections.html?component=Table), accepting both static and dynamic collections.
9496
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.
9597

9698
```tsx render
@@ -718,3 +720,88 @@ function ReorderableTable() {
718720
### TableLoadMoreItem
719721

720722
<PropTable component={docs.exports.TableLoadMoreItem} links={docs.links} showDescription />
723+
724+
725+
## Testing
726+
727+
TODO make this a subpage
728+
729+
730+
### General setup
731+
732+
Table features long press interactions on its rows depending on the row actions provided and if the user is interacting with the table on
733+
a touch device. Please see the following sections in the general testing documentation for more information on how to handle these
734+
behaviors in your test suite.
735+
736+
[Timers](testing.html#timers)
737+
738+
[Long press](testing.html#simulating-user-long-press)
739+
740+
### Test utils
741+
742+
`@react-aria/test-utils` offers common table interaction utilities which you may find helpful when writing tests. To install, simply
743+
add it to your dev dependencies via your preferred package manager.
744+
745+
<InstallCommand pkg="@react-aria/test-utils" flags="--dev" />
746+
747+
<InlineAlert variant="notice">
748+
<Heading>Requirements</Heading>
749+
<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>
750+
</InlineAlert>
751+
752+
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
753+
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.
754+
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.
755+
756+
```ts
757+
// Table.test.ts
758+
import {render, within} from '@testing-library/react';
759+
import {User} from '@react-aria/test-utils';
760+
761+
let testUtilUser = new User({interactionType: 'mouse', advanceTimer: jest.advanceTimersByTime});
762+
// ...
763+
764+
it('Table can toggle row selection', async function () {
765+
// Render your test component/app and initialize the table tester
766+
let {getByTestId} = render(
767+
<Table data-testid="test-table" selectionMode="multiple">
768+
...
769+
</Table>
770+
);
771+
let tableTester = testUtilUser.createTester('Table', {root: getByTestId('test-table')});
772+
expect(tableTester.selectedRows).toHaveLength(0);
773+
774+
await tableTester.toggleSelectAll();
775+
expect(tableTester.selectedRows).toHaveLength(10);
776+
777+
await tableTester.toggleRowSelection({row: 2});
778+
expect(tableTester.selectedRows).toHaveLength(9);
779+
let checkbox = within(tableTester.rows[2]).getByRole('checkbox');
780+
expect(checkbox).not.toBeChecked();
781+
782+
await tableTester.toggleSelectAll();
783+
expect(tableTester.selectedRows).toHaveLength(10);
784+
expect(checkbox).toBeChecked();
785+
786+
await tableTester.toggleSelectAll();
787+
expect(tableTester.selectedRows).toHaveLength(0);
788+
});
789+
```
790+
791+
See below for the full definition of the `User` and the `Table` tester.
792+
793+
<ClassAPI links={testUtilDocs.links} class={testUtilDocs.exports.User} />
794+
<ClassAPI links={testUtilDocs.links} class={testUtilDocs.exports.TableTester} />
795+
796+
### Testing FAQ
797+
798+
- When using the test utils, what if a certain interaction errors or doesn't seem to result in the expected state?
799+
800+
In cases like this, first double check your test setup and make sure that your test is rendering your table in its expected
801+
state before the test util interaction call. If everything looks correct, you can always fall back to simulating interactions manually,
802+
and using the test util to query your table's state post interaction.
803+
804+
- The tester doesn't offer a specific interaction flow, what should I do?
805+
806+
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
807+
whatever simulated user flow you add.

packages/dev/s2-docs/pages/react-aria/testing.mdx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,86 @@ userEvent.tab();
9999
userEvent.click(document.activeElement);
100100
```
101101

102+
## Test setup and common gotchas
103+
104+
### Timers
105+
106+
If you are using fake timers in your test suite, be aware that you may need to advance your timers after various interactions. We have `requestAnimationFrame` calls in various underlying hooks that you will need to also handle by advancing your timers in the tests.
107+
This happens most prominently in our collection components after selection. In Jest, this can be handled by calling `act(() => jest.runAllTimers());` but you may require more precise control
108+
depending on the other time-sensitive behavior you are testing. Please see [Jest's timer docs](https://jestjs.io/docs/timer-mocks) or the equivalent docs of your test frameworks for more information on how to do so.
109+
It is also a good idea to run all timers to completion after each test case to avoid any left over transitions or timeouts that a component may have setup during its lifecycle.
110+
111+
```tsx
112+
afterEach(() => {
113+
act(() => jest.runAllTimers());
114+
});
115+
```
116+
117+
Consider adding a `act(() => jest.runAllTimers());` after your simulated user interaction if you run into a test failure that looks like the following:
118+
119+
```
120+
TestingLibraryElementError: Unable to find an accessible element with the role "listbox"
121+
```
122+
123+
If you are using real timers instead, you can await a particular state of your app to be reached. If you are using React Testing Library, you can perform a `waitFor` query
124+
to wait for a dialog to appear:
125+
126+
```tsx
127+
await waitFor(() => {
128+
expect(getByRole('dialog')).toBeInTheDocument();
129+
});
130+
```
131+
132+
### Simulating user long press
133+
134+
Some components like Menu support long press operations. Unfortunately, the approach of using the userEvent library to simulate a press event and running timers to hit the
135+
long press internal timer threshold isn't sufficient due to `useLongPress`'s usage of `PointerEvent` and our own detection of `virtual` vs `mouse`/`touch` pointer types. Mock [PointerEvent](https://github.com/adobe/react-spectrum/blob/16ff0efac57eebeb1cd601ab376ce7c58a4e4efd/packages/dev/test-utils/src/events.ts#L70-L103)
136+
globally and use `fireEvent` from `@testing-library/react` to properly simulate these long press events in your tests.
137+
If you are using Jest, you can call our <TypeLink links={testUtilDocs.links} type={testUtilDocs.exports.installPointerEvent} /> utility to automatically set up and tear down this mock in your test.
138+
Additionally, if you are using fake timers and don't need to control the specific timings around the long press interaction, feel free to use our <TypeLink links={testUtilDocs.links} type={testUtilDocs.exports.triggerLongPress} /> utility as shown below.
139+
140+
```tsx
141+
import {fireEvent} from '@testing-library/react';
142+
import {installPointerEvent, triggerLongPress} from '@react-aria/test-utils';
143+
installPointerEvent();
144+
145+
// In test case
146+
let button = getByRole('button');
147+
148+
// With fireEvent and specific timing control
149+
fireEvent.pointerDown(el, {pointerType: 'touch'});
150+
act(() => jest.advanceTimersByTime(800));
151+
fireEvent.up(el, {pointerType: 'touch'});
152+
153+
// With triggerLongPress
154+
triggerLongPress(button);
155+
```
156+
157+
### Simulating move event
158+
159+
Components like ColorArea, ColorSlider, ColorWheel, and Slider each feature a draggable handle that a user can interact with to change the component's value. Similar to long press, the interactions offered by userEvent library aren't sufficient to trigger
160+
the underlying event handlers governing these drag/move operations. [Mock MouseEvent globally](https://github.com/adobe/react-spectrum/blob/f40b575e38837e1aa7cabf0431406e81275d118a/packages/%40react-aria/test-utils/src/testSetup.ts#L16-L36) and `fireEvent` from `@testing-library/react` to simulate these drag/move events in your tests.
161+
If you are using Jest, you can call our <TypeLink links={testUtilDocs.links} type={testUtilDocs.exports.installMouseEvent} /> utility to automatically set up and tear down this mock in your test. Additionally, the track dimensions
162+
for the draggable handle should be mocked so that the move operation calculations can be properly computed.
163+
164+
```tsx
165+
import {fireEvent} from '@testing-library/react';
166+
import {installMouseEvent} from '@react-aria/test-utils';
167+
installMouseEvent();
168+
169+
beforeAll(() => {
170+
jest.spyOn(window.HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(() => ({top: 0, left: 0, width: 100, height: 10}));
171+
})
172+
173+
// In test case
174+
let sliderThumb = getByRole('slider').parentElement;
175+
176+
// With fireEvent, move thumb from 0 to 50
177+
fireEvent.mouseDown(thumb, {clientX: 0, pageX: 0});
178+
fireEvent.mouseMove(thumb, {pageX: 50});
179+
fireEvent.mouseUp(thumb, {pageX: 50});
180+
```
181+
102182
## React Aria test utils
103183

104184
TODO can't place this next to the header here

0 commit comments

Comments
 (0)