@react-aria/test-utils 1.0.0-alpha.2 → 1.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/combobox.main.js.map +1 -1
- package/dist/combobox.module.js.map +1 -1
- package/dist/gridlist.main.js.map +1 -1
- package/dist/gridlist.module.js.map +1 -1
- package/dist/menu.main.js +57 -13
- package/dist/menu.main.js.map +1 -1
- package/dist/menu.mjs +57 -13
- package/dist/menu.module.js +57 -13
- package/dist/menu.module.js.map +1 -1
- package/dist/select.main.js.map +1 -1
- package/dist/select.module.js.map +1 -1
- package/dist/table.main.js +3 -7
- package/dist/table.main.js.map +1 -1
- package/dist/table.mjs +4 -8
- package/dist/table.module.js +4 -8
- package/dist/table.module.js.map +1 -1
- package/dist/testSetup.main.js +2 -6
- package/dist/testSetup.main.js.map +1 -1
- package/dist/testSetup.mjs +2 -6
- package/dist/testSetup.module.js +2 -6
- package/dist/testSetup.module.js.map +1 -1
- package/dist/types.d.ts +17 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/user.main.js +2 -2
- package/dist/user.main.js.map +1 -1
- package/dist/user.mjs +2 -2
- package/dist/user.module.js +2 -2
- package/dist/user.module.js.map +1 -1
- package/package.json +5 -4
- package/src/combobox.ts +1 -1
- package/src/gridlist.ts +1 -1
- package/src/menu.ts +81 -19
- package/src/select.ts +1 -1
- package/src/table.ts +6 -15
- package/src/testSetup.ts +2 -6
- package/src/user.ts +1 -1
package/src/menu.ts
CHANGED
|
@@ -15,16 +15,18 @@ import {BaseTesterOpts, UserOpts} from './user';
|
|
|
15
15
|
import {triggerLongPress} from './events';
|
|
16
16
|
|
|
17
17
|
export interface MenuOptions extends UserOpts, BaseTesterOpts {
|
|
18
|
-
user
|
|
18
|
+
user?: any,
|
|
19
|
+
isSubmenu?: boolean
|
|
19
20
|
}
|
|
20
21
|
export class MenuTester {
|
|
21
22
|
private user;
|
|
22
23
|
private _interactionType: UserOpts['interactionType'];
|
|
23
24
|
private _advanceTimer: UserOpts['advanceTimer'];
|
|
24
|
-
private _trigger: HTMLElement;
|
|
25
|
+
private _trigger: HTMLElement | undefined;
|
|
26
|
+
private _isSubmenu: boolean = false;
|
|
25
27
|
|
|
26
28
|
constructor(opts: MenuOptions) {
|
|
27
|
-
let {root, user, interactionType, advanceTimer} = opts;
|
|
29
|
+
let {root, user, interactionType, advanceTimer, isSubmenu} = opts;
|
|
28
30
|
this.user = user;
|
|
29
31
|
this._interactionType = interactionType || 'mouse';
|
|
30
32
|
this._advanceTimer = advanceTimer;
|
|
@@ -41,6 +43,8 @@ export class MenuTester {
|
|
|
41
43
|
this._trigger = root;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
46
|
+
|
|
47
|
+
this._isSubmenu = isSubmenu || false;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
setInteractionType = (type: UserOpts['interactionType']) => {
|
|
@@ -49,12 +53,12 @@ export class MenuTester {
|
|
|
49
53
|
|
|
50
54
|
// TODO: this has been common to select as well, maybe make select use it? Or make a generic method. Will need to make error messages generic
|
|
51
55
|
// One difference will be that it supports long press as well
|
|
52
|
-
open = async (opts: {needsLongPress?: boolean, interactionType?: UserOpts['interactionType']} = {}) => {
|
|
56
|
+
open = async (opts: {needsLongPress?: boolean, interactionType?: UserOpts['interactionType'], direction?: 'up' | 'down'} = {}) => {
|
|
53
57
|
let {
|
|
54
58
|
needsLongPress,
|
|
55
|
-
interactionType = this._interactionType
|
|
59
|
+
interactionType = this._interactionType,
|
|
60
|
+
direction
|
|
56
61
|
} = opts;
|
|
57
|
-
|
|
58
62
|
let trigger = this.trigger;
|
|
59
63
|
let isDisabled = trigger.hasAttribute('disabled');
|
|
60
64
|
if (interactionType === 'mouse' || interactionType === 'touch') {
|
|
@@ -70,8 +74,16 @@ export class MenuTester {
|
|
|
70
74
|
await this.user.pointer({target: trigger, keys: '[TouchA]'});
|
|
71
75
|
}
|
|
72
76
|
} else if (interactionType === 'keyboard' && !isDisabled) {
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
if (direction === 'up') {
|
|
78
|
+
act(() => trigger.focus());
|
|
79
|
+
await this.user.keyboard('[ArrowUp]');
|
|
80
|
+
} else if (direction === 'down') {
|
|
81
|
+
act(() => trigger.focus());
|
|
82
|
+
await this.user.keyboard('[ArrowDown]');
|
|
83
|
+
} else {
|
|
84
|
+
act(() => trigger.focus());
|
|
85
|
+
await this.user.keyboard('[Enter]');
|
|
86
|
+
}
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
await waitFor(() => {
|
|
@@ -95,24 +107,37 @@ export class MenuTester {
|
|
|
95
107
|
|
|
96
108
|
// TODO: also very similar to select, barring potential long press support
|
|
97
109
|
// Close on select is also kinda specific?
|
|
98
|
-
selectOption = async (opts: {
|
|
110
|
+
selectOption = async (opts: {
|
|
111
|
+
option?: HTMLElement,
|
|
112
|
+
optionText?: string,
|
|
113
|
+
menuSelectionMode?: 'single' | 'multiple',
|
|
114
|
+
needsLongPress?: boolean,
|
|
115
|
+
closesOnSelect?: boolean,
|
|
116
|
+
interactionType?: UserOpts['interactionType'],
|
|
117
|
+
keyboardActivation?: 'Space' | 'Enter'
|
|
118
|
+
}) => {
|
|
99
119
|
let {
|
|
100
120
|
optionText,
|
|
101
121
|
menuSelectionMode = 'single',
|
|
102
122
|
needsLongPress,
|
|
103
123
|
closesOnSelect = true,
|
|
104
124
|
option,
|
|
105
|
-
interactionType = this._interactionType
|
|
125
|
+
interactionType = this._interactionType,
|
|
126
|
+
keyboardActivation = 'Enter'
|
|
106
127
|
} = opts;
|
|
107
128
|
let trigger = this.trigger;
|
|
108
|
-
|
|
129
|
+
|
|
130
|
+
if (!trigger.getAttribute('aria-controls') && !trigger.hasAttribute('aria-expanded')) {
|
|
109
131
|
await this.open({needsLongPress});
|
|
110
132
|
}
|
|
111
133
|
|
|
112
134
|
let menu = this.menu;
|
|
113
135
|
if (menu) {
|
|
114
136
|
if (!option && optionText) {
|
|
115
|
-
option = within(menu).getByText(optionText);
|
|
137
|
+
option = (within(menu!).getByText(optionText).closest('[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]'))! as HTMLElement;
|
|
138
|
+
}
|
|
139
|
+
if (!option) {
|
|
140
|
+
throw new Error('No option found in the menu.');
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
if (interactionType === 'keyboard') {
|
|
@@ -120,8 +145,8 @@ export class MenuTester {
|
|
|
120
145
|
act(() => menu.focus());
|
|
121
146
|
}
|
|
122
147
|
|
|
123
|
-
await this.
|
|
124
|
-
await this.user.keyboard(
|
|
148
|
+
await this.keyboardNavigateToOption({option});
|
|
149
|
+
await this.user.keyboard(`[${keyboardActivation}]`);
|
|
125
150
|
} else {
|
|
126
151
|
if (interactionType === 'mouse') {
|
|
127
152
|
await this.user.click(option);
|
|
@@ -129,8 +154,9 @@ export class MenuTester {
|
|
|
129
154
|
await this.user.pointer({target: option, keys: '[TouchA]'});
|
|
130
155
|
}
|
|
131
156
|
}
|
|
157
|
+
act(() => {jest.runAllTimers();});
|
|
132
158
|
|
|
133
|
-
if (option && option.getAttribute('href') == null && option.getAttribute('aria-haspopup') == null && menuSelectionMode === 'single' && closesOnSelect) {
|
|
159
|
+
if (option && option.getAttribute('href') == null && option.getAttribute('aria-haspopup') == null && menuSelectionMode === 'single' && closesOnSelect && keyboardActivation !== 'Space' && !this._isSubmenu) {
|
|
134
160
|
await waitFor(() => {
|
|
135
161
|
if (document.activeElement !== trigger) {
|
|
136
162
|
throw new Error(`Expected the document.activeElement after selecting an option to be the menu trigger but got ${document.activeElement}`);
|
|
@@ -156,6 +182,7 @@ export class MenuTester {
|
|
|
156
182
|
needsLongPress,
|
|
157
183
|
interactionType = this._interactionType
|
|
158
184
|
} = opts;
|
|
185
|
+
|
|
159
186
|
let trigger = this.trigger;
|
|
160
187
|
let isDisabled = trigger.hasAttribute('disabled');
|
|
161
188
|
if (!trigger.getAttribute('aria-controls') && !isDisabled) {
|
|
@@ -171,8 +198,18 @@ export class MenuTester {
|
|
|
171
198
|
submenu = within(menu).getByText(submenuTriggerText);
|
|
172
199
|
}
|
|
173
200
|
|
|
174
|
-
let submenuTriggerTester = new MenuTester({user: this.user, interactionType:
|
|
175
|
-
|
|
201
|
+
let submenuTriggerTester = new MenuTester({user: this.user, interactionType: this._interactionType, root: submenu, isSubmenu: true});
|
|
202
|
+
if (interactionType === 'mouse') {
|
|
203
|
+
await this.user.pointer({target: submenu});
|
|
204
|
+
act(() => {jest.runAllTimers();});
|
|
205
|
+
} else if (interactionType === 'keyboard') {
|
|
206
|
+
await this.keyboardNavigateToOption({option: submenu});
|
|
207
|
+
await this.user.keyboard('[ArrowRight]');
|
|
208
|
+
act(() => {jest.runAllTimers();});
|
|
209
|
+
} else {
|
|
210
|
+
await submenuTriggerTester.open();
|
|
211
|
+
}
|
|
212
|
+
|
|
176
213
|
|
|
177
214
|
return submenuTriggerTester;
|
|
178
215
|
}
|
|
@@ -181,6 +218,28 @@ export class MenuTester {
|
|
|
181
218
|
return null;
|
|
182
219
|
};
|
|
183
220
|
|
|
221
|
+
keyboardNavigateToOption = async (opts: {option: HTMLElement}) => {
|
|
222
|
+
let {option} = opts;
|
|
223
|
+
let options = this.options;
|
|
224
|
+
let targetIndex = options.indexOf(option);
|
|
225
|
+
if (targetIndex === -1) {
|
|
226
|
+
throw new Error('Option provided is not in the menu');
|
|
227
|
+
}
|
|
228
|
+
if (document.activeElement === this.menu) {
|
|
229
|
+
await this.user.keyboard('[ArrowDown]');
|
|
230
|
+
}
|
|
231
|
+
let currIndex = options.indexOf(document.activeElement as HTMLElement);
|
|
232
|
+
if (targetIndex === -1) {
|
|
233
|
+
throw new Error('ActiveElement is not in the menu');
|
|
234
|
+
}
|
|
235
|
+
let direction = targetIndex > currIndex ? 'down' : 'up';
|
|
236
|
+
|
|
237
|
+
for (let i = 0; i < Math.abs(targetIndex - currIndex); i++) {
|
|
238
|
+
await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
|
|
184
243
|
close = async () => {
|
|
185
244
|
let menu = this.menu;
|
|
186
245
|
if (menu) {
|
|
@@ -202,6 +261,9 @@ export class MenuTester {
|
|
|
202
261
|
};
|
|
203
262
|
|
|
204
263
|
get trigger() {
|
|
264
|
+
if (!this._trigger) {
|
|
265
|
+
throw new Error('No trigger element found for menu.');
|
|
266
|
+
}
|
|
205
267
|
return this._trigger;
|
|
206
268
|
}
|
|
207
269
|
|
|
@@ -210,9 +272,9 @@ export class MenuTester {
|
|
|
210
272
|
return menuId ? document.getElementById(menuId) : undefined;
|
|
211
273
|
}
|
|
212
274
|
|
|
213
|
-
get options(): HTMLElement[]
|
|
275
|
+
get options(): HTMLElement[] {
|
|
214
276
|
let menu = this.menu;
|
|
215
|
-
let options = [];
|
|
277
|
+
let options: HTMLElement[] = [];
|
|
216
278
|
if (menu) {
|
|
217
279
|
options = within(menu).queryAllByRole('menuitem');
|
|
218
280
|
if (options.length === 0) {
|
package/src/select.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {BaseTesterOpts, UserOpts} from './user';
|
|
|
15
15
|
|
|
16
16
|
export interface SelectOptions extends UserOpts, BaseTesterOpts {
|
|
17
17
|
// TODO: I think the type grabbed from the testing library dist for UserEvent is breaking the build, will need to figure out a better place to grab from
|
|
18
|
-
user
|
|
18
|
+
user?: any
|
|
19
19
|
}
|
|
20
20
|
export class SelectTester {
|
|
21
21
|
private user;
|
package/src/table.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {act, fireEvent, waitFor, within} from '@testing-library/react';
|
|
|
14
14
|
import {BaseTesterOpts, UserOpts} from './user';
|
|
15
15
|
import {pressElement, triggerLongPress} from './events';
|
|
16
16
|
export interface TableOptions extends UserOpts, BaseTesterOpts {
|
|
17
|
-
user
|
|
17
|
+
user?: any,
|
|
18
18
|
advanceTimer: UserOpts['advanceTimer']
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -68,15 +68,6 @@ export class TableTester {
|
|
|
68
68
|
await pressElement(this.user, cell, interactionType);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
// Handle cases where the table may transition in response to the row selection/deselection
|
|
73
|
-
await act(async () => {
|
|
74
|
-
if (this._advanceTimer == null) {
|
|
75
|
-
throw new Error('No advanceTimers provided for table transition.');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await this._advanceTimer(200);
|
|
79
|
-
});
|
|
80
71
|
};
|
|
81
72
|
|
|
82
73
|
toggleSort = async (opts: {index?: number, text?: string, interactionType?: UserOpts['interactionType']} = {}) => {
|
|
@@ -145,12 +136,12 @@ export class TableTester {
|
|
|
145
136
|
}
|
|
146
137
|
|
|
147
138
|
// Handle cases where the table may transition in response to the row selection/deselection
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
139
|
+
if (!this._advanceTimer) {
|
|
140
|
+
throw new Error('No advanceTimers provided for table transition.');
|
|
141
|
+
}
|
|
152
142
|
|
|
153
|
-
|
|
143
|
+
await act(async () => {
|
|
144
|
+
await this._advanceTimer?.(200);
|
|
154
145
|
});
|
|
155
146
|
|
|
156
147
|
await waitFor(() => {
|
package/src/testSetup.ts
CHANGED
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
* Enables reading pageX/pageY from fireEvent.mouse*(..., {pageX: ..., pageY: ...}).
|
|
15
15
|
*/
|
|
16
16
|
export function installMouseEvent() {
|
|
17
|
+
let oldMouseEvent = MouseEvent;
|
|
17
18
|
beforeAll(() => {
|
|
18
|
-
let oldMouseEvent = MouseEvent;
|
|
19
|
-
// @ts-ignore
|
|
20
19
|
global.MouseEvent = class FakeMouseEvent extends MouseEvent {
|
|
21
20
|
_init: {pageX: number, pageY: number};
|
|
22
21
|
constructor(name, init) {
|
|
@@ -30,12 +29,9 @@ export function installMouseEvent() {
|
|
|
30
29
|
return this._init.pageY;
|
|
31
30
|
}
|
|
32
31
|
};
|
|
33
|
-
// @ts-ignore
|
|
34
|
-
global.MouseEvent.oldMouseEvent = oldMouseEvent;
|
|
35
32
|
});
|
|
36
33
|
afterAll(() => {
|
|
37
|
-
|
|
38
|
-
global.MouseEvent = global.MouseEvent.oldMouseEvent;
|
|
34
|
+
global.MouseEvent = oldMouseEvent;
|
|
39
35
|
});
|
|
40
36
|
}
|
|
41
37
|
|
package/src/user.ts
CHANGED
|
@@ -68,6 +68,6 @@ export class User {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
createTester<T extends PatternNames>(patternName: T, opts: ObjectOptionsTypes<T>): ObjectType<T> {
|
|
71
|
-
return new (keyToUtil)[patternName]({
|
|
71
|
+
return new (keyToUtil)[patternName]({user: this.user, interactionType: this.interactionType, advanceTimer: this.advanceTimer, ...opts}) as ObjectType<T>;
|
|
72
72
|
}
|
|
73
73
|
}
|