-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: support selectionMode="replace" in grid collection test utils #8028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a32b16e
e326a35
9481673
f625818
69d289f
366c1f8
5a502b8
8f55260
93cbb33
5207639
14452d8
8bd33c4
dd52a74
5ef9f19
fc4b727
40d7b2f
972e5a2
53763bd
96b4a85
94b383c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,9 +11,17 @@ | |
*/ | ||
|
||
import {act, fireEvent} from '@testing-library/react'; | ||
import {isMac} from '@react-aria/utils'; | ||
import {UserOpts} from './types'; | ||
|
||
export const DEFAULT_LONG_PRESS_TIME = 500; | ||
export function getAltKey(): 'Alt' | 'ControlLeft' { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? what does |
||
return isMac() ? 'Alt' : 'ControlLeft'; | ||
} | ||
|
||
export function getMetaKey(): 'MetaLeft' | 'ControlLeft' { | ||
return isMac() ? 'MetaLeft' : 'ControlLeft'; | ||
} | ||
|
||
/** | ||
* Simulates a "long press" event on a element. | ||
|
@@ -58,9 +66,10 @@ export async function triggerLongPress(opts: {element: HTMLElement, advanceTimer | |
} | ||
|
||
// Docs cannot handle the types that userEvent actually declares, so hopefully this sub set is okay | ||
export async function pressElement(user: {click: (element: Element) => Promise<void>, keyboard: (keys: string) => Promise<void>, pointer: (opts: {target: Element, keys: string}) => Promise<void>}, element: HTMLElement, interactionType: UserOpts['interactionType']): Promise<void> { | ||
export async function pressElement(user: {click: (element: Element) => Promise<void>, keyboard: (keys: string) => Promise<void>, pointer: (opts: {target: Element, keys: string, coords?: any}) => Promise<void>}, element: HTMLElement, interactionType: UserOpts['interactionType']): Promise<void> { | ||
if (interactionType === 'mouse') { | ||
await user.click(element); | ||
// Add coords with pressure so this isn't detected as a virtual click | ||
await user.pointer({target: element, keys: '[MouseLeft]', coords: {pressure: .5}}); | ||
} else if (interactionType === 'keyboard') { | ||
// TODO: For the keyboard flow, I wonder if it would be reasonable to just do fireEvent directly on the obtained row node or if we should | ||
// stick to simulting an actual user's keyboard operations as closely as possible | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,8 @@ | |
*/ | ||
|
||
import {act, within} from '@testing-library/react'; | ||
import {getAltKey, getMetaKey, pressElement, triggerLongPress} from './events'; | ||
import {GridListTesterOpts, GridRowActionOpts, ToggleGridRowOpts, UserOpts} from './types'; | ||
import {pressElement, triggerLongPress} from './events'; | ||
|
||
interface GridListToggleRowOpts extends ToggleGridRowOpts {} | ||
interface GridListRowActionOpts extends GridRowActionOpts {} | ||
|
@@ -57,20 +57,21 @@ export class GridListTester { | |
} | ||
|
||
// TODO: RTL | ||
private async keyboardNavigateToRow(opts: {row: HTMLElement}) { | ||
let {row} = opts; | ||
private async keyboardNavigateToRow(opts: {row: HTMLElement, useAltKey?: boolean}) { | ||
let {row, useAltKey} = opts; | ||
let altKey = getAltKey(); | ||
let rows = this.rows; | ||
let targetIndex = rows.indexOf(row); | ||
if (targetIndex === -1) { | ||
throw new Error('Option provided is not in the gridlist'); | ||
} | ||
|
||
if (document.activeElement !== this._gridlist || !this._gridlist.contains(document.activeElement)) { | ||
if (document.activeElement !== this._gridlist && !this._gridlist.contains(document.activeElement)) { | ||
act(() => this._gridlist.focus()); | ||
} | ||
|
||
if (document.activeElement === this._gridlist) { | ||
await this.user.keyboard('[ArrowDown]'); | ||
await this.user.keyboard(`${useAltKey ? `[${altKey}>]` : ''}[ArrowDown]${useAltKey ? `[/${altKey}]` : ''}`); | ||
} else if (this._gridlist.contains(document.activeElement) && document.activeElement!.getAttribute('role') !== 'row') { | ||
do { | ||
await this.user.keyboard('[ArrowLeft]'); | ||
|
@@ -82,22 +83,34 @@ export class GridListTester { | |
} | ||
let direction = targetIndex > currIndex ? 'down' : 'up'; | ||
|
||
if (useAltKey) { | ||
await this.user.keyboard(`[${altKey}>]`); | ||
} | ||
for (let i = 0; i < Math.abs(targetIndex - currIndex); i++) { | ||
await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`); | ||
} | ||
if (useAltKey) { | ||
await this.user.keyboard(`[/${altKey}]`); | ||
} | ||
}; | ||
|
||
/** | ||
* Toggles the selection for the specified gridlist row. Defaults to using the interaction type set on the gridlist tester. | ||
* Note that this will endevor to always add/remove JUST the provided row to the set of selected rows. | ||
*/ | ||
async toggleRowSelection(opts: GridListToggleRowOpts): Promise<void> { | ||
let { | ||
row, | ||
needsLongPress, | ||
checkboxSelection = true, | ||
interactionType = this._interactionType | ||
interactionType = this._interactionType, | ||
// TODO: perhaps this should just be shouldUseModifierKeys? | ||
selectionBehavior = 'toggle' | ||
Comment on lines
+107
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This API naming feels unclear TBH, people may be confused as to when they would apply it. When set to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think selectionBehavior is fine or gridListSelectionBehavior if you want to be clear it should match, then the user profiles/interactions should handle most things behind the scenes |
||
} = opts; | ||
|
||
let altKey = getAltKey(); | ||
let metaKey = getMetaKey(); | ||
|
||
if (typeof row === 'string' || typeof row === 'number') { | ||
row = this.findRow({rowIndexOrText: row}); | ||
} | ||
|
@@ -116,9 +129,15 @@ export class GridListTester { | |
|
||
// this would be better than the check to do nothing in events.ts | ||
// also, it'd be good to be able to trigger selection on the row instead of having to go to the checkbox directly | ||
if (interactionType === 'keyboard' && !checkboxSelection) { | ||
await this.keyboardNavigateToRow({row}); | ||
await this.user.keyboard('{Space}'); | ||
if (interactionType === 'keyboard' && (!checkboxSelection || !rowCheckbox)) { | ||
await this.keyboardNavigateToRow({row, useAltKey: selectionBehavior === 'replace'}); | ||
if (selectionBehavior === 'replace') { | ||
await this.user.keyboard(`[${altKey}>]`); | ||
} | ||
await this.user.keyboard('[Space]'); | ||
if (selectionBehavior === 'replace') { | ||
await this.user.keyboard(`[/${altKey}]`); | ||
} | ||
return; | ||
} | ||
if (rowCheckbox && checkboxSelection) { | ||
|
@@ -132,9 +151,14 @@ export class GridListTester { | |
|
||
// Note that long press interactions with rows is strictly touch only for grid rows | ||
await triggerLongPress({element: cell, advanceTimer: this._advanceTimer, pointerOpts: {pointerType: 'touch'}}); | ||
|
||
} else { | ||
await pressElement(this.user, cell, interactionType); | ||
if (selectionBehavior === 'replace' && interactionType !== 'touch') { | ||
await this.user.keyboard(`[${metaKey}>]`); | ||
} | ||
await pressElement(this.user, row, interactionType); | ||
if (selectionBehavior === 'replace' && interactionType !== 'touch') { | ||
await this.user.keyboard(`[/${metaKey}]`); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -166,7 +190,7 @@ export class GridListTester { | |
return; | ||
} | ||
|
||
await this.keyboardNavigateToRow({row}); | ||
await this.keyboardNavigateToRow({row, useAltKey: true}); | ||
await this.user.keyboard('[Enter]'); | ||
} else { | ||
await pressElement(this.user, row, interactionType); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should have a dependency going in this direction, test-utils should never depend on our actual packages
too easy to make a circular dependency