Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ to maintain.
* [`toHaveAttribute`](#tohaveattribute)
* [`toHaveClass`](#tohaveclass)
* [`toHaveStyle`](#tohavestyle)
* [`toBeVisible`](#tobevisible)
* [Inspiration](#inspiration)
* [Other Solutions](#other-solutions)
* [Guiding Principles](#guiding-principles)
Expand Down Expand Up @@ -194,6 +195,35 @@ This also works with rules that are applied to the element via a class name for
which some rules are defined in a stylesheet currently active in the document.
The usual rules of css precedence apply.

### `toBeVisible`

This allows you to check if an element is currently visible to the user.

An element is visible if **all** the following conditions are met:

* it does not have its css property `display` set to `none`
* it does not have its css property `visibility` set to either `hidden` or
`collapse`
* it does not have its css property `opacity` set to `0`
* its parent element is also visible (and so on up to the top of the DOM tree)

```javascript
// add the custom expect matchers once
import 'jest-dom/extend-expect'

// ...
// <header>
// <h1 style="display: none">Page title</h1>
// </header>
// <section>
// <p style="visibility: hidden">Hello <strong>World</strong></h1>
// </section>
expect(container.querySelector('header')).toBeVisible()
expect(container.querySelector('h1')).not.toBeVisible()
expect(container.querySelector('strong')).not.toBeVisible()
// ...
```

## Inspiration

This whole library was extracted out of Kent C. Dodds' [dom-testing-library][],
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,35 @@ test('.toHaveStyle', () => {
document.body.removeChild(style)
document.body.removeChild(container)
})

test('.toBeVisible', () => {
const {container} = render(`
<div>
<header>
<h1 style="display: none">Main title</h1>
<h2 style="visibility: hidden">Secondary title</h2>
<h3 style="visibility: collapse">Secondary title</h3>
<h4 style="opacity: 0">Secondary title</h4>
<h5 style="opacity: 0.1">Secondary title</h5>
</header>
<section style="display: block; visibility: hidden">
<p>Hello <strong>World</strong></p>
</section>
</div>
`)

expect(container.querySelector('header')).toBeVisible()
expect(container.querySelector('h1')).not.toBeVisible()
expect(container.querySelector('h2')).not.toBeVisible()
expect(container.querySelector('h3')).not.toBeVisible()
expect(container.querySelector('h4')).not.toBeVisible()
expect(container.querySelector('h5')).toBeVisible()
expect(container.querySelector('strong')).not.toBeVisible()

expect(() =>
expect(container.querySelector('header')).not.toBeVisible(),
).toThrowError()
expect(() =>
expect(container.querySelector('p')).toBeVisible(),
).toThrowError()
})
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {toHaveTextContent} from './to-have-text-content'
import {toHaveAttribute} from './to-have-attribute'
import {toHaveClass} from './to-have-class'
import {toHaveStyle} from './to-have-style'
import {toBeVisible} from './to-be-visible'

export {
toBeInTheDOM,
toHaveTextContent,
toHaveAttribute,
toHaveClass,
toHaveStyle,
toBeVisible,
}
39 changes: 39 additions & 0 deletions src/to-be-visible.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {matcherHint} from 'jest-matcher-utils'
import {checkHtmlElement, getMessage} from './utils'

function isStyleVisible(element) {
const {display, visibility, opacity} = getComputedStyle(element)
return (
display !== 'none' &&
visibility !== 'hidden' &&
visibility !== 'collapse' &&
opacity !== '0' &&
opacity !== 0
)
}

function isElementVisible(element) {
return (
isStyleVisible(element) &&
(!element.parentElement || isElementVisible(element.parentElement))
)
}

export function toBeVisible(element) {
checkHtmlElement(element)
const isVisible = isElementVisible(element)
return {
pass: isVisible,
message: () => {
const to = this.isNot ? 'not to' : 'to'
const is = isVisible ? 'is' : 'is not'
return getMessage(
matcherHint(`${this.isNot ? '.not' : ''}.toBeVisible`, 'element', ''),
'Expected',
`element ${to} be visible`,
'Received',
`element ${is} visible`,
)
},
}
}