Skip to content

Commit 5a89af4

Browse files
committed
feat: add toBePressed matcher (#203)
1 parent 918b6fb commit 5a89af4

File tree

5 files changed

+213
-0
lines changed

5 files changed

+213
-0
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ clear to read and to maintain.
8282
- [`toHaveRole`](#tohaverole)
8383
- [`toHaveErrorMessage`](#tohaveerrormessage)
8484
- [`toHaveSelection`](#tohaveselection)
85+
- [`toBePressed`](#tobepressed)
8586
- [Deprecated matchers](#deprecated-matchers)
8687
- [`toBeEmpty`](#tobeempty)
8788
- [`toBeInTheDOM`](#tobeinthedom)
@@ -1306,6 +1307,44 @@ 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 has been [pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed).
1315+
1316+
It accepts elements with explicit or implicit `button` role and valid `aria-pressed` attribute of `"true"` or `"false"`.
1317+
1318+
```typescript
1319+
toBePressed()
1320+
```
1321+
1322+
#### Examples
1323+
1324+
```html
1325+
<button aria-pressed="true">Pressed</button>
1326+
<button aria-pressed="false">Released</button>
1327+
1328+
<input type="button" aria-pressed="true" value="Pressed input button" />
1329+
<input type="button" aria-pressed="false" value="Released input button" />
1330+
1331+
<span role="button" aria-pressed="true">Pressed span</span>
1332+
<span role="button" aria-pressed="false">Released span</span>
1333+
```
1334+
1335+
##### Using DOM Testing Library
1336+
1337+
```javascript
1338+
screen.getByRole('button', { name: 'Pressed' }).toBePressed();
1339+
screen.getByRole('button', { name: 'Released' }).not.toBePressed();
1340+
1341+
screen.getByRole('button', { name: 'Pressed input button' }).toBePressed();
1342+
screen.getByRole('button', { name: 'Released input button' }).not.toBePressed();
1343+
1344+
screen.getByRole('button', { name: 'Pressed span' }).toBePressed();
1345+
screen.getByRole('button', { name: 'Released span' }).not.toBePressed();
1346+
```
1347+
13091348
## Deprecated matchers
13101349

13111350
### `toBeEmpty`

src/__tests__/to-be-pressed.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 when element do not have aria-pressed attribute', () => {
79+
const {queryByTestId} = render(`<span data-testid="span" />`)
80+
81+
expect(() =>
82+
expect(queryByTestId('span')).toBePressed(),
83+
).toThrowErrorMatchingInlineSnapshot(
84+
`Only button or input with type="button" or element with role="button" and a valid aria-pressed attribute can be used with .toBePressed()`,
85+
)
86+
})
87+
})

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

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)