Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add toHaveDisplayValue() matcher #223

Merged
merged 13 commits into from Apr 9, 2020
Merged
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')
Comment on lines +13 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if the test also covered the case of changing the selected option programmatically to banana and then check that toHaveDisplayValue('Banana') pass.

And maybe do it for the other cases as well (textarea and input).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I'm wondering why the repository is not using fireEvent from the testing-library core (or even userEvent) 🤔

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(),
gnapse marked this conversation as resolved.
Show resolved Hide resolved
message: () =>
getMessage(
matcherHint(
`${this.isNot ? '.not' : ''}.toHaveDisplayValue`,
'element',
'',
),
`Expected element ${this.isNot ? 'not ' : ''}to have display value`,
expectedValue,
'Received',
value,
),
}
}