Skip to content

Commit fd747bf

Browse files
lukaszfiszergnapse
authored andcommitted
feat: Add toHaveValue matcher #82 (#90)
1 parent 28f960f commit fd747bf

File tree

8 files changed

+274
-48
lines changed

8 files changed

+274
-48
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,15 @@
258258
"ideas",
259259
"test"
260260
]
261+
},
262+
{
263+
"login": "lukaszfiszer",
264+
"name": "Łukasz Fiszer",
265+
"avatar_url": "https://avatars3.githubusercontent.com/u/1201711?v=4",
266+
"profile": "https://github.com/lukaszfiszer",
267+
"contributions": [
268+
"code"
269+
]
261270
}
262271
]
263272
}

README.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
[![downloads][downloads-badge]][npmtrends]
1717
[![MIT License][license-badge]][license]
1818

19-
[![All Contributors](https://img.shields.io/badge/all_contributors-25-orange.svg?style=flat-square)](#contributors)
19+
[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors)
2020
[![PRs Welcome][prs-badge]][prs]
2121
[![Code of Conduct][coc-badge]][coc]
2222

@@ -61,6 +61,7 @@ to maintain.
6161
- [`toHaveFormValues`](#tohaveformvalues)
6262
- [`toHaveStyle`](#tohavestyle)
6363
- [`toHaveTextContent`](#tohavetextcontent)
64+
- [`toHaveValue`](#tohavevalue)
6465
- [Deprecated matchers](#deprecated-matchers)
6566
- [`toBeInTheDOM`](#tobeinthedom)
6667
- [Inspiration](#inspiration)
@@ -839,6 +840,63 @@ expect(element).not.toHaveTextContent('content')
839840
840841
<hr />
841842
843+
### `toHaveValue`
844+
845+
```typescript
846+
toHaveValue(value: string | string[] | number)
847+
```
848+
849+
This allows you to check whether the given form element has the specified value.
850+
It accepts `<input>`, `<select>` and `<textarea>` elements with the exception of
851+
of `<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
852+
matched only using [`toHaveFormValue`](#tohaveformvalues).
853+
854+
For all other form elements, the value is matched using the same algorithm
855+
as in [`toHaveFormValue`](#tohaveformvalues) does.
856+
857+
#### Examples
858+
859+
```html
860+
<input type="text" value="text" data-testid="input-text" />
861+
<input type="number" value="5" data-testid="input-number" />
862+
<input type="text" data-testid="input-empty" />
863+
<select data-testid="multiple" multiple data-testid="select-number">
864+
<option value="first">First Value</option>
865+
<option value="second" selected>Second Value</option>
866+
<option value="third" selected>Third Value</option>
867+
</select>
868+
```
869+
870+
##### Using document.querySelector
871+
872+
```javascript
873+
const textInput = document.querySelector('[data-testid="input-text"]')
874+
const numberInput = document.querySelector('[data-testid="input-number"]')
875+
const emptyInput = document.querySelector('[data-testid="input-empty"]')
876+
const selectInput = document.querySelector('[data-testid="select-number"]')
877+
878+
expect(textInput).toHaveValue('text')
879+
expect(numberInput).toHaveValue(5)
880+
expect(emptyInput).not.toHaveValue()
881+
expect(selectInput).not.toHaveValue(['second', 'third'])
882+
```
883+
884+
##### Using dom-testing-library
885+
886+
```javascript
887+
const {getByTestId} = render(/* Rendered HTML */)
888+
889+
const textInput = getByTestId('input-text')
890+
const numberInput = getByTestId('input-number')
891+
const emptyInput = getByTestId('input-empty')
892+
const selectInput = getByTestId('select-number')
893+
894+
expect(textInput).toHaveValue('text')
895+
expect(numberInput).toHaveValue(5)
896+
expect(emptyInput).not.toHaveValue()
897+
expect(selectInput).not.toHaveValue(['second', 'third'])
898+
```
899+
842900
## Deprecated matchers
843901
844902
### `toBeInTheDOM`
@@ -912,7 +970,7 @@ Thanks goes to these people ([emoji key][emojis]):
912970
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
913971
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;" alt="Anto Aravinth"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=antoaravinth "Code") [⚠️](https://github.com/testing-library/jest-dom/commits?author=antoaravinth "Tests") [📖](https://github.com/testing-library/jest-dom/commits?author=antoaravinth "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;" alt="Jonah Moses"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[📖](https://github.com/testing-library/jest-dom/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;" alt="Łukasz Gandecki"/><br /><sub><b>Łukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=lgandecki "Code") [⚠️](https://github.com/testing-library/jest-dom/commits?author=lgandecki "Tests") [📖](https://github.com/testing-library/jest-dom/commits?author=lgandecki "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/498274?v=4" width="100px;" alt="Ivan Babak"/><br /><sub><b>Ivan Babak</b></sub>](https://sompylasar.github.io)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Asompylasar "Bug reports") [🤔](#ideas-sompylasar "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/4439618?v=4" width="100px;" alt="Jesse Day"/><br /><sub><b>Jesse Day</b></sub>](https://github.com/jday3)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=jday3 "Code") | [<img src="https://avatars0.githubusercontent.com/u/15199?v=4" width="100px;" alt="Ernesto García"/><br /><sub><b>Ernesto García</b></sub>](http://gnapse.github.io)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=gnapse "Code") [📖](https://github.com/testing-library/jest-dom/commits?author=gnapse "Documentation") [⚠️](https://github.com/testing-library/jest-dom/commits?author=gnapse "Tests") | [<img src="https://avatars0.githubusercontent.com/u/79312?v=4" width="100px;" alt="Mark Volkmann"/><br /><sub><b>Mark Volkmann</b></sub>](http://ociweb.com/mark/)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Amvolkmann "Bug reports") [💻](https://github.com/testing-library/jest-dom/commits?author=mvolkmann "Code") |
914972
| [<img src="https://avatars1.githubusercontent.com/u/1659099?v=4" width="100px;" alt="smacpherson64"/><br /><sub><b>smacpherson64</b></sub>](https://github.com/smacpherson64)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=smacpherson64 "Code") [📖](https://github.com/testing-library/jest-dom/commits?author=smacpherson64 "Documentation") [⚠️](https://github.com/testing-library/jest-dom/commits?author=smacpherson64 "Tests") | [<img src="https://avatars2.githubusercontent.com/u/132233?v=4" width="100px;" alt="John Gozde"/><br /><sub><b>John Gozde</b></sub>](https://github.com/jgoz)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Ajgoz "Bug reports") [💻](https://github.com/testing-library/jest-dom/commits?author=jgoz "Code") | [<img src="https://avatars2.githubusercontent.com/u/7830590?v=4" width="100px;" alt="Iwona"/><br /><sub><b>Iwona</b></sub>](https://github.com/callada)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=callada "Code") [📖](https://github.com/testing-library/jest-dom/commits?author=callada "Documentation") [⚠️](https://github.com/testing-library/jest-dom/commits?author=callada "Tests") | [<img src="https://avatars0.githubusercontent.com/u/840609?v=4" width="100px;" alt="Lewis"/><br /><sub><b>Lewis</b></sub>](https://github.com/6ewis)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=6ewis "Code") | [<img src="https://avatars3.githubusercontent.com/u/2339362?v=4" width="100px;" alt="Leandro Lourenci"/><br /><sub><b>Leandro Lourenci</b></sub>](https://blog.lourenci.com/)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Alourenci "Bug reports") [📖](https://github.com/testing-library/jest-dom/commits?author=lourenci "Documentation") [💻](https://github.com/testing-library/jest-dom/commits?author=lourenci "Code") [⚠️](https://github.com/testing-library/jest-dom/commits?author=lourenci "Tests") | [<img src="https://avatars1.githubusercontent.com/u/626420?v=4" width="100px;" alt="Shukhrat Mukimov"/><br /><sub><b>Shukhrat Mukimov</b></sub>](https://github.com/mufasa71)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Amufasa71 "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/1481264?v=4" width="100px;" alt="Roman Usherenko"/><br /><sub><b>Roman Usherenko</b></sub>](https://github.com/dreyks)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=dreyks "Code") [⚠️](https://github.com/testing-library/jest-dom/commits?author=dreyks "Tests") |
915-
| [<img src="https://avatars1.githubusercontent.com/u/648?v=4" width="100px;" alt="Joe Hsu"/><br /><sub><b>Joe Hsu</b></sub>](http://josephhsu.com)<br />[📖](https://github.com/testing-library/jest-dom/commits?author=jhsu "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/3068563?v=4" width="100px;" alt="Haz"/><br /><sub><b>Haz</b></sub>](https://twitter.com/diegohaz)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Adiegohaz "Bug reports") [💻](https://github.com/testing-library/jest-dom/commits?author=diegohaz "Code") | [<img src="https://avatars3.githubusercontent.com/u/463904?v=4" width="100px;" alt="Revath S Kumar"/><br /><sub><b>Revath S Kumar</b></sub>](https://blog.revathskumar.com)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=revathskumar "Code") | [<img src="https://avatars0.githubusercontent.com/u/4989733?v=4" width="100px;" alt="hiwelo."/><br /><sub><b>hiwelo.</b></sub>](https://raccoon.studio)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=hiwelo "Code") [🤔](#ideas-hiwelo "Ideas, Planning, & Feedback") [⚠️](https://github.com/testing-library/jest-dom/commits?author=hiwelo "Tests") |
973+
| [<img src="https://avatars1.githubusercontent.com/u/648?v=4" width="100px;" alt="Joe Hsu"/><br /><sub><b>Joe Hsu</b></sub>](http://josephhsu.com)<br />[📖](https://github.com/testing-library/jest-dom/commits?author=jhsu "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/3068563?v=4" width="100px;" alt="Haz"/><br /><sub><b>Haz</b></sub>](https://twitter.com/diegohaz)<br />[🐛](https://github.com/testing-library/jest-dom/issues?q=author%3Adiegohaz "Bug reports") [💻](https://github.com/testing-library/jest-dom/commits?author=diegohaz "Code") | [<img src="https://avatars3.githubusercontent.com/u/463904?v=4" width="100px;" alt="Revath S Kumar"/><br /><sub><b>Revath S Kumar</b></sub>](https://blog.revathskumar.com)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=revathskumar "Code") | [<img src="https://avatars0.githubusercontent.com/u/4989733?v=4" width="100px;" alt="hiwelo."/><br /><sub><b>hiwelo.</b></sub>](https://raccoon.studio)<br />[💻](https://github.com/testing-library/jest-dom/commits?author=hiwelo "Code") [🤔](#ideas-hiwelo "Ideas, Planning, & Feedback") [⚠️](https://github.com/testing-library/jest-dom/commits?author=hiwelo "Tests") | [<img src="https://avatars3.githubusercontent.com/u/1201711?v=4" width="100px;" alt="Łukasz Fiszer"/><br /><sub><b>Łukasz Fiszer</b></sub>](https://github.com/lukaszfiszer)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=lukaszfiszer "Code") | [<img src="https://avatars3.githubusercontent.com/u/1201711?v=4" width="100px;" alt="Łukasz Fiszer"/><br /><sub><b>Łukasz Fiszer</b></sub>](https://github.com/lukaszfiszer)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=lukaszfiszer "Code") |
916974
917975
<!-- ALL-CONTRIBUTORS-LIST:END -->
918976

extend-expect.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ declare namespace jest {
2323
text: string | RegExp,
2424
options?: {normalizeWhitespace: boolean},
2525
): R
26+
toHaveValue(value?: string | string[] | number): R
2627
}
2728
}

src/__tests__/to-have-value.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {render} from './helpers/test-utils'
2+
3+
describe('.toHaveValue', () => {
4+
test('handles value of text input', () => {
5+
const {queryByTestId} = render(`
6+
<input type="text" value="foo" data-testid="value" />
7+
<input type="text" value="" data-testid="empty" />
8+
<input type="text" data-testid="without" />
9+
`)
10+
11+
expect(queryByTestId('value')).toHaveValue('foo')
12+
expect(queryByTestId('value')).toHaveValue()
13+
expect(queryByTestId('value')).not.toHaveValue('bar')
14+
expect(queryByTestId('value')).not.toHaveValue('')
15+
16+
expect(queryByTestId('empty')).toHaveValue('')
17+
expect(queryByTestId('empty')).not.toHaveValue()
18+
expect(queryByTestId('empty')).not.toHaveValue('foo')
19+
20+
expect(queryByTestId('without')).toHaveValue('')
21+
expect(queryByTestId('without')).not.toHaveValue()
22+
expect(queryByTestId('without')).not.toHaveValue('foo')
23+
queryByTestId('without').value = 'bar'
24+
expect(queryByTestId('without')).toHaveValue('bar')
25+
})
26+
27+
test('handles value of number input', () => {
28+
const {queryByTestId} = render(`
29+
<input type="number" value="5" data-testid="number" />
30+
<input type="number" value="" data-testid="empty" />
31+
<input type="number" data-testid="without" />
32+
`)
33+
34+
expect(queryByTestId('number')).toHaveValue(5)
35+
expect(queryByTestId('number')).toHaveValue()
36+
expect(queryByTestId('number')).not.toHaveValue(4)
37+
expect(queryByTestId('number')).not.toHaveValue('5')
38+
39+
expect(queryByTestId('empty')).toHaveValue(null)
40+
expect(queryByTestId('empty')).not.toHaveValue()
41+
expect(queryByTestId('empty')).not.toHaveValue('5')
42+
43+
expect(queryByTestId('without')).toHaveValue(null)
44+
expect(queryByTestId('without')).not.toHaveValue()
45+
expect(queryByTestId('without')).not.toHaveValue('10')
46+
queryByTestId('without').value = 10
47+
expect(queryByTestId('without')).toHaveValue(10)
48+
})
49+
50+
test('handles value of select element', () => {
51+
const {queryByTestId} = render(`
52+
<select data-testid="single">
53+
<option value="first">First Value</option>
54+
<option value="second" selected>Second Value</option>
55+
<option value="third">Third Value</option>
56+
</select>
57+
58+
<select data-testid="multiple" multiple>
59+
<option value="first">First Value</option>
60+
<option value="second" selected>Second Value</option>
61+
<option value="third" selected>Third Value</option>
62+
</select>
63+
64+
<select data-testid="not-selected" >
65+
<option value="" disabled selected>- Select some value - </option>
66+
<option value="first">First Value</option>
67+
<option value="second">Second Value</option>
68+
<option value="third">Third Value</option>
69+
</select>
70+
`)
71+
72+
expect(queryByTestId('single')).toHaveValue('second')
73+
expect(queryByTestId('single')).toHaveValue()
74+
75+
expect(queryByTestId('multiple')).toHaveValue(['second', 'third'])
76+
expect(queryByTestId('multiple')).toHaveValue()
77+
78+
expect(queryByTestId('not-selected')).not.toHaveValue()
79+
expect(queryByTestId('not-selected')).toHaveValue('')
80+
81+
queryByTestId('single').children[0].setAttribute('selected', true)
82+
expect(queryByTestId('single')).toHaveValue('first')
83+
})
84+
85+
test('handles value of textarea element', () => {
86+
const {queryByTestId} = render(`
87+
<textarea data-testid="textarea">text value</textarea>
88+
`)
89+
expect(queryByTestId('textarea')).toHaveValue('text value')
90+
})
91+
92+
test('throws when passed checkbox or radio', () => {
93+
const {queryByTestId} = render(`
94+
<input data-testid="checkbox" type="checkbox" name="checkbox" value="val" checked />
95+
<input data-testid="radio" type="radio" name="radio" value="val" checked />
96+
`)
97+
98+
expect(() => {
99+
expect(queryByTestId('checkbox')).toHaveValue('')
100+
}).toThrow()
101+
102+
expect(() => {
103+
expect(queryByTestId('radio')).toHaveValue('')
104+
}).toThrow()
105+
})
106+
})

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {toBeVisible} from './to-be-visible'
1313
import {toBeDisabled, toBeEnabled} from './to-be-disabled'
1414
import {toBeRequired} from './to-be-required'
1515
import {toBeInvalid, toBeValid} from './to-be-invalid'
16+
import {toHaveValue} from './to-have-value'
1617

1718
export {
1819
toBeInTheDOM,
@@ -32,4 +33,5 @@ export {
3233
toBeRequired,
3334
toBeInvalid,
3435
toBeValid,
36+
toHaveValue,
3537
}

src/to-have-form-values.js

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,13 @@
11
import {matcherHint} from 'jest-matcher-utils'
22
import jestDiff from 'jest-diff'
3-
import isEqual from 'lodash/isEqual'
43
import isEqualWith from 'lodash/isEqualWith'
54
import uniq from 'lodash/uniq'
6-
import {checkHtmlElement} from './utils'
75
import escape from 'css.escape'
8-
9-
function compareArraysAsSet(a, b) {
10-
if (Array.isArray(a) && Array.isArray(b)) {
11-
return isEqual(new Set(a), new Set(b))
12-
}
13-
return undefined
14-
}
15-
16-
function getSelectValue({multiple, selectedOptions}) {
17-
if (multiple) {
18-
return [...selectedOptions].map(opt => opt.value)
19-
}
20-
/* istanbul ignore if */
21-
if (selectedOptions.length === 0) {
22-
return undefined // Couldn't make this happen, but just in case
23-
}
24-
return selectedOptions[0].value
25-
}
26-
27-
function getInputValue(inputElement) {
28-
switch (inputElement.type) {
29-
case 'number':
30-
return inputElement.value === '' ? null : Number(inputElement.value)
31-
case 'checkbox':
32-
return inputElement.checked
33-
default:
34-
return inputElement.value
35-
}
36-
}
37-
38-
function getSingleElementValue(element) {
39-
/* istanbul ignore if */
40-
if (!element) {
41-
return undefined
42-
}
43-
switch (element.tagName.toLowerCase()) {
44-
case 'input':
45-
return getInputValue(element)
46-
case 'select':
47-
return getSelectValue(element)
48-
default:
49-
return element.value
50-
}
51-
}
6+
import {
7+
checkHtmlElement,
8+
compareArraysAsSet,
9+
getSingleElementValue,
10+
} from './utils'
5211

5312
// Returns the combined value of several elements that have the same name
5413
// e.g. radio buttons or groups of checkboxes

src/to-have-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+
import isEqualWith from 'lodash/isEqualWith'
3+
import {
4+
checkHtmlElement,
5+
compareArraysAsSet,
6+
getMessage,
7+
getSingleElementValue,
8+
} from './utils'
9+
10+
export function toHaveValue(htmlElement, expectedValue) {
11+
checkHtmlElement(htmlElement, toHaveValue, this)
12+
13+
if (
14+
htmlElement.tagName.toLowerCase() === 'input' &&
15+
['checkbox', 'radio'].includes(htmlElement.type)
16+
) {
17+
throw new Error(
18+
'input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toHaveFormValues() instead',
19+
)
20+
}
21+
22+
const receivedValue = getSingleElementValue(htmlElement)
23+
const expectsValue = expectedValue !== undefined
24+
return {
25+
pass: expectsValue
26+
? isEqualWith(receivedValue, expectedValue, compareArraysAsSet)
27+
: Boolean(receivedValue),
28+
message: () => {
29+
const to = this.isNot ? 'not to' : 'to'
30+
const matcher = matcherHint(
31+
`${this.isNot ? '.not' : ''}.toHaveValue`,
32+
'element',
33+
expectedValue,
34+
)
35+
return getMessage(
36+
matcher,
37+
`Expected the element ${to} have value`,
38+
expectsValue ? expectedValue : '(any)',
39+
'Received',
40+
receivedValue,
41+
)
42+
},
43+
}
44+
}

0 commit comments

Comments
 (0)