Skip to content

Commit ef0c595

Browse files
author
Charley
committed
quick attempt at checkVisibility API
1 parent 225a3e4 commit ef0c595

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

src/__tests__/role-helpers.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
logRoles,
55
getImplicitAriaRoles,
66
isInaccessible,
7+
isSubtreeInaccessible,
78
} from '../role-helpers'
89
import {render} from './helpers/test-utils'
910

@@ -25,7 +26,7 @@ function setup() {
2526
<a data-testid="invalid-link">invalid link</a>
2627
2728
<nav data-testid='a-nav' />
28-
29+
2930
<h1 data-testid='a-h1'>Main Heading</h1>
3031
<h2 data-testid='a-h2'>Sub Heading</h2>
3132
<h3 data-testid='a-h3'>Tertiary Heading</h3>
@@ -211,3 +212,74 @@ test.each([
211212

212213
expect(isInaccessible(container.querySelector('button'))).toBe(expected)
213214
})
215+
216+
describe('checkVisibility API integration', () => {
217+
let originalCheckVisibility
218+
219+
beforeEach(() => {
220+
// This may not exist depending on the environment
221+
originalCheckVisibility = Element.prototype.checkVisibility
222+
})
223+
224+
afterEach(() => {
225+
if (originalCheckVisibility) {
226+
Element.prototype.checkVisibility = originalCheckVisibility
227+
} else {
228+
delete Element.prototype.checkVisibility
229+
}
230+
})
231+
232+
test('uses checkVisibility when available', () => {
233+
const mockCheckVisibility = jest.fn().mockReturnValue(false)
234+
Element.prototype.checkVisibility = mockCheckVisibility
235+
236+
const {container} = render('<div><button>Test</button></div>')
237+
const button = container.querySelector('button')
238+
239+
const result = isSubtreeInaccessible(button)
240+
241+
expect(mockCheckVisibility).toHaveBeenCalledWith({
242+
visibilityProperty: true,
243+
opacityProperty: false,
244+
})
245+
expect(result).toBe(true)
246+
})
247+
248+
test('falls back to getComputedStyle when checkVisibility unavailable', () => {
249+
// Remove checkVisibility to test fallback
250+
delete Element.prototype.checkVisibility
251+
252+
const {container} = render('<button style="display: none;">Test</button>')
253+
const button = container.querySelector('button')
254+
255+
expect(isSubtreeInaccessible(button)).toBe(true)
256+
})
257+
258+
test('checkVisibility and fallback produce same results', () => {
259+
const testCases = [
260+
'<div><button>Visible</button></div>',
261+
'<div style="display: none;"><button>Hidden</button></div>',
262+
'<div style="visibility: hidden;"><button>Hidden</button></div>',
263+
'<div hidden><button>Hidden</button></div>',
264+
'<div aria-hidden="true"><button>Hidden</button></div>',
265+
]
266+
267+
testCases.forEach(html => {
268+
const {container: container1} = render(html)
269+
const button1 = container1.querySelector('button')
270+
271+
const resultWithAPI = isInaccessible(button1)
272+
273+
delete Element.prototype.checkVisibility
274+
const {container: container2} = render(html)
275+
const button2 = container2.querySelector('button')
276+
const resultWithoutAPI = isInaccessible(button2)
277+
278+
expect(resultWithAPI).toBe(resultWithoutAPI)
279+
280+
if (originalCheckVisibility) {
281+
Element.prototype.checkVisibility = originalCheckVisibility
282+
}
283+
})
284+
})
285+
})

src/role-helpers.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ import {getConfig} from './config'
88

99
const elementRoleList = buildElementRoleList(elementRoles)
1010

11+
const checkVisibilityOptions = {
12+
visibilityProperty: true,
13+
opacityProperty: false,
14+
}
15+
16+
function supportsCheckVisibility() {
17+
return (
18+
typeof Element !== 'undefined' && 'checkVisibility' in Element.prototype
19+
)
20+
}
21+
1122
/**
1223
* @param {Element} element -
1324
* @returns {boolean} - `true` if `element` and its subtree are inaccessible
@@ -21,6 +32,10 @@ function isSubtreeInaccessible(element) {
2132
return true
2233
}
2334

35+
if (supportsCheckVisibility()) {
36+
return !element.checkVisibility(checkVisibilityOptions)
37+
}
38+
2439
const window = element.ownerDocument.defaultView
2540
if (window.getComputedStyle(element).display === 'none') {
2641
return true
@@ -47,6 +62,27 @@ function isInaccessible(element, options = {}) {
4762
const {
4863
isSubtreeInaccessible: isSubtreeInaccessibleImpl = isSubtreeInaccessible,
4964
} = options
65+
66+
if (supportsCheckVisibility()) {
67+
if (!element.checkVisibility(checkVisibilityOptions)) {
68+
return true
69+
}
70+
71+
// Still need to walk up the tree for aria-hidden and hidden attributes
72+
let currentElement = element
73+
while (currentElement) {
74+
if (
75+
currentElement.hidden === true ||
76+
currentElement.getAttribute('aria-hidden') === 'true'
77+
) {
78+
return true
79+
}
80+
currentElement = currentElement.parentElement
81+
}
82+
83+
return false
84+
}
85+
5086
const window = element.ownerDocument.defaultView
5187
// since visibility is inherited we can exit early
5288
if (window.getComputedStyle(element).visibility === 'hidden') {

0 commit comments

Comments
 (0)