Skip to content

Commit

Permalink
feat: add toBePartiallyChecked matcher (#249)
Browse files Browse the repository at this point in the history
* feat: add toBePartiallyChecked matcher

* refactor: don't check the aria-checked in isValidAriaElement
  • Loading branch information
rubenmoya authored May 19, 2020
1 parent 5c9e8e5 commit e46299b
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 1 deletion.
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ clear to read and to maintain.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Usage](#usage)
- [Custom matchers](#custom-matchers)
Expand All @@ -69,6 +68,7 @@ clear to read and to maintain.
- [`toHaveValue`](#tohavevalue)
- [`toHaveDisplayValue`](#tohavedisplayvalue)
- [`toBeChecked`](#tobechecked)
- [`toBePartiallyChecked`](#tobepartiallychecked)
- [`toHaveDescription`](#tohavedescription)
- [Deprecated matchers](#deprecated-matchers)
- [`toBeInTheDOM`](#tobeinthedom)
Expand Down Expand Up @@ -895,6 +895,52 @@ expect(ariaSwitchUnchecked).not.toBeChecked()

<hr />

### `toBePartiallyChecked`

```typescript
toBePartiallyChecked()
```

This allows you to check whether the given element is partially checked. It
accepts an `input` of type `checkbox` and elements with a `role` of `checkbox`
with a `aria-checked="mixed"`, or `input` of type `checkbox` with
`indeterminate` set to `true`

#### Examples

```html
<input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
<input type="checkbox" checked data-testid="input-checkbox-checked" />
<input type="checkbox" data-testid="input-checkbox-unchecked" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div
role="checkbox"
aria-checked="false"
data-testid="aria-checkbox-unchecked"
/>
<input type="checkbox" data-testid="input-checkbox-indeterminate" />
```

```javascript
const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
const inputCheckboxChecked = getByTestId('input-checkbox-checked')
const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')

expect(ariaCheckboxMixed).toBePartiallyChecked()
expect(inputCheckboxChecked).not.toBePartiallyChecked()
expect(inputCheckboxUnchecked).not.toBePartiallyChecked()
expect(ariaCheckboxChecked).not.toBePartiallyChecked()
expect(ariaCheckboxUnchecked).not.toBePartiallyChecked()

inputCheckboxIndeterminate.indeterminate = true
expect(inputCheckboxIndeterminate).toBePartiallyChecked()
```

<hr />

### `toHaveDescription`

```typescript
Expand Down
122 changes: 122 additions & 0 deletions src/__tests__/to-be-partially-checked.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {render} from './helpers/test-utils'

describe('.toBePartiallyChecked', () => {
test('handles input checkbox with aria-checked', () => {
const {queryByTestId} = render(`
<input type="checkbox" aria-checked="mixed" data-testid="checkbox-mixed" />
<input type="checkbox" checked data-testid="checkbox-checked" />
<input type="checkbox" data-testid="checkbox-unchecked" />
`)

expect(queryByTestId('checkbox-mixed')).toBePartiallyChecked()
expect(queryByTestId('checkbox-checked')).not.toBePartiallyChecked()
expect(queryByTestId('checkbox-unchecked')).not.toBePartiallyChecked()
})

test('handles input checkbox set as indeterminate', () => {
const {queryByTestId} = render(`
<input type="checkbox" data-testid="checkbox-mixed" />
<input type="checkbox" checked data-testid="checkbox-checked" />
<input type="checkbox" data-testid="checkbox-unchecked" />
`)

queryByTestId('checkbox-mixed').indeterminate = true

expect(queryByTestId('checkbox-mixed')).toBePartiallyChecked()
expect(queryByTestId('checkbox-checked')).not.toBePartiallyChecked()
expect(queryByTestId('checkbox-unchecked')).not.toBePartiallyChecked()
})

test('handles element with role="checkbox"', () => {
const {queryByTestId} = render(`
<div role="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div role="checkbox" aria-checked="false" data-testid="aria-checkbox-unchecked" />
`)

expect(queryByTestId('aria-checkbox-mixed')).toBePartiallyChecked()
expect(queryByTestId('aria-checkbox-checked')).not.toBePartiallyChecked()
expect(queryByTestId('aria-checkbox-unchecked')).not.toBePartiallyChecked()
})

test('throws when input checkbox is mixed but expected not to be', () => {
const {queryByTestId} = render(
`<input type="checkbox" aria-checked="mixed" data-testid="checkbox-mixed" />`,
)

expect(() =>
expect(queryByTestId('checkbox-mixed')).not.toBePartiallyChecked(),
).toThrowError()
})

test('throws when input checkbox is indeterminate but expected not to be', () => {
const {queryByTestId} = render(
`<input type="checkbox" data-testid="checkbox-mixed" />`,
)

queryByTestId('checkbox-mixed').indeterminate = true

expect(() =>
expect(queryByTestId('input-mixed')).not.toBePartiallyChecked(),
).toThrowError()
})

test('throws when input checkbox is not checked but expected to be', () => {
const {queryByTestId} = render(
`<input type="checkbox" data-testid="checkbox-empty" />`,
)

expect(() =>
expect(queryByTestId('checkbox-empty')).toBePartiallyChecked(),
).toThrowError()
})

test('throws when element with role="checkbox" is partially checked but expected not to be', () => {
const {queryByTestId} = render(
`<div role="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />`,
)

expect(() =>
expect(queryByTestId('aria-checkbox-mixed')).not.toBePartiallyChecked(),
).toThrowError()
})

test('throws when element with role="checkbox" is checked but expected to be partially checked', () => {
const {queryByTestId} = render(
`<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />`,
)

expect(() =>
expect(queryByTestId('aria-checkbox-checked')).toBePartiallyChecked(),
).toThrowError()
})

test('throws when element with role="checkbox" is not checked but expected to be', () => {
const {queryByTestId} = render(
`<div role="checkbox" aria-checked="false" data-testid="aria-checkbox" />`,
)

expect(() =>
expect(queryByTestId('aria-checkbox')).toBePartiallyChecked(),
).toThrowError()
})

test('throws when element with role="checkbox" has an invalid aria-checked attribute', () => {
const {queryByTestId} = render(
`<div role="checkbox" aria-checked="something" data-testid="aria-checkbox-invalid" />`,
)

expect(() =>
expect(queryByTestId('aria-checkbox-invalid')).toBePartiallyChecked(),
).toThrowError()
})

test('throws when the element is not a checkbox', () => {
const {queryByTestId} = render(`<select data-testid="select"></select>`)
expect(() =>
expect(queryByTestId('select')).toBePartiallyChecked(),
).toThrowError(
'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead',
)
})
})
2 changes: 2 additions & 0 deletions src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {toBeInvalid, toBeValid} from './to-be-invalid'
import {toHaveValue} from './to-have-value'
import {toHaveDisplayValue} from './to-have-display-value'
import {toBeChecked} from './to-be-checked'
import {toBePartiallyChecked} from './to-be-partially-checked'
import {toHaveDescription} from './to-have-description'

export {
Expand All @@ -39,5 +40,6 @@ export {
toHaveValue,
toHaveDisplayValue,
toBeChecked,
toBePartiallyChecked,
toHaveDescription,
}
51 changes: 51 additions & 0 deletions src/to-be-partially-checked.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {matcherHint, printReceived} from 'jest-matcher-utils'
import {checkHtmlElement} from './utils'

export function toBePartiallyChecked(element) {
checkHtmlElement(element, toBePartiallyChecked, this)

const isValidInput = () => {
return (
element.tagName.toLowerCase() === 'input' && element.type === 'checkbox'
)
}

const isValidAriaElement = () => {
return element.getAttribute('role') === 'checkbox'
}

if (!isValidInput() && !isValidAriaElement()) {
return {
pass: false,
message: () =>
'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead',
}
}

const isPartiallyChecked = () => {
const isAriaMixed = element.getAttribute('aria-checked') === 'mixed'

if (isValidInput()) {
return element.indeterminate || isAriaMixed
}

return isAriaMixed
}

return {
pass: isPartiallyChecked(),
message: () => {
const is = isPartiallyChecked() ? 'is' : 'is not'
return [
matcherHint(
`${this.isNot ? '.not' : ''}.toBePartiallyChecked`,
'element',
'',
),
'',
`Received element ${is} partially checked:`,
` ${printReceived(element.cloneNode(false))}`,
].join('\n')
},
}
}

0 comments on commit e46299b

Please sign in to comment.