Skip to content

Commit e4d61c2

Browse files
lourencignapse
authored andcommitted
fix: toBeVisible ignoring Details element (#184)
1 parent d87dfee commit e4d61c2

File tree

3 files changed

+258
-35
lines changed

3 files changed

+258
-35
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ clear to read and to maintain.
4646
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
4747
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
4848

49+
4950
- [Installation](#installation)
5051
- [Usage](#usage)
5152
- [Custom matchers](#custom-matchers)
@@ -378,6 +379,7 @@ An element is visible if **all** the following conditions are met:
378379
- it does not have the
379380
[`hidden`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden)
380381
attribute
382+
- if `<details />` it has the `open` attribute
381383

382384
#### Examples
383385

src/__tests__/to-be-visible.js

Lines changed: 244 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,247 @@
11
import {render} from './helpers/test-utils'
22

3-
test('.toBeVisible', () => {
4-
const {container} = render(`
5-
<div>
6-
<header>
7-
<h1 style="display: none">Main title</h1>
8-
<h2 style="visibility: hidden">Secondary title</h2>
9-
<h3 style="visibility: collapse">Secondary title</h3>
10-
<h4 style="opacity: 0">Secondary title</h4>
11-
<h5 style="opacity: 0.1">Secondary title</h5>
12-
</header>
13-
<button hidden>Hidden button</button>
14-
<section style="display: block; visibility: hidden">
15-
<p>Hello <strong>World</strong></p>
16-
</section>
17-
</div>
18-
`)
19-
20-
expect(container.querySelector('header')).toBeVisible()
21-
expect(container.querySelector('h1')).not.toBeVisible()
22-
expect(container.querySelector('h2')).not.toBeVisible()
23-
expect(container.querySelector('h3')).not.toBeVisible()
24-
expect(container.querySelector('h4')).not.toBeVisible()
25-
expect(container.querySelector('h5')).toBeVisible()
26-
expect(container.querySelector('button')).not.toBeVisible()
27-
expect(container.querySelector('strong')).not.toBeVisible()
28-
29-
expect(() =>
30-
expect(container.querySelector('header')).not.toBeVisible(),
31-
).toThrowError()
32-
expect(() =>
33-
expect(container.querySelector('p')).toBeVisible(),
34-
).toThrowError()
3+
// eslint-disable-next-line max-lines-per-function
4+
describe('.toBeVisible', () => {
5+
it('returns the visibility of an element', () => {
6+
const {container} = render(`
7+
<div>
8+
<header>
9+
<h1 style="display: none">Main title</h1>
10+
<h2 style="visibility: hidden">Secondary title</h2>
11+
<h3 style="visibility: collapse">Secondary title</h3>
12+
<h4 style="opacity: 0">Secondary title</h4>
13+
<h5 style="opacity: 0.1">Secondary title</h5>
14+
</header>
15+
<button hidden>Hidden button</button>
16+
<section style="display: block; visibility: hidden">
17+
<p>Hello <strong>World</strong></p>
18+
</section>
19+
</div>
20+
`)
21+
22+
expect(container.querySelector('header')).toBeVisible()
23+
expect(container.querySelector('h1')).not.toBeVisible()
24+
expect(container.querySelector('h2')).not.toBeVisible()
25+
expect(container.querySelector('h3')).not.toBeVisible()
26+
expect(container.querySelector('h4')).not.toBeVisible()
27+
expect(container.querySelector('h5')).toBeVisible()
28+
expect(container.querySelector('button')).not.toBeVisible()
29+
expect(container.querySelector('strong')).not.toBeVisible()
30+
31+
expect(() =>
32+
expect(container.querySelector('header')).not.toBeVisible(),
33+
).toThrowError()
34+
expect(() =>
35+
expect(container.querySelector('p')).toBeVisible(),
36+
).toThrowError()
37+
})
38+
39+
// eslint-disable-next-line max-lines-per-function
40+
describe('with a <details /> element', () => {
41+
let subject
42+
43+
afterEach(() => {
44+
subject = undefined
45+
})
46+
47+
describe('when the details is opened', () => {
48+
beforeEach(() => {
49+
subject = render(`
50+
<details open>
51+
<summary>Title of visible</summary>
52+
<div>Visible <small>details</small></div>
53+
</details>
54+
`)
55+
})
56+
57+
it('returns true to the details content', () => {
58+
expect(subject.container.querySelector('div')).toBeVisible()
59+
})
60+
61+
it('returns true to the most inner details content', () => {
62+
expect(subject.container.querySelector('small')).toBeVisible()
63+
})
64+
65+
it('returns true to the details summary', () => {
66+
expect(subject.container.querySelector('summary')).toBeVisible()
67+
})
68+
69+
describe('when the user clicks on the summary', () => {
70+
beforeEach(() => subject.container.querySelector('summary').click())
71+
72+
it('returns false to the details content', () => {
73+
expect(subject.container.querySelector('div')).not.toBeVisible()
74+
})
75+
76+
it('returns true to the details summary', () => {
77+
expect(subject.container.querySelector('summary')).toBeVisible()
78+
})
79+
})
80+
})
81+
82+
describe('when the details is not opened', () => {
83+
beforeEach(() => {
84+
subject = render(`
85+
<details>
86+
<summary>Title of hidden</summary>
87+
<div>Hidden details</div>
88+
</details>
89+
`)
90+
})
91+
92+
it('returns false to the details content', () => {
93+
expect(subject.container.querySelector('div')).not.toBeVisible()
94+
})
95+
96+
it('returns true to the summary content', () => {
97+
expect(subject.container.querySelector('summary')).toBeVisible()
98+
})
99+
100+
describe('when the user clicks on the summary', () => {
101+
beforeEach(() => subject.container.querySelector('summary').click())
102+
103+
it('returns true to the details content', () => {
104+
expect(subject.container.querySelector('div')).toBeVisible()
105+
})
106+
107+
it('returns true to the details summary', () => {
108+
expect(subject.container.querySelector('summary')).toBeVisible()
109+
})
110+
})
111+
})
112+
113+
describe('when the details is opened but it is hidden', () => {
114+
beforeEach(() => {
115+
subject = render(`
116+
<details open hidden>
117+
<summary>Title of visible</summary>
118+
<div>Visible details</div>
119+
</details>
120+
`)
121+
})
122+
123+
it('returns false to the details content', () => {
124+
expect(subject.container.querySelector('div')).not.toBeVisible()
125+
})
126+
127+
it('returns false to the details summary', () => {
128+
expect(subject.container.querySelector('summary')).not.toBeVisible()
129+
})
130+
})
131+
132+
describe('with a nested <details /> element', () => {
133+
describe('when the nested <details /> is opened', () => {
134+
beforeEach(() => {
135+
subject = render(`
136+
<details open>
137+
<summary>Title of visible</summary>
138+
<div>Outer content</div>
139+
<details open>
140+
<summary>Title of nested details</summary>
141+
<div>Inner content</div>
142+
</details>
143+
</details>
144+
`)
145+
})
146+
147+
it('returns true to the nested details content', () => {
148+
expect(
149+
subject.container.querySelector('details > details > div'),
150+
).toBeVisible()
151+
})
152+
153+
it('returns true to the nested details summary', () => {
154+
expect(
155+
subject.container.querySelector('details > details > summary'),
156+
).toBeVisible()
157+
})
158+
159+
it('returns true to the outer details content', () => {
160+
expect(subject.container.querySelector('details > div')).toBeVisible()
161+
})
162+
163+
it('returns true to the outer details summary', () => {
164+
expect(
165+
subject.container.querySelector('details > summary'),
166+
).toBeVisible()
167+
})
168+
})
169+
170+
describe('when the nested <details /> is not opened', () => {
171+
beforeEach(() => {
172+
subject = render(`
173+
<details open>
174+
<summary>Title of visible</summary>
175+
<div>Outer content</div>
176+
<details>
177+
<summary>Title of nested details</summary>
178+
<div>Inner content</div>
179+
</details>
180+
</details>
181+
`)
182+
})
183+
184+
it('returns false to the nested details content', () => {
185+
expect(
186+
subject.container.querySelector('details > details > div'),
187+
).not.toBeVisible()
188+
})
189+
190+
it('returns true to the nested details summary', () => {
191+
expect(
192+
subject.container.querySelector('details > details > summary'),
193+
).toBeVisible()
194+
})
195+
196+
it('returns true to the outer details content', () => {
197+
expect(subject.container.querySelector('details > div')).toBeVisible()
198+
})
199+
200+
it('returns true to the outer details summary', () => {
201+
expect(
202+
subject.container.querySelector('details > summary'),
203+
).toBeVisible()
204+
})
205+
})
206+
207+
describe('when the outer <details /> is not opened and the nested one is opened', () => {
208+
beforeEach(() => {
209+
subject = render(`
210+
<details>
211+
<summary>Title of visible</summary>
212+
<div>Outer content</div>
213+
<details open>
214+
<summary>Title of nested details</summary>
215+
<div>Inner content</div>
216+
</details>
217+
</details>
218+
`)
219+
})
220+
221+
it('returns false to the nested details content', () => {
222+
expect(
223+
subject.container.querySelector('details > details > div'),
224+
).not.toBeVisible()
225+
})
226+
227+
it('returns false to the nested details summary', () => {
228+
expect(
229+
subject.container.querySelector('details > details > summary'),
230+
).not.toBeVisible()
231+
})
232+
233+
it('returns false to the outer details content', () => {
234+
expect(
235+
subject.container.querySelector('details > div'),
236+
).not.toBeVisible()
237+
})
238+
239+
it('returns true to the outer details summary', () => {
240+
expect(
241+
subject.container.querySelector('details > summary'),
242+
).toBeVisible()
243+
})
244+
})
245+
})
246+
})
35247
})

src/to-be-visible.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ function isStyleVisible(element) {
1414
)
1515
}
1616

17-
function isElementVisible(element) {
17+
function isAttributeVisible(element, previousElement) {
1818
return (
19-
isStyleVisible(element) &&
2019
!element.hasAttribute('hidden') &&
21-
(!element.parentElement || isElementVisible(element.parentElement))
20+
(element.nodeName === 'DETAILS' && previousElement.nodeName !== 'SUMMARY'
21+
? element.hasAttribute('open')
22+
: true)
23+
)
24+
}
25+
26+
function isElementVisible(element, previousElement) {
27+
return (
28+
isStyleVisible(element) &&
29+
isAttributeVisible(element, previousElement) &&
30+
(!element.parentElement || isElementVisible(element.parentElement, element))
2231
)
2332
}
2433

0 commit comments

Comments
 (0)