Skip to content

Commit e46299b

Browse files
authoredMay 19, 2020
feat: add toBePartiallyChecked matcher (#249)
* feat: add toBePartiallyChecked matcher * refactor: don't check the aria-checked in isValidAriaElement
1 parent 5c9e8e5 commit e46299b

File tree

4 files changed

+222
-1
lines changed

4 files changed

+222
-1
lines changed
 

‎README.md

+47-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ clear to read and to maintain.
4646
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
4747
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
4848

49-
5049
- [Installation](#installation)
5150
- [Usage](#usage)
5251
- [Custom matchers](#custom-matchers)
@@ -69,6 +68,7 @@ clear to read and to maintain.
6968
- [`toHaveValue`](#tohavevalue)
7069
- [`toHaveDisplayValue`](#tohavedisplayvalue)
7170
- [`toBeChecked`](#tobechecked)
71+
- [`toBePartiallyChecked`](#tobepartiallychecked)
7272
- [`toHaveDescription`](#tohavedescription)
7373
- [Deprecated matchers](#deprecated-matchers)
7474
- [`toBeInTheDOM`](#tobeinthedom)
@@ -895,6 +895,52 @@ expect(ariaSwitchUnchecked).not.toBeChecked()
895895

896896
<hr />
897897

898+
### `toBePartiallyChecked`
899+
900+
```typescript
901+
toBePartiallyChecked()
902+
```
903+
904+
This allows you to check whether the given element is partially checked. It
905+
accepts an `input` of type `checkbox` and elements with a `role` of `checkbox`
906+
with a `aria-checked="mixed"`, or `input` of type `checkbox` with
907+
`indeterminate` set to `true`
908+
909+
#### Examples
910+
911+
```html
912+
<input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
913+
<input type="checkbox" checked data-testid="input-checkbox-checked" />
914+
<input type="checkbox" data-testid="input-checkbox-unchecked" />
915+
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
916+
<div
917+
role="checkbox"
918+
aria-checked="false"
919+
data-testid="aria-checkbox-unchecked"
920+
/>
921+
<input type="checkbox" data-testid="input-checkbox-indeterminate" />
922+
```
923+
924+
```javascript
925+
const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
926+
const inputCheckboxChecked = getByTestId('input-checkbox-checked')
927+
const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
928+
const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
929+
const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
930+
const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')
931+
932+
expect(ariaCheckboxMixed).toBePartiallyChecked()
933+
expect(inputCheckboxChecked).not.toBePartiallyChecked()
934+
expect(inputCheckboxUnchecked).not.toBePartiallyChecked()
935+
expect(ariaCheckboxChecked).not.toBePartiallyChecked()
936+
expect(ariaCheckboxUnchecked).not.toBePartiallyChecked()
937+
938+
inputCheckboxIndeterminate.indeterminate = true
939+
expect(inputCheckboxIndeterminate).toBePartiallyChecked()
940+
```
941+
942+
<hr />
943+
898944
### `toHaveDescription`
899945

900946
```typescript
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {render} from './helpers/test-utils'
2+
3+
describe('.toBePartiallyChecked', () => {
4+
test('handles input checkbox with aria-checked', () => {
5+
const {queryByTestId} = render(`
6+
<input type="checkbox" aria-checked="mixed" data-testid="checkbox-mixed" />
7+
<input type="checkbox" checked data-testid="checkbox-checked" />
8+
<input type="checkbox" data-testid="checkbox-unchecked" />
9+
`)
10+
11+
expect(queryByTestId('checkbox-mixed')).toBePartiallyChecked()
12+
expect(queryByTestId('checkbox-checked')).not.toBePartiallyChecked()
13+
expect(queryByTestId('checkbox-unchecked')).not.toBePartiallyChecked()
14+
})
15+
16+
test('handles input checkbox set as indeterminate', () => {
17+
const {queryByTestId} = render(`
18+
<input type="checkbox" data-testid="checkbox-mixed" />
19+
<input type="checkbox" checked data-testid="checkbox-checked" />
20+
<input type="checkbox" data-testid="checkbox-unchecked" />
21+
`)
22+
23+
queryByTestId('checkbox-mixed').indeterminate = true
24+
25+
expect(queryByTestId('checkbox-mixed')).toBePartiallyChecked()
26+
expect(queryByTestId('checkbox-checked')).not.toBePartiallyChecked()
27+
expect(queryByTestId('checkbox-unchecked')).not.toBePartiallyChecked()
28+
})
29+
30+
test('handles element with role="checkbox"', () => {
31+
const {queryByTestId} = render(`
32+
<div role="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
33+
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
34+
<div role="checkbox" aria-checked="false" data-testid="aria-checkbox-unchecked" />
35+
`)
36+
37+
expect(queryByTestId('aria-checkbox-mixed')).toBePartiallyChecked()
38+
expect(queryByTestId('aria-checkbox-checked')).not.toBePartiallyChecked()
39+
expect(queryByTestId('aria-checkbox-unchecked')).not.toBePartiallyChecked()
40+
})
41+
42+
test('throws when input checkbox is mixed but expected not to be', () => {
43+
const {queryByTestId} = render(
44+
`<input type="checkbox" aria-checked="mixed" data-testid="checkbox-mixed" />`,
45+
)
46+
47+
expect(() =>
48+
expect(queryByTestId('checkbox-mixed')).not.toBePartiallyChecked(),
49+
).toThrowError()
50+
})
51+
52+
test('throws when input checkbox is indeterminate but expected not to be', () => {
53+
const {queryByTestId} = render(
54+
`<input type="checkbox" data-testid="checkbox-mixed" />`,
55+
)
56+
57+
queryByTestId('checkbox-mixed').indeterminate = true
58+
59+
expect(() =>
60+
expect(queryByTestId('input-mixed')).not.toBePartiallyChecked(),
61+
).toThrowError()
62+
})
63+
64+
test('throws when input checkbox is not checked but expected to be', () => {
65+
const {queryByTestId} = render(
66+
`<input type="checkbox" data-testid="checkbox-empty" />`,
67+
)
68+
69+
expect(() =>
70+
expect(queryByTestId('checkbox-empty')).toBePartiallyChecked(),
71+
).toThrowError()
72+
})
73+
74+
test('throws when element with role="checkbox" is partially checked but expected not to be', () => {
75+
const {queryByTestId} = render(
76+
`<div role="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />`,
77+
)
78+
79+
expect(() =>
80+
expect(queryByTestId('aria-checkbox-mixed')).not.toBePartiallyChecked(),
81+
).toThrowError()
82+
})
83+
84+
test('throws when element with role="checkbox" is checked but expected to be partially checked', () => {
85+
const {queryByTestId} = render(
86+
`<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />`,
87+
)
88+
89+
expect(() =>
90+
expect(queryByTestId('aria-checkbox-checked')).toBePartiallyChecked(),
91+
).toThrowError()
92+
})
93+
94+
test('throws when element with role="checkbox" is not checked but expected to be', () => {
95+
const {queryByTestId} = render(
96+
`<div role="checkbox" aria-checked="false" data-testid="aria-checkbox" />`,
97+
)
98+
99+
expect(() =>
100+
expect(queryByTestId('aria-checkbox')).toBePartiallyChecked(),
101+
).toThrowError()
102+
})
103+
104+
test('throws when element with role="checkbox" has an invalid aria-checked attribute', () => {
105+
const {queryByTestId} = render(
106+
`<div role="checkbox" aria-checked="something" data-testid="aria-checkbox-invalid" />`,
107+
)
108+
109+
expect(() =>
110+
expect(queryByTestId('aria-checkbox-invalid')).toBePartiallyChecked(),
111+
).toThrowError()
112+
})
113+
114+
test('throws when the element is not a checkbox', () => {
115+
const {queryByTestId} = render(`<select data-testid="select"></select>`)
116+
expect(() =>
117+
expect(queryByTestId('select')).toBePartiallyChecked(),
118+
).toThrowError(
119+
'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead',
120+
)
121+
})
122+
})

‎src/matchers.js

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {toBeInvalid, toBeValid} from './to-be-invalid'
1616
import {toHaveValue} from './to-have-value'
1717
import {toHaveDisplayValue} from './to-have-display-value'
1818
import {toBeChecked} from './to-be-checked'
19+
import {toBePartiallyChecked} from './to-be-partially-checked'
1920
import {toHaveDescription} from './to-have-description'
2021

2122
export {
@@ -39,5 +40,6 @@ export {
3940
toHaveValue,
4041
toHaveDisplayValue,
4142
toBeChecked,
43+
toBePartiallyChecked,
4244
toHaveDescription,
4345
}

‎src/to-be-partially-checked.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {matcherHint, printReceived} from 'jest-matcher-utils'
2+
import {checkHtmlElement} from './utils'
3+
4+
export function toBePartiallyChecked(element) {
5+
checkHtmlElement(element, toBePartiallyChecked, this)
6+
7+
const isValidInput = () => {
8+
return (
9+
element.tagName.toLowerCase() === 'input' && element.type === 'checkbox'
10+
)
11+
}
12+
13+
const isValidAriaElement = () => {
14+
return element.getAttribute('role') === 'checkbox'
15+
}
16+
17+
if (!isValidInput() && !isValidAriaElement()) {
18+
return {
19+
pass: false,
20+
message: () =>
21+
'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead',
22+
}
23+
}
24+
25+
const isPartiallyChecked = () => {
26+
const isAriaMixed = element.getAttribute('aria-checked') === 'mixed'
27+
28+
if (isValidInput()) {
29+
return element.indeterminate || isAriaMixed
30+
}
31+
32+
return isAriaMixed
33+
}
34+
35+
return {
36+
pass: isPartiallyChecked(),
37+
message: () => {
38+
const is = isPartiallyChecked() ? 'is' : 'is not'
39+
return [
40+
matcherHint(
41+
`${this.isNot ? '.not' : ''}.toBePartiallyChecked`,
42+
'element',
43+
'',
44+
),
45+
'',
46+
`Received element ${is} partially checked:`,
47+
` ${printReceived(element.cloneNode(false))}`,
48+
].join('\n')
49+
},
50+
}
51+
}

0 commit comments

Comments
 (0)
Please sign in to comment.