Skip to content

Commit 1df7560

Browse files
6ewisgnapse
authored andcommitted
feat: add new matcher toContainHTML (#42)
1 parent c8d29c6 commit 1df7560

File tree

6 files changed

+147
-3
lines changed

6 files changed

+147
-3
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@
179179
"doc",
180180
"test"
181181
]
182+
},
183+
{
184+
"login": "6ewis",
185+
"name": "Lewis",
186+
"avatar_url": "https://avatars0.githubusercontent.com/u/840609?v=4",
187+
"profile": "https://github.com/6ewis",
188+
"contributions": [
189+
"code"
190+
]
182191
}
183192
]
184193
}

README.md

Lines changed: 29 additions & 3 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-17-orange.svg?style=flat-square)](#contributors)
19+
[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors)
2020
[![PRs Welcome][prs-badge]][prs]
2121
[![Code of Conduct][coc-badge]][coc]
2222

@@ -48,6 +48,7 @@ to maintain.
4848
- [`toBeEmpty`](#tobeempty)
4949
- [`toBeInTheDocument`](#tobeinthedocument)
5050
- [`toContainElement`](#tocontainelement)
51+
- [`toContainHTML`](#tocontainhtml)
5152
- [`toHaveTextContent`](#tohavetextcontent)
5253
- [`toHaveAttribute`](#tohaveattribute)
5354
- [`toHaveClass`](#tohaveclass)
@@ -167,6 +168,31 @@ expect(descendant).not.toContainElement(ancestor)
167168
// ...
168169
```
169170

171+
### `toContainHTML`
172+
173+
```typescript
174+
toContainHTML(htmlText: string)
175+
```
176+
177+
Assert whether a string representing a HTML element is contained in another element:
178+
179+
```javascript
180+
// add the custom expect matchers once
181+
import 'jest-dom/extend-expect'
182+
183+
// ...
184+
// <span data-testid="parent"><span data-testid="child"></span></span>
185+
const parent = queryByTestId('parent')
186+
expect(parentElement).toContainHTML('<span data-testid="child"></span>')
187+
// ...
188+
```
189+
190+
> Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised.
191+
>
192+
> It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended.
193+
>
194+
> It should not be used to check DOM structure that you control. Please use [`toContainElement`](#tocontainelement) instead.
195+
170196
### `toHaveTextContent`
171197

172198
```typescript
@@ -401,7 +427,7 @@ value is indeed an `HTMLElement` you can always use some of
401427

402428
```js
403429
expect(document.querySelector('.ok-button')).toBeInstanceOf(HTMLElement)
404-
expect(document.querySelector('.cancel-button')).toBeThruthy();
430+
expect(document.querySelector('.cancel-button')).toBeThruthy()
405431
```
406432

407433
> Note: The differences between `toBeInTheDOM` and `toBeInTheDocument` are
@@ -448,7 +474,7 @@ Thanks goes to these people ([emoji key][emojis]):
448474
| [<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/gnapse/jest-dom/commits?author=kentcdodds "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=kentcdodds "Documentation") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/gnapse/jest-dom/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/gnapse/jest-dom/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/gnapse/jest-dom/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/gnapse/jest-dom/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/gnapse/jest-dom/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/gnapse/jest-dom/issues?q=author%3Apbomb "Bug reports") [💻](https://github.com/gnapse/jest-dom/commits?author=pbomb "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=pbomb "Documentation") [⚠️](https://github.com/gnapse/jest-dom/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") |
449475
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
450476
| [<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/gnapse/jest-dom/commits?author=antoaravinth "Code") [⚠️](https://github.com/gnapse/jest-dom/commits?author=antoaravinth "Tests") [📖](https://github.com/gnapse/jest-dom/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/gnapse/jest-dom/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;"/><br /><sub><b>Łukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=lgandecki "Code") [⚠️](https://github.com/gnapse/jest-dom/commits?author=lgandecki "Tests") [📖](https://github.com/gnapse/jest-dom/commits?author=lgandecki "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/498274?v=4" width="100px;"/><br /><sub><b>Ivan Babak</b></sub>](https://sompylasar.github.io)<br />[🐛](https://github.com/gnapse/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;"/><br /><sub><b>Jesse Day</b></sub>](https://github.com/jday3)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=jday3 "Code") | [<img src="https://avatars0.githubusercontent.com/u/15199?v=4" width="100px;"/><br /><sub><b>Ernesto García</b></sub>](http://gnapse.github.io)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=gnapse "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=gnapse "Documentation") [⚠️](https://github.com/gnapse/jest-dom/commits?author=gnapse "Tests") | [<img src="https://avatars0.githubusercontent.com/u/79312?v=4" width="100px;"/><br /><sub><b>Mark Volkmann</b></sub>](http://ociweb.com/mark/)<br />[🐛](https://github.com/gnapse/jest-dom/issues?q=author%3Amvolkmann "Bug reports") [💻](https://github.com/gnapse/jest-dom/commits?author=mvolkmann "Code") |
451-
| [<img src="https://avatars1.githubusercontent.com/u/1659099?v=4" width="100px;"/><br /><sub><b>smacpherson64</b></sub>](https://github.com/smacpherson64)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Documentation") [⚠️](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Tests") | [<img src="https://avatars2.githubusercontent.com/u/132233?v=4" width="100px;"/><br /><sub><b>John Gozde</b></sub>](https://github.com/jgoz)<br />[🐛](https://github.com/gnapse/jest-dom/issues?q=author%3Ajgoz "Bug reports") [💻](https://github.com/gnapse/jest-dom/commits?author=jgoz "Code") | [<img src="https://avatars2.githubusercontent.com/u/7830590?v=4" width="100px;"/><br /><sub><b>Iwona</b></sub>](https://github.com/callada)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=callada "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=callada "Documentation") [⚠️](https://github.com/gnapse/jest-dom/commits?author=callada "Tests") |
477+
| [<img src="https://avatars1.githubusercontent.com/u/1659099?v=4" width="100px;"/><br /><sub><b>smacpherson64</b></sub>](https://github.com/smacpherson64)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Documentation") [⚠️](https://github.com/gnapse/jest-dom/commits?author=smacpherson64 "Tests") | [<img src="https://avatars2.githubusercontent.com/u/132233?v=4" width="100px;"/><br /><sub><b>John Gozde</b></sub>](https://github.com/jgoz)<br />[🐛](https://github.com/gnapse/jest-dom/issues?q=author%3Ajgoz "Bug reports") [💻](https://github.com/gnapse/jest-dom/commits?author=jgoz "Code") | [<img src="https://avatars2.githubusercontent.com/u/7830590?v=4" width="100px;"/><br /><sub><b>Iwona</b></sub>](https://github.com/callada)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=callada "Code") [📖](https://github.com/gnapse/jest-dom/commits?author=callada "Documentation") [⚠️](https://github.com/gnapse/jest-dom/commits?author=callada "Tests") | [<img src="https://avatars0.githubusercontent.com/u/840609?v=4" width="100px;"/><br /><sub><b>Lewis</b></sub>](https://github.com/6ewis)<br />[💻](https://github.com/gnapse/jest-dom/commits?author=6ewis "Code") |
452478

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

extend-expect.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare namespace jest {
99
toBeEmpty(): R
1010
toBeDisabled(): R
1111
toContainElement(element: HTMLElement | SVGElement): R
12+
toContainHTML(htmlText: string): R
1213
toHaveAttribute(attr: string, value?: string): R
1314
toHaveClass(...classNames: string[]): R
1415
toHaveStyle(css: string): R

src/__tests__/to-contain-html.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {render} from './helpers/test-utils'
2+
3+
/* eslint-disable max-statements */
4+
test('.toContainHTML', () => {
5+
const {queryByTestId} = render(`
6+
<span data-testid="grandparent">
7+
<span data-testid="parent">
8+
<span data-testid="child"></span>
9+
</span>
10+
<svg data-testid="svg-element"></svg>
11+
</span>
12+
`)
13+
14+
const grandparent = queryByTestId('grandparent')
15+
const parent = queryByTestId('parent')
16+
const child = queryByTestId('child')
17+
const nonExistantElement = queryByTestId('not-exists')
18+
const fakeElement = {thisIsNot: 'an html element'}
19+
const stringChildElement = '<span data-testid="child"></span>'
20+
const incorrectStringHtml = '<span data-testid="child"></div>'
21+
const nonExistantString = '<span> Does not exists </span>'
22+
const svgElement = queryByTestId('svg-element')
23+
24+
expect(grandparent).toContainHTML(stringChildElement)
25+
expect(parent).toContainHTML(stringChildElement)
26+
expect(child).toContainHTML(stringChildElement)
27+
expect(grandparent).not.toContainHTML(nonExistantString)
28+
expect(parent).not.toContainHTML(nonExistantString)
29+
expect(child).not.toContainHTML(nonExistantString)
30+
expect(child).not.toContainHTML(nonExistantString)
31+
expect(grandparent).not.toContainHTML(incorrectStringHtml)
32+
expect(parent).not.toContainHTML(incorrectStringHtml)
33+
expect(child).not.toContainHTML(incorrectStringHtml)
34+
expect(child).not.toContainHTML(incorrectStringHtml)
35+
36+
// negative test cases wrapped in throwError assertions for coverage.
37+
expect(() =>
38+
expect(nonExistantElement).not.toContainHTML(stringChildElement),
39+
).toThrowError()
40+
expect(() =>
41+
expect(nonExistantElement).not.toContainHTML(nonExistantElement),
42+
).toThrowError()
43+
expect(() =>
44+
expect(stringChildElement).not.toContainHTML(fakeElement),
45+
).toThrowError()
46+
expect(() =>
47+
expect(svgElement).toContainHTML(stringChildElement),
48+
).toThrowError()
49+
expect(() =>
50+
expect(grandparent).not.toContainHTML(stringChildElement),
51+
).toThrowError()
52+
expect(() =>
53+
expect(parent).not.toContainHTML(stringChildElement),
54+
).toThrowError()
55+
expect(() =>
56+
expect(child).not.toContainHTML(stringChildElement),
57+
).toThrowError()
58+
expect(() =>
59+
expect(child).not.toContainHTML(stringChildElement),
60+
).toThrowError()
61+
expect(() => expect(child).toContainHTML(nonExistantString)).toThrowError()
62+
expect(() => expect(parent).toContainHTML(nonExistantString)).toThrowError()
63+
expect(() =>
64+
expect(grandparent).toContainHTML(nonExistantString),
65+
).toThrowError()
66+
expect(() => expect(child).toContainHTML(nonExistantElement)).toThrowError()
67+
expect(() => expect(parent).toContainHTML(nonExistantElement)).toThrowError()
68+
expect(() =>
69+
expect(grandparent).toContainHTML(nonExistantElement),
70+
).toThrowError()
71+
expect(() =>
72+
expect(nonExistantElement).toContainHTML(incorrectStringHtml),
73+
).toThrowError()
74+
expect(() =>
75+
expect(grandparent).toContainHTML(incorrectStringHtml),
76+
).toThrowError()
77+
expect(() => expect(child).toContainHTML(incorrectStringHtml)).toThrowError()
78+
expect(() => expect(parent).toContainHTML(incorrectStringHtml)).toThrowError()
79+
})

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {toBeInTheDOM} from './to-be-in-the-dom'
22
import {toBeInTheDocument} from './to-be-in-the-document'
33
import {toBeEmpty} from './to-be-empty'
44
import {toContainElement} from './to-contain-element'
5+
import {toContainHTML} from './to-contain-html'
56
import {toHaveTextContent} from './to-have-text-content'
67
import {toHaveAttribute} from './to-have-attribute'
78
import {toHaveClass} from './to-have-class'
@@ -15,6 +16,7 @@ export {
1516
toBeInTheDocument,
1617
toBeEmpty,
1718
toContainElement,
19+
toContainHTML,
1820
toHaveTextContent,
1921
toHaveAttribute,
2022
toHaveClass,

src/to-contain-html.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {matcherHint, printReceived} from 'jest-matcher-utils'
2+
import {checkHtmlElement} from './utils'
3+
4+
function checkHtmlText(htmlText, ...args) {
5+
const htmlElement =
6+
typeof htmlText === 'string'
7+
? new DOMParser().parseFromString(htmlText, 'text/html').body.firstChild
8+
: null
9+
checkHtmlElement(htmlElement, ...args)
10+
}
11+
12+
export function toContainHTML(container, htmlText) {
13+
checkHtmlElement(container, toContainHTML, this)
14+
checkHtmlText(htmlText, toContainHTML, this)
15+
16+
return {
17+
pass: container.outerHTML.includes(htmlText),
18+
message: () => {
19+
return [
20+
matcherHint(`${this.isNot ? '.not' : ''}.toContainHTML`, 'element', ''),
21+
'',
22+
'Received:',
23+
` ${printReceived(container.cloneNode(false))}`,
24+
].join('\n')
25+
},
26+
}
27+
}

0 commit comments

Comments
 (0)