Skip to content

Commit 620ebd7

Browse files
committed
add sample subpage content
1 parent 78fb92b commit 620ebd7

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

@@ -89,7 +91,7 @@ export const tags = ['data', 'grid'];
8991

9092
## Content
9193

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

9597
```tsx render
@@ -707,3 +709,88 @@ function ReorderableTable() {
707709
### TableLoadMoreItem
708710

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