Skip to content

Commit

Permalink
feat: Add toHaveDisplayValue() matcher (#223)
Browse files Browse the repository at this point in the history
* Introduce .toHaveDisplay() custom matcher
* Improve docs by showing getByLabelText usage
* Apply suggestions from code review
* Add input and textarea examples to readme
* Add support for input and textarea elements
* Update README.md
* Update docs description to match the new elements supported
* Add value change expects for input, select and textarea

Co-authored-by: Toni Villena <tonivj5@Gmail.com>
Co-authored-by: Ernesto García <gnapse@gmail.com>
  • Loading branch information
3 people committed Apr 9, 2020
1 parent 779448f commit 840414f
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 2 deletions.
61 changes: 59 additions & 2 deletions README.md
Expand Up @@ -66,6 +66,7 @@ clear to read and to maintain.
- [`toHaveStyle`](#tohavestyle)
- [`toHaveTextContent`](#tohavetextcontent)
- [`toHaveValue`](#tohavevalue)
- [`toHaveDisplayValue`](#tohavedisplayvalue)
- [`toBeChecked`](#tobechecked)
- [Deprecated matchers](#deprecated-matchers)
- [`toBeInTheDOM`](#tobeinthedom)
Expand Down Expand Up @@ -732,8 +733,8 @@ toHaveValue(value: string | string[] | number)

This allows you to check whether the given form element has the specified value.
It accepts `<input>`, `<select>` and `<textarea>` elements with the exception of
of `<input type="checkbox">` and `<input type="radio">`, which can be
meaningfully matched only using [`toBeChecked`](#tobechecked) or
`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
matched only using [`toBeChecked`](#tobechecked) or
[`toHaveFormValues`](#tohaveformvalues).

For all other form elements, the value is matched using the same algorithm as in
Expand Down Expand Up @@ -768,6 +769,61 @@ expect(selectInput).not.toHaveValue(['second', 'third'])

<hr />

### `toHaveDisplayValue`

```typescript
toHaveDisplayValue(value: string | string[])
```

This allows you to check whether the given form element has the specified
displayed value (the one the end user will see). It accepts `<input>`,
`<select>` and `<textarea>` elements with the exception of
`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
matched only using [`toBeChecked`](#tobechecked) or
[`toHaveFormValues`](#tohaveformvalues).

#### Examples

```html
<label for="input-example">First name</label>
<input type="text" id="input-example" value="Luca" />

<label for="textarea-example">Description</label>
<textarea id="textarea-example">An example description here.</textarea>

<label for="single-select-example">Fruit</label>
<select id="single-select-example">
<option value="">Select a fruit...</option>
<option value="banana">Banana</option>
<option value="ananas">Ananas</option>
<option value="avocado">Avocado</option>
</select>

<label for="mutiple-select-example">Fruits</label>
<select id="multiple-select-example" multiple>
<option value="">Select a fruit...</option>
<option value="banana" selected>Banana</option>
<option value="ananas">Ananas</option>
<option value="avocado" selected>Avocado</option>
</select>
```

##### Using DOM Testing Library

```javascript
const input = screen.getByLabelText('First name')
const textarea = screen.getByLabelText('Description')
const selectSingle = screen.getByLabelText('Fruit')
const selectMultiple = screen.getByLabelText('Fruits')

expect(input).toHaveDisplayValue('Luca')
expect(textarea).toHaveDisplayValue('An example description here.')
expect(selectSingle).toHaveDisplayValue('Select a fruit...')
expect(selectMultiple).toHaveDisplayValue(['Banana', 'Avocado'])
```

<hr />

### `toBeChecked`

```typescript
Expand Down Expand Up @@ -959,6 +1015,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
119 changes: 119 additions & 0 deletions src/__tests__/to-have-display-value.js
@@ -0,0 +1,119 @@
import {render} from './helpers/test-utils'

test('it should work as expected', () => {
const {queryByTestId} = render(`
<select id="fruits" data-testid="select">
<option value="">Select a fruit...</option>
<option value="ananas">Ananas</option>
<option value="banana">Banana</option>
<option value="avocado">Avocado</option>
</select>
`)

expect(queryByTestId('select')).toHaveDisplayValue('Select a fruit...')
expect(queryByTestId('select')).not.toHaveDisplayValue('Banana')
expect(() =>
expect(queryByTestId('select')).not.toHaveDisplayValue('Select a fruit...'),
).toThrow()
expect(() =>
expect(queryByTestId('select')).toHaveDisplayValue('Ananas'),
).toThrow()

queryByTestId('select').value = 'banana'
expect(queryByTestId('select')).toHaveDisplayValue('Banana')
})

test('it should work with select multiple', () => {
const {queryByTestId} = render(`
<select id="fruits" data-testid="select" multiple>
<option value="">Select a fruit...</option>
<option value="ananas" selected>Ananas</option>
<option value="banana">Banana</option>
<option value="avocado" selected>Avocado</option>
</select>
`)

expect(queryByTestId('select')).toHaveDisplayValue(['Ananas', 'Avocado'])
expect(() =>
expect(queryByTestId('select')).not.toHaveDisplayValue([
'Ananas',
'Avocado',
]),
).toThrow()

expect(queryByTestId('select')).not.toHaveDisplayValue('Ananas')
expect(() =>
expect(queryByTestId('select')).toHaveDisplayValue('Ananas'),
).toThrow()

Array.from(queryByTestId('select').options).forEach(option => {
option.selected = ['ananas', 'banana'].includes(option.value)
})

expect(queryByTestId('select')).toHaveDisplayValue(['Ananas', 'Banana'])
})

test('it should work with input elements', () => {
const {queryByTestId} = render(`
<input type="text" data-testid="input" value="Luca" />
`)

expect(queryByTestId('input')).toHaveDisplayValue('Luca')

queryByTestId('input').value = 'Piero'
expect(queryByTestId('input')).toHaveDisplayValue('Piero')
})

test('it should work with textarea elements', () => {
const {queryByTestId} = render(
'<textarea data-testid="textarea-example">An example description here.</textarea>',
)

expect(queryByTestId('textarea-example')).toHaveDisplayValue(
'An example description here.',
)

queryByTestId('textarea-example').value = 'Another example'
expect(queryByTestId('textarea-example')).toHaveDisplayValue(
'Another example',
)
})

test('it should throw if element is not valid', () => {
const {queryByTestId} = render(`
<div data-testid="div">Banana</div>
<input type="radio" data-testid="radio" value="Something" />
<input type="checkbox" data-testid="checkbox" />
`)

let errorMessage
try {
expect(queryByTestId('div')).toHaveDisplayValue('Banana')
} catch (err) {
errorMessage = err.message
}

expect(errorMessage).toMatchInlineSnapshot(
`".toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead."`,
)

try {
expect(queryByTestId('radio')).toHaveDisplayValue('Something')
} catch (err) {
errorMessage = err.message
}

expect(errorMessage).toMatchInlineSnapshot(
`".toHaveDisplayValue() currently does not support input[type=\\"radio\\"], try with another matcher instead."`,
)

try {
expect(queryByTestId('checkbox')).toHaveDisplayValue(true)
} catch (err) {
errorMessage = err.message
}

expect(errorMessage).toMatchInlineSnapshot(
`".toHaveDisplayValue() currently does not support input[type=\\"checkbox\\"], try with another matcher instead."`,
)
})
2 changes: 2 additions & 0 deletions src/matchers.js
Expand Up @@ -14,6 +14,7 @@ import {toBeDisabled, toBeEnabled} from './to-be-disabled'
import {toBeRequired} from './to-be-required'
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'

export {
Expand All @@ -35,5 +36,6 @@ export {
toBeInvalid,
toBeValid,
toHaveValue,
toHaveDisplayValue,
toBeChecked,
}
44 changes: 44 additions & 0 deletions src/to-have-display-value.js
@@ -0,0 +1,44 @@
import {matcherHint} from 'jest-matcher-utils'

import {checkHtmlElement, getMessage} from './utils'

export function toHaveDisplayValue(htmlElement, expectedValue) {
checkHtmlElement(htmlElement, toHaveDisplayValue, this)
const tagName = htmlElement.tagName.toLowerCase()

if (!['select', 'input', 'textarea'].includes(tagName)) {
throw new Error(
'.toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead.',
)
}

if (tagName === 'input' && ['radio', 'checkbox'].includes(htmlElement.type)) {
throw new Error(
`.toHaveDisplayValue() currently does not support input[type="${htmlElement.type}"], try with another matcher instead.`,
)
}

const value =
tagName === 'select'
? Array.from(htmlElement)
.filter(option => option.selected)
.map(option => option.textContent)
.toString()
: htmlElement.value

return {
pass: value === expectedValue.toString(),
message: () =>
getMessage(
matcherHint(
`${this.isNot ? '.not' : ''}.toHaveDisplayValue`,
'element',
'',
),
`Expected element ${this.isNot ? 'not ' : ''}to have display value`,
expectedValue,
'Received',
value,
),
}
}

0 comments on commit 840414f

Please sign in to comment.