Skip to content

Commit 18104fe

Browse files
antsmartianKent C. Dodds
authored and
Kent C. Dodds
committed
feat(matchers): add custom jest matchers (#13)
* Improving API's for testing. * Improving Apis * Adding all contributions * Fixing wrong url * Fixing review comments & making colorful assertions :) * Removing unwanted changes * Fixing review comments * removing unwanted comments * Adding test cases for the coverage * removing commented code and making few changes to the contribution file * Updating the readme * Making line break changes * Update README.md
1 parent 50aa1bc commit 18104fe

10 files changed

+168
-15
lines changed

Diff for: .all-contributorsrc

+11
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@
7777
"platform"
7878
]
7979
},
80+
{
81+
"login": "antoaravinth",
82+
"name": "Anto Aravinth",
83+
"avatar_url": "https://avatars1.githubusercontent.com/u/1241511?s=460&v=4",
84+
"profile": "https://github.com/antoaravinth",
85+
"contributions": [
86+
"code",
87+
"test",
88+
"doc"
89+
]
90+
},
8091
{
8192
"login": "JonahMoses",
8293
"name": "Jonah Moses",

Diff for: README.md

+41-2
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-8-orange.svg?style=flat-square)](#contributors)
19+
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors)
2020
[![PRs Welcome][prs-badge]][prs]
2121
[![Code of Conduct][coc-badge]][coc]
2222

@@ -79,6 +79,7 @@ facilitate testing implementation details). Read more about this in
7979
* [`Simulate`](#simulate)
8080
* [`flushPromises`](#flushpromises)
8181
* [`render`](#render)
82+
* [Custom Jest Matchers](#custom-jest-matchers)
8283
* [`TextMatch`](#textmatch)
8384
* [`query` APIs](#query-apis)
8485
* [Examples](#examples)
@@ -250,6 +251,44 @@ const usernameInputElement = getByTestId('username-input')
250251
> Learn more about `data-testid`s from the blog post
251252
> ["Making your UI tests resilient to change"][data-testid-blog-post]
252253
254+
## Custom Jest Matchers
255+
256+
There are two simple API which extend the `expect` API of jest for making assertions easier.
257+
258+
### `toBeInTheDOM`
259+
260+
This allows you to assert whether an element present in the DOM or not.
261+
262+
```javascript
263+
// add the custom expect matchers
264+
import 'react-testing-library/extend-expect'
265+
266+
// ...
267+
const {queryByTestId} = render(<span data-testid="count-value">2</span>)
268+
expect(queryByTestId('count-value')).toBeInTheDOM()
269+
expect(queryByTestId('count-value1')).not.toBeInTheDOM()
270+
// ...
271+
```
272+
273+
> Note: when using `toBeInTheDOM`, make sure you use a query function
274+
> (like `queryByTestId`) rather than a get function (like `getByTestId`).
275+
> Otherwise the `get*` function could throw an error before your assertion.
276+
277+
### `toHaveTextContent`
278+
279+
This API allows you to check whether the given element has a text content or not.
280+
281+
```javascript
282+
// add the custom expect matchers
283+
import 'react-testing-library/extend-expect'
284+
285+
// ...
286+
const {getByTestId} = render(<span data-testid="count-value">2</span>)
287+
expect(getByTestId('count-value')).toHaveTextContent('2')
288+
expect(getByTestId('count-value')).not.toHaveTextContent('21')
289+
// ...
290+
```
291+
253292
## `TextMatch`
254293

255294
Several APIs accept a `TextMatch` which can be a `string`, `regex` or a
@@ -595,7 +634,7 @@ Thanks goes to these people ([emoji key][emojis]):
595634
<!-- prettier-ignore -->
596635
| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub><b>Kent C. Dodds</b></sub>](https://kentcdodds.com)<br />[💻](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Documentation") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Tests") | [<img src="https://avatars1.githubusercontent.com/u/2430381?v=4" width="100px;"/><br /><sub><b>Ryan Castner</b></sub>](http://audiolion.github.io)<br />[📖](https://github.com/kentcdodds/react-testing-library/commits?author=audiolion "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/8008023?v=4" width="100px;"/><br /><sub><b>Daniel Sandiego</b></sub>](https://www.dnlsandiego.com)<br />[💻](https://github.com/kentcdodds/react-testing-library/commits?author=dnlsandiego "Code") | [<img src="https://avatars2.githubusercontent.com/u/12592677?v=4" width="100px;"/><br /><sub><b>Paweł Mikołajczyk</b></sub>](https://github.com/Miklet)<br />[💻](https://github.com/kentcdodds/react-testing-library/commits?author=Miklet "Code") | [<img src="https://avatars3.githubusercontent.com/u/464978?v=4" width="100px;"/><br /><sub><b>Alejandro Ñáñez Ortiz</b></sub>](http://co.linkedin.com/in/alejandronanez/)<br />[📖](https://github.com/kentcdodds/react-testing-library/commits?author=alejandronanez "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1402095?v=4" width="100px;"/><br /><sub><b>Matt Parrish</b></sub>](https://github.com/pbomb)<br />[🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Apbomb "Bug reports") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Documentation") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Tests") | [<img src="https://avatars1.githubusercontent.com/u/1288694?v=4" width="100px;"/><br /><sub><b>Justin Hall</b></sub>](https://github.com/wKovacs64)<br />[📦](#platform-wKovacs64 "Packaging/porting to new platform") |
597636
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
598-
| [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[📖](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") |
637+
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[💻](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[📖](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") |
599638

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

Diff for: extend-expect.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('./dist/extend-expect')

Diff for: package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"eslintConfig": {
4646
"extends": "./node_modules/kcd-scripts/eslint.js",
4747
"rules": {
48-
"react/prop-types": "off"
48+
"react/prop-types": "off",
49+
"import/no-unassigned-import": "off"
4950
}
5051
},
5152
"eslintIgnore": [
@@ -61,4 +62,4 @@
6162
"url": "https://github.com/kentcdodds/react-testing-library/issues"
6263
},
6364
"homepage": "https://github.com/kentcdodds/react-testing-library#readme"
64-
}
65+
}

Diff for: src/__tests__/element-queries.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import {render} from '../'
3+
import '../extend-expect'
34

45
test('query can return null', () => {
56
const {
@@ -66,4 +67,17 @@ test('totally empty label', () => {
6667
expect(() => getByLabelText('')).toThrowErrorMatchingSnapshot()
6768
})
6869

70+
test('using jest helpers to assert element states', () => {
71+
const {queryByTestId} = render(<span data-testid="count-value">2</span>)
72+
73+
// other ways to assert your test cases, but you don't need all of them.
74+
expect(queryByTestId('count-value')).toBeInTheDOM()
75+
expect(queryByTestId('count-value1')).not.toBeInTheDOM()
76+
expect(queryByTestId('count-value')).toHaveTextContent('2')
77+
expect(queryByTestId('count-value')).not.toHaveTextContent('21')
78+
expect(() =>
79+
expect(queryByTestId('count-value2')).toHaveTextContent('2'),
80+
).toThrowError()
81+
})
82+
6983
/* eslint jsx-a11y/label-has-for:0 */

Diff for: src/__tests__/fetch.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
3535
}),
3636
)
3737
const url = '/greeting'
38-
const {getByText, container} = render(<Fetch url={url} />)
38+
const {container, getByText} = render(<Fetch url={url} />)
3939

4040
// Act
4141
Simulate.click(getByText('Fetch'))

Diff for: src/extend-expect.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import expect from 'expect' //eslint-disable-line import/no-extraneous-dependencies
2+
import extensions from './jest-extensions'
3+
4+
const {toBeInTheDOM, toHaveTextContent} = extensions
5+
expect.extend({toBeInTheDOM, toHaveTextContent})
6+
7+
export default expect

Diff for: src/jest-extensions.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {matcherHint, printReceived, printExpected} from 'jest-matcher-utils' //eslint-disable-line import/no-extraneous-dependencies
2+
import {matches} from './utils'
3+
4+
function getDisplayName(subject) {
5+
if (subject && subject.constructor) {
6+
return subject.constructor.name
7+
} else {
8+
return typeof subject
9+
}
10+
}
11+
12+
const assertMessage = (assertionName, message, received, expected) =>
13+
`${matcherHint(`${assertionName}`, 'received', '')} \n${message}: ` +
14+
`${printExpected(expected)} \nReceived: ${printReceived(received)}`
15+
16+
const extensions = {
17+
toBeInTheDOM(received) {
18+
getDisplayName(received)
19+
if (received) {
20+
return {
21+
message:
22+
`${matcherHint(
23+
'.not.toBeInTheDOM',
24+
'received',
25+
'',
26+
)} Expected the element not to be present` +
27+
`\nReceived : ${printReceived(received)}`,
28+
pass: true,
29+
}
30+
} else {
31+
return {
32+
message:
33+
`${matcherHint(
34+
'.toBeInTheDOM',
35+
'received',
36+
'',
37+
)} Expected the element to be present` +
38+
`\nReceived : ${printReceived(received)}`,
39+
pass: false,
40+
}
41+
}
42+
},
43+
44+
toHaveTextContent(htmlElement, checkWith) {
45+
if (!(htmlElement instanceof HTMLElement))
46+
throw new Error(
47+
`The given subject is a ${getDisplayName(
48+
htmlElement,
49+
)}, not an HTMLElement`,
50+
)
51+
52+
const textContent = htmlElement.textContent
53+
const pass = matches(textContent, htmlElement, checkWith)
54+
if (pass) {
55+
return {
56+
message: assertMessage(
57+
'.not.toHaveTextContent',
58+
'Expected value not equals to',
59+
htmlElement,
60+
checkWith,
61+
),
62+
pass: true,
63+
}
64+
} else {
65+
return {
66+
message: assertMessage(
67+
'.toHaveTextContent',
68+
'Expected value equals to',
69+
htmlElement,
70+
checkWith,
71+
),
72+
pass: false,
73+
}
74+
}
75+
},
76+
}
77+
78+
export default extensions

Diff for: src/queries.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {matches} from './utils'
2+
13
// Here are the queries for the library.
24
// The queries here should only be things that are accessible to both users who are using a screen reader
35
// and those who are not using a screen reader (with the exception of the data-testid attribute query).
@@ -69,16 +71,6 @@ function getText(node) {
6971
.join(' ')
7072
}
7173

72-
function matches(textToMatch, node, matcher) {
73-
if (typeof matcher === 'string') {
74-
return textToMatch.toLowerCase().includes(matcher.toLowerCase())
75-
} else if (typeof matcher === 'function') {
76-
return matcher(textToMatch, node)
77-
} else {
78-
return matcher.test(textToMatch)
79-
}
80-
}
81-
8274
// getters
8375
// the reason we're not dynamically generating these functions that look so similar:
8476
// 1. The error messages are specific to each one and depend on arguments

Diff for: src/utils.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//eslint-disable-next-line import/prefer-default-export
2+
export function matches(textToMatch, node, matcher) {
3+
if (typeof matcher === 'string') {
4+
return textToMatch.toLowerCase().includes(matcher.toLowerCase())
5+
} else if (typeof matcher === 'function') {
6+
return matcher(textToMatch, node)
7+
} else {
8+
return matcher.test(textToMatch)
9+
}
10+
}

0 commit comments

Comments
 (0)