Skip to content

Commit 840414f

Browse files
cloud-walkertonivj5gnapse
authored
feat: Add toHaveDisplayValue() matcher (#223)
* 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 <[email protected]> Co-authored-by: Ernesto García <[email protected]>
1 parent 779448f commit 840414f

File tree

4 files changed

+224
-2
lines changed

4 files changed

+224
-2
lines changed

README.md

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ clear to read and to maintain.
6666
- [`toHaveStyle`](#tohavestyle)
6767
- [`toHaveTextContent`](#tohavetextcontent)
6868
- [`toHaveValue`](#tohavevalue)
69+
- [`toHaveDisplayValue`](#tohavedisplayvalue)
6970
- [`toBeChecked`](#tobechecked)
7071
- [Deprecated matchers](#deprecated-matchers)
7172
- [`toBeInTheDOM`](#tobeinthedom)
@@ -732,8 +733,8 @@ toHaveValue(value: string | string[] | number)
732733

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

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

769770
<hr />
770771

772+
### `toHaveDisplayValue`
773+
774+
```typescript
775+
toHaveDisplayValue(value: string | string[])
776+
```
777+
778+
This allows you to check whether the given form element has the specified
779+
displayed value (the one the end user will see). It accepts `<input>`,
780+
`<select>` and `<textarea>` elements with the exception of
781+
`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
782+
matched only using [`toBeChecked`](#tobechecked) or
783+
[`toHaveFormValues`](#tohaveformvalues).
784+
785+
#### Examples
786+
787+
```html
788+
<label for="input-example">First name</label>
789+
<input type="text" id="input-example" value="Luca" />
790+
791+
<label for="textarea-example">Description</label>
792+
<textarea id="textarea-example">An example description here.</textarea>
793+
794+
<label for="single-select-example">Fruit</label>
795+
<select id="single-select-example">
796+
<option value="">Select a fruit...</option>
797+
<option value="banana">Banana</option>
798+
<option value="ananas">Ananas</option>
799+
<option value="avocado">Avocado</option>
800+
</select>
801+
802+
<label for="mutiple-select-example">Fruits</label>
803+
<select id="multiple-select-example" multiple>
804+
<option value="">Select a fruit...</option>
805+
<option value="banana" selected>Banana</option>
806+
<option value="ananas">Ananas</option>
807+
<option value="avocado" selected>Avocado</option>
808+
</select>
809+
```
810+
811+
##### Using DOM Testing Library
812+
813+
```javascript
814+
const input = screen.getByLabelText('First name')
815+
const textarea = screen.getByLabelText('Description')
816+
const selectSingle = screen.getByLabelText('Fruit')
817+
const selectMultiple = screen.getByLabelText('Fruits')
818+
819+
expect(input).toHaveDisplayValue('Luca')
820+
expect(textarea).toHaveDisplayValue('An example description here.')
821+
expect(selectSingle).toHaveDisplayValue('Select a fruit...')
822+
expect(selectMultiple).toHaveDisplayValue(['Banana', 'Avocado'])
823+
```
824+
825+
<hr />
826+
771827
### `toBeChecked`
772828

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

9601016
<!-- markdownlint-enable -->
9611017
<!-- prettier-ignore-end -->
1018+
9621019
<!-- ALL-CONTRIBUTORS-LIST:END -->
9631020

9641021
This project follows the [all-contributors][all-contributors] specification.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {render} from './helpers/test-utils'
2+
3+
test('it should work as expected', () => {
4+
const {queryByTestId} = render(`
5+
<select id="fruits" data-testid="select">
6+
<option value="">Select a fruit...</option>
7+
<option value="ananas">Ananas</option>
8+
<option value="banana">Banana</option>
9+
<option value="avocado">Avocado</option>
10+
</select>
11+
`)
12+
13+
expect(queryByTestId('select')).toHaveDisplayValue('Select a fruit...')
14+
expect(queryByTestId('select')).not.toHaveDisplayValue('Banana')
15+
expect(() =>
16+
expect(queryByTestId('select')).not.toHaveDisplayValue('Select a fruit...'),
17+
).toThrow()
18+
expect(() =>
19+
expect(queryByTestId('select')).toHaveDisplayValue('Ananas'),
20+
).toThrow()
21+
22+
queryByTestId('select').value = 'banana'
23+
expect(queryByTestId('select')).toHaveDisplayValue('Banana')
24+
})
25+
26+
test('it should work with select multiple', () => {
27+
const {queryByTestId} = render(`
28+
<select id="fruits" data-testid="select" multiple>
29+
<option value="">Select a fruit...</option>
30+
<option value="ananas" selected>Ananas</option>
31+
<option value="banana">Banana</option>
32+
<option value="avocado" selected>Avocado</option>
33+
</select>
34+
`)
35+
36+
expect(queryByTestId('select')).toHaveDisplayValue(['Ananas', 'Avocado'])
37+
expect(() =>
38+
expect(queryByTestId('select')).not.toHaveDisplayValue([
39+
'Ananas',
40+
'Avocado',
41+
]),
42+
).toThrow()
43+
44+
expect(queryByTestId('select')).not.toHaveDisplayValue('Ananas')
45+
expect(() =>
46+
expect(queryByTestId('select')).toHaveDisplayValue('Ananas'),
47+
).toThrow()
48+
49+
Array.from(queryByTestId('select').options).forEach(option => {
50+
option.selected = ['ananas', 'banana'].includes(option.value)
51+
})
52+
53+
expect(queryByTestId('select')).toHaveDisplayValue(['Ananas', 'Banana'])
54+
})
55+
56+
test('it should work with input elements', () => {
57+
const {queryByTestId} = render(`
58+
<input type="text" data-testid="input" value="Luca" />
59+
`)
60+
61+
expect(queryByTestId('input')).toHaveDisplayValue('Luca')
62+
63+
queryByTestId('input').value = 'Piero'
64+
expect(queryByTestId('input')).toHaveDisplayValue('Piero')
65+
})
66+
67+
test('it should work with textarea elements', () => {
68+
const {queryByTestId} = render(
69+
'<textarea data-testid="textarea-example">An example description here.</textarea>',
70+
)
71+
72+
expect(queryByTestId('textarea-example')).toHaveDisplayValue(
73+
'An example description here.',
74+
)
75+
76+
queryByTestId('textarea-example').value = 'Another example'
77+
expect(queryByTestId('textarea-example')).toHaveDisplayValue(
78+
'Another example',
79+
)
80+
})
81+
82+
test('it should throw if element is not valid', () => {
83+
const {queryByTestId} = render(`
84+
<div data-testid="div">Banana</div>
85+
<input type="radio" data-testid="radio" value="Something" />
86+
<input type="checkbox" data-testid="checkbox" />
87+
`)
88+
89+
let errorMessage
90+
try {
91+
expect(queryByTestId('div')).toHaveDisplayValue('Banana')
92+
} catch (err) {
93+
errorMessage = err.message
94+
}
95+
96+
expect(errorMessage).toMatchInlineSnapshot(
97+
`".toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead."`,
98+
)
99+
100+
try {
101+
expect(queryByTestId('radio')).toHaveDisplayValue('Something')
102+
} catch (err) {
103+
errorMessage = err.message
104+
}
105+
106+
expect(errorMessage).toMatchInlineSnapshot(
107+
`".toHaveDisplayValue() currently does not support input[type=\\"radio\\"], try with another matcher instead."`,
108+
)
109+
110+
try {
111+
expect(queryByTestId('checkbox')).toHaveDisplayValue(true)
112+
} catch (err) {
113+
errorMessage = err.message
114+
}
115+
116+
expect(errorMessage).toMatchInlineSnapshot(
117+
`".toHaveDisplayValue() currently does not support input[type=\\"checkbox\\"], try with another matcher instead."`,
118+
)
119+
})

src/matchers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {toBeDisabled, toBeEnabled} from './to-be-disabled'
1414
import {toBeRequired} from './to-be-required'
1515
import {toBeInvalid, toBeValid} from './to-be-invalid'
1616
import {toHaveValue} from './to-have-value'
17+
import {toHaveDisplayValue} from './to-have-display-value'
1718
import {toBeChecked} from './to-be-checked'
1819

1920
export {
@@ -35,5 +36,6 @@ export {
3536
toBeInvalid,
3637
toBeValid,
3738
toHaveValue,
39+
toHaveDisplayValue,
3840
toBeChecked,
3941
}

src/to-have-display-value.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {matcherHint} from 'jest-matcher-utils'
2+
3+
import {checkHtmlElement, getMessage} from './utils'
4+
5+
export function toHaveDisplayValue(htmlElement, expectedValue) {
6+
checkHtmlElement(htmlElement, toHaveDisplayValue, this)
7+
const tagName = htmlElement.tagName.toLowerCase()
8+
9+
if (!['select', 'input', 'textarea'].includes(tagName)) {
10+
throw new Error(
11+
'.toHaveDisplayValue() currently supports only input, textarea or select elements, try with another matcher instead.',
12+
)
13+
}
14+
15+
if (tagName === 'input' && ['radio', 'checkbox'].includes(htmlElement.type)) {
16+
throw new Error(
17+
`.toHaveDisplayValue() currently does not support input[type="${htmlElement.type}"], try with another matcher instead.`,
18+
)
19+
}
20+
21+
const value =
22+
tagName === 'select'
23+
? Array.from(htmlElement)
24+
.filter(option => option.selected)
25+
.map(option => option.textContent)
26+
.toString()
27+
: htmlElement.value
28+
29+
return {
30+
pass: value === expectedValue.toString(),
31+
message: () =>
32+
getMessage(
33+
matcherHint(
34+
`${this.isNot ? '.not' : ''}.toHaveDisplayValue`,
35+
'element',
36+
'',
37+
),
38+
`Expected element ${this.isNot ? 'not ' : ''}to have display value`,
39+
expectedValue,
40+
'Received',
41+
value,
42+
),
43+
}
44+
}

0 commit comments

Comments
 (0)