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
-
- -
-

Hello World

-
-
- `) - - 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
+
+ +
+

Hello World

+
+
+ `) + + 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(` + + `) + }) + + 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)) ) }