diff --git a/README.md b/README.md
index bd3aa59e..411255a1 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ clear to read and to maintain.
+
- [Installation](#installation)
- [Usage](#usage)
- [Custom matchers](#custom-matchers)
@@ -378,6 +379,7 @@ An element is visible if **all** the following conditions are met:
- it does not have the
[`hidden`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden)
attribute
+- if ` ` it has the `open` attribute
#### Examples
diff --git a/src/__tests__/to-be-visible.js b/src/__tests__/to-be-visible.js
index d3f63ded..43d47284 100644
--- a/src/__tests__/to-be-visible.js
+++ b/src/__tests__/to-be-visible.js
@@ -1,35 +1,247 @@
import {render} from './helpers/test-utils'
-test('.toBeVisible', () => {
- const {container} = render(`
-
-
- Main title
- Secondary title
- Secondary title
- Secondary title
- Secondary title
-
-
Hidden button
-
-
- `)
-
- 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('button')).not.toBeVisible()
- expect(container.querySelector('strong')).not.toBeVisible()
-
- expect(() =>
- expect(container.querySelector('header')).not.toBeVisible(),
- ).toThrowError()
- expect(() =>
- expect(container.querySelector('p')).toBeVisible(),
- ).toThrowError()
+// eslint-disable-next-line max-lines-per-function
+describe('.toBeVisible', () => {
+ it('returns the visibility of an element', () => {
+ const {container} = render(`
+
+
+ Main title
+ Secondary title
+ Secondary title
+ Secondary title
+ Secondary title
+
+
Hidden button
+
+
+ `)
+
+ 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('button')).not.toBeVisible()
+ expect(container.querySelector('strong')).not.toBeVisible()
+
+ expect(() =>
+ expect(container.querySelector('header')).not.toBeVisible(),
+ ).toThrowError()
+ expect(() =>
+ expect(container.querySelector('p')).toBeVisible(),
+ ).toThrowError()
+ })
+
+ // eslint-disable-next-line max-lines-per-function
+ describe('with a element', () => {
+ let subject
+
+ afterEach(() => {
+ subject = undefined
+ })
+
+ describe('when the details is opened', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of visible
+ Visible details
+
+ `)
+ })
+
+ it('returns true to the details content', () => {
+ expect(subject.container.querySelector('div')).toBeVisible()
+ })
+
+ it('returns true to the most inner details content', () => {
+ expect(subject.container.querySelector('small')).toBeVisible()
+ })
+
+ it('returns true to the details summary', () => {
+ expect(subject.container.querySelector('summary')).toBeVisible()
+ })
+
+ describe('when the user clicks on the summary', () => {
+ beforeEach(() => subject.container.querySelector('summary').click())
+
+ it('returns false to the details content', () => {
+ expect(subject.container.querySelector('div')).not.toBeVisible()
+ })
+
+ it('returns true to the details summary', () => {
+ expect(subject.container.querySelector('summary')).toBeVisible()
+ })
+ })
+ })
+
+ describe('when the details is not opened', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of hidden
+ Hidden details
+
+ `)
+ })
+
+ it('returns false to the details content', () => {
+ expect(subject.container.querySelector('div')).not.toBeVisible()
+ })
+
+ it('returns true to the summary content', () => {
+ expect(subject.container.querySelector('summary')).toBeVisible()
+ })
+
+ describe('when the user clicks on the summary', () => {
+ beforeEach(() => subject.container.querySelector('summary').click())
+
+ it('returns true to the details content', () => {
+ expect(subject.container.querySelector('div')).toBeVisible()
+ })
+
+ it('returns true to the details summary', () => {
+ expect(subject.container.querySelector('summary')).toBeVisible()
+ })
+ })
+ })
+
+ describe('when the details is opened but it is hidden', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of visible
+ Visible details
+
+ `)
+ })
+
+ it('returns false to the details content', () => {
+ expect(subject.container.querySelector('div')).not.toBeVisible()
+ })
+
+ it('returns false to the details summary', () => {
+ expect(subject.container.querySelector('summary')).not.toBeVisible()
+ })
+ })
+
+ describe('with a nested element', () => {
+ describe('when the nested is opened', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of visible
+ Outer content
+
+ Title of nested details
+ Inner content
+
+
+ `)
+ })
+
+ it('returns true to the nested details content', () => {
+ expect(
+ subject.container.querySelector('details > details > div'),
+ ).toBeVisible()
+ })
+
+ it('returns true to the nested details summary', () => {
+ expect(
+ subject.container.querySelector('details > details > summary'),
+ ).toBeVisible()
+ })
+
+ it('returns true to the outer details content', () => {
+ expect(subject.container.querySelector('details > div')).toBeVisible()
+ })
+
+ it('returns true to the outer details summary', () => {
+ expect(
+ subject.container.querySelector('details > summary'),
+ ).toBeVisible()
+ })
+ })
+
+ describe('when the nested is not opened', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of visible
+ Outer content
+
+ Title of nested details
+ Inner content
+
+
+ `)
+ })
+
+ it('returns false to the nested details content', () => {
+ expect(
+ subject.container.querySelector('details > details > div'),
+ ).not.toBeVisible()
+ })
+
+ it('returns true to the nested details summary', () => {
+ expect(
+ subject.container.querySelector('details > details > summary'),
+ ).toBeVisible()
+ })
+
+ it('returns true to the outer details content', () => {
+ expect(subject.container.querySelector('details > div')).toBeVisible()
+ })
+
+ it('returns true to the outer details summary', () => {
+ expect(
+ subject.container.querySelector('details > summary'),
+ ).toBeVisible()
+ })
+ })
+
+ describe('when the outer is not opened and the nested one is opened', () => {
+ beforeEach(() => {
+ subject = render(`
+
+ Title of visible
+ Outer content
+
+ Title of nested details
+ Inner content
+
+
+ `)
+ })
+
+ it('returns false to the nested details content', () => {
+ expect(
+ subject.container.querySelector('details > details > div'),
+ ).not.toBeVisible()
+ })
+
+ it('returns false to the nested details summary', () => {
+ expect(
+ subject.container.querySelector('details > details > summary'),
+ ).not.toBeVisible()
+ })
+
+ it('returns false to the outer details content', () => {
+ expect(
+ subject.container.querySelector('details > div'),
+ ).not.toBeVisible()
+ })
+
+ it('returns true to the outer details summary', () => {
+ expect(
+ subject.container.querySelector('details > summary'),
+ ).toBeVisible()
+ })
+ })
+ })
+ })
})
diff --git a/src/to-be-visible.js b/src/to-be-visible.js
index 44a6c907..43c6362b 100644
--- a/src/to-be-visible.js
+++ b/src/to-be-visible.js
@@ -14,11 +14,20 @@ function isStyleVisible(element) {
)
}
-function isElementVisible(element) {
+function isAttributeVisible(element, previousElement) {
return (
- isStyleVisible(element) &&
!element.hasAttribute('hidden') &&
- (!element.parentElement || isElementVisible(element.parentElement))
+ (element.nodeName === 'DETAILS' && previousElement.nodeName !== 'SUMMARY'
+ ? element.hasAttribute('open')
+ : true)
+ )
+}
+
+function isElementVisible(element, previousElement) {
+ return (
+ isStyleVisible(element) &&
+ isAttributeVisible(element, previousElement) &&
+ (!element.parentElement || isElementVisible(element.parentElement, element))
)
}