Skip to content

Commit 9ee0c05

Browse files
Added support for type=radio, role=checkbox and role=radio elements to .toBeChecked() matcher.
1 parent 338a65e commit 9ee0c05

File tree

3 files changed

+210
-38
lines changed

3 files changed

+210
-38
lines changed

README.md

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -923,8 +923,8 @@ toHaveValue(value: string | string[] | number)
923923
This allows you to check whether the given form element has the specified value.
924924
It accepts `<input>`, `<select>` and `<textarea>` elements with the exception of
925925
of `<input type="checkbox">` and `<input type="radio">`, which can be
926-
meaningfully matched only using [`toHaveFormValue`](#tohaveformvalues).
927-
`<input type="checkbox>` can also be matched with ['toBeChecked'](#tobechecked).
926+
meaningfully matched only using [`toBeChecked`](#tobechecked) or
927+
[`toHaveFormValue`](#tohaveformvalues).
928928
929929
For all other form elements, the value is matched using the same algorithm as in
930930
[`toHaveFormValue`](#tohaveformvalues) does.
@@ -980,36 +980,88 @@ expect(selectInput).not.toHaveValue(['second', 'third'])
980980
toBeChecked()
981981
```
982982
983-
This allows you to check whether the input checkbox element is checked. It
984-
accepts `<input type="checkbox">` only.
983+
This allows you to check whether the given element is checked. It accepts an
984+
`input` of type `checkbox` or `radio` and elements with a `role` of `checkbox`
985+
or `radio` with a valid `aria-checked` attribute of `"true"` or `"false"`.
985986
986987
#### Examples
987988
988989
```html
989-
<input type="checked" checked data-testid="input-checked" />
990-
<input type="checked" data-testid="input-empty" />
990+
<input type="checkbox" checked data-testid="input-checkbox-checked" />
991+
<input type="checkbox" data-testid="input-checkbox-unchecked" />
992+
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
993+
<div
994+
role="checkbox"
995+
aria-checked="false"
996+
data-testid="aria-checkbox-unchecked"
997+
/>
998+
999+
<input type="radio" checked value="foo" data-testid="input-radio-checked" />
1000+
<input type="radio" value="foo" data-testid="input-radio-unchecked" />
1001+
<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
1002+
<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
9911003
```
9921004
9931005
##### Using document.querySelector
9941006
9951007
```javascript
996-
const checkedInput = document.querySelector('[data-testid="input-checked"]')
997-
const emptyInput = document.querySelector('[data-testid="input-empty"]')
1008+
const inputCheckboxChecked = document.querySelector(
1009+
'[data-testid="input-checkbox-checked"]',
1010+
)
1011+
const inputCheckboxUnchecked = document.querySelector(
1012+
'[data-testid="input-checkbox-unchecked"]',
1013+
)
1014+
const ariaCheckboxChecked = document.querySelector(
1015+
'[data-testid="aria-checkbox-checked"]',
1016+
)
1017+
const ariaCheckboxUnchecked = document.querySelector(
1018+
'[data-testid="aria-checkbox-unchecked"]',
1019+
)
1020+
expect(inputCheckboxChecked).toBeChecked()
1021+
expect(inputCheckboxUnchecked).not.toBeChecked()
1022+
expect(ariaCheckboxChecked).toBeChecked()
1023+
expect(ariaCheckboxUnchecked).not.toBeChecked()
9981024

999-
expect(checkedInput).toBeChecked()
1000-
expect(emptyInput).not.toBeChecked()
1025+
const inputRadioChecked = document.querySelector(
1026+
'[data-testid="input-radio-checked"]',
1027+
)
1028+
const inputRadioUnchecked = document.querySelector(
1029+
'[data-testid="input-radio-unchecked"]',
1030+
)
1031+
const ariaRadioChecked = document.querySelector(
1032+
'[data-testid="aria-radio-checked"]',
1033+
)
1034+
const ariaRadioUnchecked = document.querySelector(
1035+
'[data-testid="aria-radio-unchecked"]',
1036+
)
1037+
expect(inputRadioChecked).toBeChecked()
1038+
expect(inputRadioUnchecked).not.toBeChecked()
1039+
expect(ariaRadioChecked).toBeChecked()
1040+
expect(ariaRadioUnchecked).not.toBeChecked()
10011041
```
10021042
10031043
##### Using DOM Testing Library
10041044
10051045
```javascript
10061046
const {getByTestId} = render(/* Rendered HTML */)
10071047

1008-
const checkedInput = getByTestId('input-text')
1009-
const emptyInput = getByTestId('input-empty')
1010-
1011-
expect(checkedInput).toBeChecked()
1012-
expect(emptyInput).not.toBeChecked()
1048+
const inputCheckboxChecked = getByTestId('input-checkbox-checked')
1049+
const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
1050+
const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
1051+
const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
1052+
expect(inputCheckboxChecked).toBeChecked()
1053+
expect(inputCheckboxUnchecked).not.toBeChecked()
1054+
expect(ariaCheckboxChecked).toBeChecked()
1055+
expect(ariaCheckboxUnchecked).not.toBeChecked()
1056+
1057+
const inputRadioChecked = getByTestId('input-radio-checked')
1058+
const inputRadioUnchecked = getByTestId('input-radio-unchecked')
1059+
const ariaRadioChecked = getByTestId('aria-radio-checked')
1060+
const ariaRadioUnchecked = getByTestId('aria-radio-unchecked')
1061+
expect(inputRadioChecked).toBeChecked()
1062+
expect(inputRadioUnchecked).not.toBeChecked()
1063+
expect(ariaRadioChecked).toBeChecked()
1064+
expect(ariaRadioUnchecked).not.toBeChecked()
10131065
```
10141066
10151067
## Deprecated matchers

src/__tests__/to-be-checked.js

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,45 @@ import {render} from './helpers/test-utils'
33
describe('.toBeChecked', () => {
44
test('handles checkbox input', () => {
55
const {queryByTestId} = render(`
6-
<input type="checkbox" checked data-testid="input-checked" />
7-
<input type="checkbox" data-testid="input-empty" />
6+
<input type="checkbox" checked data-testid="input-checkbox-checked" />
7+
<input type="checkbox" data-testid="input-checkbox-unchecked" />
88
`)
99

10-
expect(queryByTestId('input-checked')).toBeChecked()
11-
expect(queryByTestId('input-empty')).not.toBeChecked()
10+
expect(queryByTestId('input-checkbox-checked')).toBeChecked()
11+
expect(queryByTestId('input-checkbox-unchecked')).not.toBeChecked()
1212
})
1313

14-
test('throws when checkbox is checked but expected not to be', () => {
14+
test('handles radio input', () => {
15+
const {queryByTestId} = render(`
16+
<input type="radio" checked value="foo" data-testid="input-radio-checked" />
17+
<input type="radio" value="foo" data-testid="input-radio-unchecked" />
18+
`)
19+
20+
expect(queryByTestId('input-radio-checked')).toBeChecked()
21+
expect(queryByTestId('input-radio-unchecked')).not.toBeChecked()
22+
})
23+
24+
test('handles element with role="checkbox"', () => {
25+
const {queryByTestId} = render(`
26+
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
27+
<div role="checkbox" aria-checked="false" data-testid="aria-checkbox-unchecked" />
28+
`)
29+
30+
expect(queryByTestId('aria-checkbox-checked')).toBeChecked()
31+
expect(queryByTestId('aria-checkbox-unchecked')).not.toBeChecked()
32+
})
33+
34+
test('handles element with role="radio"', () => {
35+
const {queryByTestId} = render(`
36+
<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
37+
<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
38+
`)
39+
40+
expect(queryByTestId('aria-radio-checked')).toBeChecked()
41+
expect(queryByTestId('aria-radio-unchecked')).not.toBeChecked()
42+
})
43+
44+
test('throws when checkbox input is checked but expected not to be', () => {
1545
const {queryByTestId} = render(
1646
`<input type="checkbox" checked data-testid="input-checked" />`,
1747
)
@@ -21,7 +51,7 @@ describe('.toBeChecked', () => {
2151
).toThrowError()
2252
})
2353

24-
test('throws when checkbox is not checked but expected to be', () => {
54+
test('throws when input checkbox is not checked but expected to be', () => {
2555
const {queryByTestId} = render(
2656
`<input type="checkbox" data-testid="input-empty" />`,
2757
)
@@ -31,20 +61,96 @@ describe('.toBeChecked', () => {
3161
).toThrowError()
3262
})
3363

34-
test('throws when the element is not an input', () => {
35-
const {queryByTestId} = render(`<select data-testid="select"></select>`)
36-
expect(() => expect(queryByTestId('select')).toBeChecked()).toThrowError(
37-
'only inputs with type=checkbox can be used with .toBeChecked(). Use .toHaveFormValues() instead',
64+
test('throws when element with role="checkbox" is checked but expected not to be', () => {
65+
const {queryByTestId} = render(
66+
`<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />`,
67+
)
68+
69+
expect(() =>
70+
expect(queryByTestId('aria-checkbox-checked')).not.toBeChecked(),
71+
).toThrowError()
72+
})
73+
74+
test('throws when element with role="checkbox" is not checked but expected to be', () => {
75+
const {queryByTestId} = render(
76+
`<div role="checkbox" aria-checked="false" data-testid="aria-checkbox-unchecked" />`,
77+
)
78+
79+
expect(() =>
80+
expect(queryByTestId('aria-checkbox-unchecked')).toBeChecked(),
81+
).toThrowError()
82+
})
83+
84+
test('throws when radio input is checked but expected not to be', () => {
85+
const {queryByTestId} = render(
86+
`<input type="radio" checked data-testid="input-radio-checked" />`,
87+
)
88+
89+
expect(() =>
90+
expect(queryByTestId('input-radio-checked')).not.toBeChecked(),
91+
).toThrowError()
92+
})
93+
94+
test('throws when input radio is not checked but expected to be', () => {
95+
const {queryByTestId} = render(
96+
`<input type="radio" data-testid="input-radio-unchecked" />`,
97+
)
98+
99+
expect(() =>
100+
expect(queryByTestId('input-radio-unchecked')).toBeChecked(),
101+
).toThrowError()
102+
})
103+
104+
test('throws when element with role="radio" is checked but expected not to be', () => {
105+
const {queryByTestId} = render(
106+
`<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />`,
107+
)
108+
109+
expect(() =>
110+
expect(queryByTestId('aria-radio-checked')).not.toBeChecked(),
111+
).toThrowError()
112+
})
113+
114+
test('throws when element with role="radio" is not checked but expected to be', () => {
115+
const {queryByTestId} = render(
116+
`<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />`,
117+
)
118+
119+
expect(() =>
120+
expect(queryByTestId('aria-checkbox-unchecked')).toBeChecked(),
121+
).toThrowError()
122+
})
123+
124+
test('throws when element with role="checkbox" has an invalid aria-checked attribute', () => {
125+
const {queryByTestId} = render(
126+
`<div role="checkbox" aria-checked="something" data-testid="aria-checkbox-invalid" />`,
127+
)
128+
129+
expect(() =>
130+
expect(queryByTestId('aria-checkbox-invalid')).toBeChecked(),
131+
).toThrowError(
132+
'only inputs with type="checkbox" or type="radio" or elements with role="checkbox" or role="radio" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead',
38133
)
39134
})
40135

41-
test('throws when the element is not a checkbox input', () => {
136+
test('throws when element with role="radio" has an invalid aria-checked attribute', () => {
42137
const {queryByTestId} = render(
43-
`<input type="radio" checked data-testid="radio" />`,
138+
`<div role="radio" aria-checked="something" data-testid="aria-radio-invalid" />`,
44139
)
45140

46-
expect(() => expect(queryByTestId('radio')).toBeChecked()).toThrowError(
47-
`only inputs with type=checkbox can be used with .toBeChecked(). Use .toHaveFormValues() instead`,
141+
expect(() =>
142+
expect(queryByTestId('aria-radio-invalid')).toBeChecked(),
143+
).toThrowError(
144+
'only inputs with type="checkbox" or type="radio" or elements with role="checkbox" or role="radio" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead',
145+
)
146+
})
147+
148+
test('throws when the element is not an input', () => {
149+
const {queryByTestId} = render(`<select data-testid="select"></select>`)
150+
expect(() => expect(queryByTestId('select')).toBeChecked()).toThrowError(
151+
'only inputs with type="checkbox" or type="radio" or elements with role="checkbox" or role="radio" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead',
48152
)
49153
})
50154
})
155+
156+
/* eslint max-lines-per-function:0 */

src/to-be-checked.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
import {matcherHint, printReceived} from 'jest-matcher-utils'
2-
import {checkHtmlElement, getSingleElementValue} from './utils'
2+
import {checkHtmlElement} from './utils'
33

44
export function toBeChecked(element) {
55
checkHtmlElement(element, toBeChecked, this)
66

7-
if (
8-
element.tagName.toLowerCase() !== 'input' ||
9-
element.type !== 'checkbox'
10-
) {
7+
const isValidInput = () => {
8+
return (
9+
element.tagName.toLowerCase() === 'input' &&
10+
['checkbox', 'radio'].includes(element.type)
11+
)
12+
}
13+
14+
const isValidAriaElement = () => {
15+
return (
16+
['checkbox', 'radio'].includes(element.getAttribute('role')) &&
17+
['true', 'false'].includes(element.getAttribute('aria-checked'))
18+
)
19+
}
20+
21+
if (!isValidInput() && !isValidAriaElement()) {
1122
return {
1223
pass: false,
1324
message: () =>
14-
'only inputs with type=checkbox can be used with .toBeChecked(). Use .toHaveFormValues() instead',
25+
'only inputs with type="checkbox" or type="radio" or elements with role="checkbox" or role="radio" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead',
1526
}
1627
}
1728

18-
const isChecked = getSingleElementValue(element)
29+
const isChecked = () => {
30+
if (isValidInput()) return element.checked
31+
return element.getAttribute('aria-checked') === 'true'
32+
}
1933

2034
return {
21-
pass: isChecked,
35+
pass: isChecked(),
2236
message: () => {
23-
const is = isChecked ? 'is' : 'is not'
37+
const is = isChecked() ? 'is' : 'is not'
2438
return [
2539
matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''),
2640
'',

0 commit comments

Comments
 (0)