-
Notifications
You must be signed in to change notification settings - Fork 726
feat: document user-event v14 #980
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
Changes from all commits
addd7a4
f82fde5
536a100
8613e7c
658c96e
7698942
5650682
fcb18ab
2798eda
dacbae2
703d37d
13a699a
868fb40
9150ef9
d57fed0
3768cc5
8d0c9bd
62b4dda
1978f66
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 |
---|---|---|
@@ -0,0 +1,49 @@ | ||
--- | ||
id: clipboard | ||
title: Clipboard | ||
--- | ||
|
||
Note that the | ||
[Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard) is | ||
usually not available outside of secure context. | ||
To enable testing of workflows involving the clipboard, | ||
[`userEvent.setup()`](setup) replaces `window.navigator.clipboard` with a stub. | ||
|
||
## copy() | ||
|
||
```ts | ||
copy(): Promise<DataTransfer|undefined> | ||
``` | ||
|
||
Copy the current selection. | ||
|
||
If [`writeToClipboard`](options#writetoclipboard) is `true`, this will also | ||
write the data to the `Clipboard`. | ||
|
||
## cut() | ||
|
||
```ts | ||
cut(): Promise<DataTransfer|undefined> | ||
``` | ||
|
||
Cut the current selection. | ||
|
||
If [`writeToClipboard`](options#writetoclipboard) is `true`, this will also | ||
write the data to the `Clipboard`. | ||
|
||
When performed in editable context, it removes the selected content from the | ||
document. | ||
|
||
## paste() | ||
|
||
```ts | ||
paste(clipboardData?: DataTransfer|string): Promise<void> | ||
``` | ||
|
||
Paste data into the document. | ||
|
||
When called without `clipboardData`, the content to be pasted is read from the | ||
`Clipboard`. | ||
|
||
When performed in editable context, the pasted content is inserted into to the | ||
document. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
--- | ||
id: convenience | ||
title: Convenience APIs | ||
--- | ||
|
||
The following APIs are shortcuts for equivalent calls to the underlying | ||
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'm not sure these API's should be "hidden" in the convenience section because most of the users will probably be using them. 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. Yeah, I don't know. On one hand they exist because they are easy to use and so - yes - they are probably used often, but on the other hand they are self-explanatory and merely documented for sake of completeness. The auto-completion in IDE would probably be enough. Maybe this could be solved by another blog post walking through some examples with heavy use of these convenience methods and mentioning the post with a short excerpt in the introduction. 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. Not sure we should rely on the IDE auto-completion for this one and IMO the package's functionality should be easily found in the docs and not in a personal blog post (but we should also think of writing a blog post about user-event 14 in the testing-library blog). I strongly prefer a mention in |
||
[`pointer()`](pointer) and [`keyboard()`](keyboard) APIs. | ||
|
||
## Clicks | ||
|
||
### click() | ||
|
||
```ts | ||
click(element: Element): Promise<void> | ||
``` | ||
|
||
```js | ||
pointer([{target: element}, {keys: '[MouseLeft]', target: element}]) | ||
``` | ||
|
||
The first action might be skipped per [`skipHover`](options#skiphover). | ||
|
||
### dblClick() | ||
|
||
```ts | ||
dblClick(element: Element): Promise<void> | ||
``` | ||
|
||
```js | ||
pointer([{target: element}, {keys: '[MouseLeft][MouseLeft]', target: element}]) | ||
``` | ||
|
||
### tripleClick() | ||
|
||
```ts | ||
tripleClick(element: Element): Promise<void> | ||
``` | ||
|
||
```js | ||
pointer([ | ||
{target: element}, | ||
{keys: '[MouseLeft][MouseLeft][MouseLeft]', target: element}, | ||
]) | ||
``` | ||
|
||
## Mouse movement | ||
|
||
### hover() | ||
|
||
```ts | ||
hover(element: Element): Promise<void> | ||
``` | ||
|
||
```js | ||
pointer({target: element}) | ||
``` | ||
|
||
### unhover() | ||
|
||
```ts | ||
hover(element: Element): Promise<void> | ||
``` | ||
|
||
```js | ||
pointer({target: element.ownerDocument.body}) | ||
``` | ||
|
||
## Keyboard | ||
|
||
### tab() | ||
|
||
```ts | ||
tab(options: {shift?: boolean}) | ||
``` | ||
|
||
```js | ||
// without shift | ||
keyboard('{Tab}') | ||
// with shift=true | ||
keyboard('{Shift>}{Tab}{/Shift}') | ||
// with shift=false | ||
keyboard('[/ShiftLeft][/ShiftRight]{Tab}') | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
--- | ||
id: keyboard | ||
title: Keyboard | ||
--- | ||
|
||
```ts | ||
keyboard(input: KeyboardInput): Promise<void> | ||
``` | ||
|
||
The `keyboard` API allows to simulate interactions with a keyboard. It accepts a | ||
`string` describing the key actions. | ||
|
||
Keystrokes can be described: | ||
|
||
- Per printable character | ||
```js | ||
keyboard('foo') // translates to: f, o, o | ||
``` | ||
The brackets `{` and `[` are used as special characters and can be referenced | ||
by doubling them. | ||
```js | ||
keyboard('{{a[[') // translates to: {, a, [ | ||
``` | ||
- Per | ||
[KeyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) | ||
|
||
```js | ||
keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o | ||
``` | ||
|
||
This does not keep any key pressed. So `Shift` will be lifted before pressing | ||
`f`. | ||
|
||
Characters with special meaning inside the key descriptor can be escaped by | ||
prefixing them with a backslash `\`. | ||
|
||
```js | ||
keyboard('{}}') // translates to: } | ||
``` | ||
|
||
- Per | ||
[KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) | ||
```js | ||
keyboard('[ShiftLeft][KeyF][KeyO][KeyO]') // translates to: Shift, f, o, o | ||
``` | ||
|
||
Keys can be kept pressed by adding a `>` to the end of the descriptor. | ||
If this should result in repeated `keydown` events, you can add the number of | ||
repetitions. | ||
If the key should also be released after this, add a slash `/` to the end of the | ||
descriptor. | ||
|
||
```js | ||
keyboard('{a>}') // press a without releasing it | ||
keyboard('{a>5}') // press a without releasing it and trigger 5 keydown | ||
keyboard('{a>5/}') // press a for 5 keydown and then release it | ||
``` | ||
|
||
A previously pressed key can be lifted by prefixing the descriptor with `/`. | ||
|
||
```js | ||
keyboard('{/a}') // release a previously pressed a | ||
``` | ||
|
||
This allows to simulate key combinations. | ||
|
||
```js | ||
keyboard('{Shift>}A{/Shift}') // translates to: Shift(down), A, Shift(up) | ||
``` | ||
|
||
The mapping of `key` to `code` is performed by a | ||
[default key map](https://github.com/testing-library/user-event/blob/beta/src/keyboard/keyMap.ts) | ||
portraying a "default" US-keyboard. You can provide your own local keyboard | ||
mapping per [`keyboardMap`](options#keyboardmap) option. | ||
|
||
Currently the different `key` meanings of single keys are treated as different | ||
keys. | ||
|
||
> Future versions might try to interpolate the modifiers needed to reach a | ||
> printable key on the keyboard. E.g. Automatically pressing `{Shift}` when | ||
> CapsLock is not active and `A` is referenced. If you don't wish this behavior, | ||
> you can deactivate the [`autoModify`](options#automodify) option to opt out | ||
> of this non-breaking change. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,138 @@ | ||||||||||||||||||||
--- | ||||||||||||||||||||
id: pointer | ||||||||||||||||||||
title: Pointer | ||||||||||||||||||||
--- | ||||||||||||||||||||
|
||||||||||||||||||||
```ts | ||||||||||||||||||||
pointer(input: PointerInput): Promise<void> | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
The `pointer` API allows to simulate interactions with pointer devices. It | ||||||||||||||||||||
accepts a single pointer action or an array of them. | ||||||||||||||||||||
|
||||||||||||||||||||
```ts | ||||||||||||||||||||
type PointerInput = PointerActionInput | Array<PointerActionInput> | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
> Our primary target audience tests per `jest` in a `jsdom` environment and there | ||||||||||||||||||||
is no layout in `jsdom`. This means that different from your browser the | ||||||||||||||||||||
elements don't exist in a specific position, layer and size. | ||||||||||||||||||||
We don't try to determine if the pointer action you describe is possible at that | ||||||||||||||||||||
position in your layout. | ||||||||||||||||||||
Comment on lines
+17
to
+21
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. Do you mean this?
Suggested change
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. No this literally means that there is no specific position, layer, or size. Elements can have IDL properties and/or CSS styles that usually correspond to elements being at a specific position, having specific dimensions, or being above/under each other. But document.body.innerHTML=`<img style="position: relative; left: 50px;"/>`
$("img").style.left // '50px'
$("img").offsetLeft // 0 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. 👍 Got it, the comment made it click to me - would it be worth it to also mention it in the docs?
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 don't know. I feel like it might be better to write a blog post about this topic explaining the concept and then adding a link. Really explaining this, and what difference it makes to testing might be worth it, but is out of scope for these docs. |
||||||||||||||||||||
|
||||||||||||||||||||
## Pointer action | ||||||||||||||||||||
|
||||||||||||||||||||
There are two types of actions: press and move. | ||||||||||||||||||||
|
||||||||||||||||||||
### <a name="press" href="#"/>Pressing a button or touching the screen | ||||||||||||||||||||
|
||||||||||||||||||||
A pointer action is a press action if it defines a key to be pressed, to be | ||||||||||||||||||||
released, or both. | ||||||||||||||||||||
|
||||||||||||||||||||
```js | ||||||||||||||||||||
pointer({keys: '[MouseLeft]'}) | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
You can declare multiple press actions (on the same position) at once which will | ||||||||||||||||||||
be resolved to multiple actions internally. If you don't need to declare any | ||||||||||||||||||||
other properties you can also just supply the `keys` string. | ||||||||||||||||||||
|
||||||||||||||||||||
```js | ||||||||||||||||||||
pointer({keys: '[MouseLeft][MouseRight]'}) | ||||||||||||||||||||
// or | ||||||||||||||||||||
pointer('[MouseLeft][MouseRight]') | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
In order to press a button without releasing it, the button name is suffixed with | ||||||||||||||||||||
`>`. | ||||||||||||||||||||
For just releasing a previously pressed button, the tag is started with `/`. | ||||||||||||||||||||
|
||||||||||||||||||||
```js | ||||||||||||||||||||
pointer('[MouseLeft>]') // press the left mouse button | ||||||||||||||||||||
pointer('[/MouseLeft]') // release the left mouse button | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
Which buttons are available depends on the [`pointerMap`](options#pointermap). | ||||||||||||||||||||
|
||||||||||||||||||||
### <a name="move" href="#"/>Moving a pointer | ||||||||||||||||||||
|
||||||||||||||||||||
Every pointer action that is not a press action describes a pointer movement. | ||||||||||||||||||||
|
||||||||||||||||||||
You can declare which pointer is moved per `pointerName` property. This defaults | ||||||||||||||||||||
to `mouse`. | ||||||||||||||||||||
|
||||||||||||||||||||
Note that the `mouse` pointer (`pointerId: 1`) is also the only pointer that | ||||||||||||||||||||
always exists and has a position. A `touch` pointer only exists while the screen | ||||||||||||||||||||
is touched and receives a new `pointerId` every time. For these pointers, we use | ||||||||||||||||||||
the "button" name from the press action as `pointerName`. | ||||||||||||||||||||
|
||||||||||||||||||||
```js | ||||||||||||||||||||
pointer([ | ||||||||||||||||||||
// touch the screen at element1 | ||||||||||||||||||||
{keys: '[TouchA>]', target: element1}, | ||||||||||||||||||||
// move the touch pointer to element2 | ||||||||||||||||||||
{pointerName: 'TouchA', target: element2}, | ||||||||||||||||||||
// release the touch pointer at the last position (element2) | ||||||||||||||||||||
{keys: '[/TouchA]'}, | ||||||||||||||||||||
]) | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
## Pointer position | ||||||||||||||||||||
|
||||||||||||||||||||
### PointerTarget | ||||||||||||||||||||
|
||||||||||||||||||||
```ts | ||||||||||||||||||||
interface PointerTarget { | ||||||||||||||||||||
target: Element | ||||||||||||||||||||
coords?: PointerCoords | ||||||||||||||||||||
} | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
The `PointerTarget` props allows to describe the position of the pointer on the | ||||||||||||||||||||
document. | ||||||||||||||||||||
The `coords` you provide are applied as-is to the resulting | ||||||||||||||||||||
[`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) and | ||||||||||||||||||||
can be omitted. | ||||||||||||||||||||
The `target` should be the element receiving the pointer input in the browser. | ||||||||||||||||||||
This is the topmost element that can receive pointer event at those coordinates. | ||||||||||||||||||||
|
||||||||||||||||||||
### SelectionTarget | ||||||||||||||||||||
|
||||||||||||||||||||
```ts | ||||||||||||||||||||
interface SelectionTarget { | ||||||||||||||||||||
node?: Node | ||||||||||||||||||||
offset?: number | ||||||||||||||||||||
} | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
Pointer actions can alter the selection in the document. | ||||||||||||||||||||
In the browser every pointer position corresponds with a DOM position. This is a | ||||||||||||||||||||
DOM node and a DOM offset which usually translates to the character closest to | ||||||||||||||||||||
the pointer position. | ||||||||||||||||||||
As all character in a no-layout environment are in the same layout position we | ||||||||||||||||||||
assume a pointer position to be closest to the last descendant of the pointer | ||||||||||||||||||||
`target`. | ||||||||||||||||||||
timdeschryver marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
||||||||||||||||||||
If you provide `offset`, we assume the pointer position to be closest to the | ||||||||||||||||||||
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. It seems to me like an example will be good here to make our users understand it properly. 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. Added an example. I hope this makes it clear enough. Might be worth explaining layout positions and DOM offsets and how the browser handles selection per pointer in a blog post, but this would break the mold of docs specific to the library. |
||||||||||||||||||||
`offset-th` character of `target.textContent`. | ||||||||||||||||||||
|
||||||||||||||||||||
If you also provide `node`, we treat `node` and `offset` as the exact DOM | ||||||||||||||||||||
position to be used for any selection. | ||||||||||||||||||||
|
||||||||||||||||||||
```jsx | ||||||||||||||||||||
// element: <div><span>foo</span><span>bar</span></div> | ||||||||||||||||||||
// | marking the cursor. | ||||||||||||||||||||
// [ ] marking a selection. | ||||||||||||||||||||
|
||||||||||||||||||||
pointer({target: element, offset: 2, keys: '[MouseLeft]'}) | ||||||||||||||||||||
// => <div><span>fo|o</span><span>bar</span></div> | ||||||||||||||||||||
|
||||||||||||||||||||
pointer([ | ||||||||||||||||||||
{target: element, offset: 2, keys: '[MouseLeft>]'}, | ||||||||||||||||||||
{offset: 5}, | ||||||||||||||||||||
]) | ||||||||||||||||||||
// => <div><span>fo[o</span><span>ba]r</span></div> | ||||||||||||||||||||
|
||||||||||||||||||||
pointer({target: element, node: element, offset: 1, keys: '[MouseLeft]'}) | ||||||||||||||||||||
// => <div><span>foo</span>|<span>bar</span></div> | ||||||||||||||||||||
``` |
Uh oh!
There was an error while loading. Please reload this page.