Skip to content

Commit e832958

Browse files
authored
feat: toBeVisible custom matcher (#13)
1 parent 6c50a1b commit e832958

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ to maintain.
4747
* [`toHaveAttribute`](#tohaveattribute)
4848
* [`toHaveClass`](#tohaveclass)
4949
* [`toHaveStyle`](#tohavestyle)
50+
* [`toBeVisible`](#tobevisible)
5051
* [Inspiration](#inspiration)
5152
* [Other Solutions](#other-solutions)
5253
* [Guiding Principles](#guiding-principles)
@@ -194,6 +195,35 @@ This also works with rules that are applied to the element via a class name for
194195
which some rules are defined in a stylesheet currently active in the document.
195196
The usual rules of css precedence apply.
196197

198+
### `toBeVisible`
199+
200+
This allows you to check if an element is currently visible to the user.
201+
202+
An element is visible if **all** the following conditions are met:
203+
204+
* it does not have its css property `display` set to `none`
205+
* it does not have its css property `visibility` set to either `hidden` or
206+
`collapse`
207+
* it does not have its css property `opacity` set to `0`
208+
* its parent element is also visible (and so on up to the top of the DOM tree)
209+
210+
```javascript
211+
// add the custom expect matchers once
212+
import 'jest-dom/extend-expect'
213+
214+
// ...
215+
// <header>
216+
// <h1 style="display: none">Page title</h1>
217+
// </header>
218+
// <section>
219+
// <p style="visibility: hidden">Hello <strong>World</strong></h1>
220+
// </section>
221+
expect(container.querySelector('header')).toBeVisible()
222+
expect(container.querySelector('h1')).not.toBeVisible()
223+
expect(container.querySelector('strong')).not.toBeVisible()
224+
// ...
225+
```
226+
197227
## Inspiration
198228

199229
This whole library was extracted out of Kent C. Dodds' [dom-testing-library][],

src/__tests__/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,35 @@ test('.toHaveStyle', () => {
167167
document.body.removeChild(style)
168168
document.body.removeChild(container)
169169
})
170+
171+
test('.toBeVisible', () => {
172+
const {container} = render(`
173+
<div>
174+
<header>
175+
<h1 style="display: none">Main title</h1>
176+
<h2 style="visibility: hidden">Secondary title</h2>
177+
<h3 style="visibility: collapse">Secondary title</h3>
178+
<h4 style="opacity: 0">Secondary title</h4>
179+
<h5 style="opacity: 0.1">Secondary title</h5>
180+
</header>
181+
<section style="display: block; visibility: hidden">
182+
<p>Hello <strong>World</strong></p>
183+
</section>
184+
</div>
185+
`)
186+
187+
expect(container.querySelector('header')).toBeVisible()
188+
expect(container.querySelector('h1')).not.toBeVisible()
189+
expect(container.querySelector('h2')).not.toBeVisible()
190+
expect(container.querySelector('h3')).not.toBeVisible()
191+
expect(container.querySelector('h4')).not.toBeVisible()
192+
expect(container.querySelector('h5')).toBeVisible()
193+
expect(container.querySelector('strong')).not.toBeVisible()
194+
195+
expect(() =>
196+
expect(container.querySelector('header')).not.toBeVisible(),
197+
).toThrowError()
198+
expect(() =>
199+
expect(container.querySelector('p')).toBeVisible(),
200+
).toThrowError()
201+
})

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {toHaveTextContent} from './to-have-text-content'
33
import {toHaveAttribute} from './to-have-attribute'
44
import {toHaveClass} from './to-have-class'
55
import {toHaveStyle} from './to-have-style'
6+
import {toBeVisible} from './to-be-visible'
67

78
export {
89
toBeInTheDOM,
910
toHaveTextContent,
1011
toHaveAttribute,
1112
toHaveClass,
1213
toHaveStyle,
14+
toBeVisible,
1315
}

src/to-be-visible.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {matcherHint} from 'jest-matcher-utils'
2+
import {checkHtmlElement, getMessage} from './utils'
3+
4+
function isStyleVisible(element) {
5+
const {display, visibility, opacity} = getComputedStyle(element)
6+
return (
7+
display !== 'none' &&
8+
visibility !== 'hidden' &&
9+
visibility !== 'collapse' &&
10+
opacity !== '0' &&
11+
opacity !== 0
12+
)
13+
}
14+
15+
function isElementVisible(element) {
16+
return (
17+
isStyleVisible(element) &&
18+
(!element.parentElement || isElementVisible(element.parentElement))
19+
)
20+
}
21+
22+
export function toBeVisible(element) {
23+
checkHtmlElement(element)
24+
const isVisible = isElementVisible(element)
25+
return {
26+
pass: isVisible,
27+
message: () => {
28+
const to = this.isNot ? 'not to' : 'to'
29+
const is = isVisible ? 'is' : 'is not'
30+
return getMessage(
31+
matcherHint(`${this.isNot ? '.not' : ''}.toBeVisible`, 'element', ''),
32+
'Expected',
33+
`element ${to} be visible`,
34+
'Received',
35+
`element ${is} visible`,
36+
)
37+
},
38+
}
39+
}

0 commit comments

Comments
 (0)