Skip to content

Commit cfdf8ae

Browse files
kretajakgnapse
andauthored
feat: add toBePressed matcher (#203) (#658)
Co-authored-by: Ernesto García <[email protected]>
1 parent f00d94d commit cfdf8ae

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ clear to read and to maintain.
8181
- [`toBePartiallyChecked`](#tobepartiallychecked)
8282
- [`toHaveRole`](#tohaverole)
8383
- [`toHaveErrorMessage`](#tohaveerrormessage)
84-
- [`toHaveSelection`](#tohaveselection)
84+
- [`toBePressed`](#tobepressed)
8585
- [Deprecated matchers](#deprecated-matchers)
8686
- [`toBeEmpty`](#tobeempty)
8787
- [`toBeInTheDOM`](#tobeinthedom)
8888
- [`toHaveDescription`](#tohavedescription)
89+
- [`toHaveSelection`](#tohaveselection)
8990
- [Inspiration](#inspiration)
9091
- [Other Solutions](#other-solutions)
9192
- [Guiding Principles](#guiding-principles)
@@ -1306,6 +1307,46 @@ expect(timeInput).toHaveErrorMessage(expect.stringContaining('Invalid time')) //
13061307
expect(timeInput).not.toHaveErrorMessage('Pikachu!')
13071308
```
13081309

1310+
<hr />
1311+
1312+
### `toBePressed`
1313+
1314+
This allows to check whether given element is
1315+
[pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed).
1316+
1317+
It accepts elements with explicit or implicit `button` role and valid
1318+
`aria-pressed` attribute of `"true"` or `"false"`.
1319+
1320+
```typescript
1321+
toBePressed()
1322+
```
1323+
1324+
#### Examples
1325+
1326+
```html
1327+
<button aria-pressed="true">Pressed</button>
1328+
<button aria-pressed="false">Released</button>
1329+
1330+
<input type="button" aria-pressed="true" value="Pressed input button" />
1331+
<input type="button" aria-pressed="false" value="Released input button" />
1332+
1333+
<span role="button" aria-pressed="true">Pressed span</span>
1334+
<span role="button" aria-pressed="false">Released span</span>
1335+
```
1336+
1337+
##### Using DOM Testing Library
1338+
1339+
```javascript
1340+
screen.getByRole('button', {name: 'Pressed'}).toBePressed()
1341+
screen.getByRole('button', {name: 'Released'}).not.toBePressed()
1342+
1343+
screen.getByRole('button', {name: 'Pressed input button'}).toBePressed()
1344+
screen.getByRole('button', {name: 'Released input button'}).not.toBePressed()
1345+
1346+
screen.getByRole('button', {name: 'Pressed span'}).toBePressed()
1347+
screen.getByRole('button', {name: 'Released span'}).not.toBePressed()
1348+
```
1349+
13091350
## Deprecated matchers
13101351

13111352
### `toBeEmpty`

src/__tests__/to-be-pressed.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {render} from './helpers/test-utils'
2+
3+
describe('.toBePressed', () => {
4+
test('handles button element', () => {
5+
const {queryByTestId} = render(`
6+
<button data-testid="button-pressed" aria-pressed="true" />
7+
<button data-testid="button-not-pressed" aria-pressed="false" />
8+
`)
9+
10+
expect(queryByTestId('button-pressed')).toBePressed()
11+
expect(queryByTestId('button-not-pressed')).not.toBePressed()
12+
})
13+
14+
test('handles element with role="button"', () => {
15+
const {queryByTestId} = render(`
16+
<span role="button" aria-pressed="true" data-testid="button-pressed" />
17+
<span role="button" aria-pressed="false" data-testid="button-not-pressed" />
18+
`)
19+
20+
expect(queryByTestId('button-pressed')).toBePressed()
21+
expect(queryByTestId('button-not-pressed')).not.toBePressed()
22+
})
23+
24+
test('handles input with button type', () => {
25+
const {queryByTestId} = render(`
26+
<input type="button" aria-pressed="true" data-testid="button-pressed" />
27+
<input type="button" aria-pressed="false" data-testid="button-not-pressed" />
28+
`)
29+
30+
expect(queryByTestId('button-pressed')).toBePressed()
31+
expect(queryByTestId('button-not-pressed')).not.toBePressed()
32+
})
33+
34+
test('throw an error when pressable element is not pressed but expected to be', () => {
35+
const {queryByTestId} = render(`
36+
<button data-testid="button" aria-pressed="false" />
37+
`)
38+
39+
expect(() => expect(queryByTestId('button')).toBePressed())
40+
.toThrowErrorMatchingInlineSnapshot(`
41+
<dim>expect(</><red>element</><dim>).toBePressed()</>
42+
43+
Expected element to have:
44+
<green> aria-pressed="true"</>
45+
Received:
46+
<red> aria-pressed="false"</>
47+
`)
48+
})
49+
50+
test('throw an error when pressable element is pressed but expected not to be', () => {
51+
const {queryByTestId} = render(`
52+
<button data-testid="button" aria-pressed="true" />
53+
`)
54+
55+
expect(() => expect(queryByTestId('button')).not.toBePressed())
56+
.toThrowErrorMatchingInlineSnapshot(`
57+
<dim>expect(</><red>element</><dim>).not.toBePressed()</>
58+
59+
Expected element to have:
60+
<green> aria-pressed="false"</>
61+
Received:
62+
<red> aria-pressed="true"</>
63+
`)
64+
})
65+
66+
test('throw an error when pressable element has invalid aria-pressed attribute', () => {
67+
const {queryByTestId} = render(`
68+
<button data-testid="button" aria-pressed="invalid" />
69+
`)
70+
71+
expect(() =>
72+
expect(queryByTestId('button')).toBePressed(),
73+
).toThrowErrorMatchingInlineSnapshot(
74+
`Only button or input with type="button" or element with role="button" and a valid aria-pressed attribute can be used with .toBePressed()`,
75+
)
76+
})
77+
78+
test('throws an error when element is not a button', () => {
79+
const {queryByTestId} = render(
80+
`<span data-testid="span" aria-pressed="true" />`,
81+
)
82+
83+
expect(() =>
84+
expect(queryByTestId('span')).toBePressed(),
85+
).toThrowErrorMatchingInlineSnapshot(
86+
`Only button or input with type="button" or element with role="button" and a valid aria-pressed attribute can be used with .toBePressed()`,
87+
)
88+
})
89+
})

src/matchers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export {toBePartiallyChecked} from './to-be-partially-checked'
2525
export {toHaveDescription} from './to-have-description'
2626
export {toHaveErrorMessage} from './to-have-errormessage'
2727
export {toHaveSelection} from './to-have-selection'
28+
export {toBePressed} from './to-be-pressed'

src/to-be-pressed.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {checkHtmlElement, getMessage} from './utils'
2+
3+
export function toBePressed(element) {
4+
checkHtmlElement(element, toBePressed, this)
5+
6+
const roles = (element.getAttribute('role') || '')
7+
.split(' ')
8+
.map(role => role.trim())
9+
10+
const isButton =
11+
element.tagName.toLowerCase() === 'button' ||
12+
(element.tagName.toLowerCase() === 'input' && element.type === 'button') ||
13+
roles.includes('button')
14+
15+
const pressedAttribute = element.getAttribute('aria-pressed')
16+
17+
const isValidAriaElement =
18+
pressedAttribute === 'true' || pressedAttribute === 'false'
19+
20+
if (!isButton || !isValidAriaElement) {
21+
return {
22+
pass: false,
23+
message: () =>
24+
`Only button or input with type="button" or element with role="button" and a valid aria-pressed attribute can be used with .toBePressed()`,
25+
}
26+
}
27+
28+
const isPressed = pressedAttribute === 'true'
29+
30+
return {
31+
pass: isButton && isPressed,
32+
33+
message: () => {
34+
const matcher = this.utils.matcherHint(
35+
`${this.isNot ? '.not' : ''}.toBePressed`,
36+
'element',
37+
'',
38+
)
39+
40+
return getMessage(
41+
this,
42+
matcher,
43+
`Expected element to have`,
44+
`aria-pressed="${this.isNot ? 'false' : 'true'}"`,
45+
`Received`,
46+
`aria-pressed="${pressedAttribute}"`,
47+
)
48+
},
49+
}
50+
}

types/matchers.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,36 @@ declare namespace matchers {
761761
* [testing-library/jest-dom#tohaveselection](https://github.com/testing-library/jest-dom#tohaveselection)
762762
*/
763763
toHaveSelection(selection?: string): R
764+
/*
765+
* @description
766+
* This allows to check whether given element has been [pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed)
767+
*
768+
* It accepts elements with explicit or implicit `button` role and valid `aria-pressed`
769+
* attribute of `"true"` or `"false"`.
770+
*
771+
* @example
772+
* <button aria-pressed="true">Pressed</button>
773+
* <button aria-pressed="false">Released</button>
774+
*
775+
* <input type="button" aria-pressed="true" value="Pressed input button" />
776+
* <input type="button" aria-pressed="false" value="Released input button" />
777+
*
778+
* <span role="button" aria-pressed="true">Pressed span</span>
779+
* <span role="button" aria-pressed="false">Released span</span>
780+
*
781+
* screen.getByRole('button', { name: 'Pressed' }).toBePressed();
782+
* screen.getByRole('button', { name: 'Released' }).not.toBePressed();
783+
*
784+
* screen.getByRole('button', { name: 'Pressed input button' }).toBePressed();
785+
* screen.getByRole('button', { name: 'Released input button' }).not.toBePressed();
786+
*
787+
* screen.getByRole('button', { name: 'Pressed span' }).toBePressed();
788+
* screen.getByRole('button', { name: 'Released span' }).not.toBePressed();
789+
*
790+
* @see
791+
* [testing-library/jest-dom#tobepressed](https://github.com/testing-library/jest-dom#tobepressed)
792+
*/
793+
toBePressed(): R
764794
}
765795
}
766796

0 commit comments

Comments
 (0)