Skip to content
Open
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
58 changes: 57 additions & 1 deletion src/__tests__/role-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
logRoles,
getImplicitAriaRoles,
isInaccessible,
isSubtreeInaccessible,
} from '../role-helpers'
import {render} from './helpers/test-utils'

Expand All @@ -25,7 +26,7 @@ function setup() {
<a data-testid="invalid-link">invalid link</a>

<nav data-testid='a-nav' />

<h1 data-testid='a-h1'>Main Heading</h1>
<h2 data-testid='a-h2'>Sub Heading</h2>
<h3 data-testid='a-h3'>Tertiary Heading</h3>
Expand Down Expand Up @@ -211,3 +212,58 @@ test.each([

expect(isInaccessible(container.querySelector('button'))).toBe(expected)
})

describe('checkVisibility API integration', () => {
beforeEach(() => {
if (Element.prototype.checkVisibility) {
delete Element.prototype.checkVisibility
}
})

test('uses checkVisibility when available', () => {
const mockCheckVisibility = jest.fn().mockReturnValue(false)
Element.prototype.checkVisibility = mockCheckVisibility

const {container} = render('<div><button>Test</button></div>')
const button = container.querySelector('button')

const result = isSubtreeInaccessible(button)

expect(mockCheckVisibility).toHaveBeenCalledWith({
visibilityProperty: true,
opacityProperty: false,
})
expect(result).toBe(true)
})

test('falls back to getComputedStyle when checkVisibility unavailable', () => {
const {container} = render('<button style="display: none;">Test</button>')
const button = container.querySelector('button')

expect(isSubtreeInaccessible(button)).toBe(true)
})

test('checkVisibility and fallback produce same results', () => {
const testCases = [
'<div><button>Visible</button></div>',
'<div style="display: none;"><button>Hidden</button></div>',
'<div style="visibility: hidden;"><button>Hidden</button></div>',
'<div hidden><button>Hidden</button></div>',
'<div aria-hidden="true"><button>Hidden</button></div>',
]

testCases.forEach(html => {
const {container: container1} = render(html)
const button1 = container1.querySelector('button')

const resultWithAPI = isInaccessible(button1)

delete Element.prototype.checkVisibility
const {container: container2} = render(html)
const button2 = container2.querySelector('button')
const resultWithoutAPI = isInaccessible(button2)

expect(resultWithAPI).toBe(resultWithoutAPI)
})
})
})
36 changes: 36 additions & 0 deletions src/role-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ import {getConfig} from './config'

const elementRoleList = buildElementRoleList(elementRoles)

const checkVisibilityOptions = {
visibilityProperty: true,
opacityProperty: false,
}

function supportsCheckVisibility() {
return (
typeof Element !== 'undefined' && 'checkVisibility' in Element.prototype
)
}

/**
* @param {Element} element -
* @returns {boolean} - `true` if `element` and its subtree are inaccessible
Expand All @@ -21,6 +32,10 @@ function isSubtreeInaccessible(element) {
return true
}

if (supportsCheckVisibility()) {
return !element.checkVisibility(checkVisibilityOptions)
}

const window = element.ownerDocument.defaultView
if (window.getComputedStyle(element).display === 'none') {
return true
Expand All @@ -47,6 +62,27 @@ function isInaccessible(element, options = {}) {
const {
isSubtreeInaccessible: isSubtreeInaccessibleImpl = isSubtreeInaccessible,
} = options

if (supportsCheckVisibility()) {
if (!element.checkVisibility(checkVisibilityOptions)) {
return true
}

// Still need to walk up the tree for aria-hidden and hidden attributes
let currentElement = element
while (currentElement) {
if (
currentElement.hidden === true ||
currentElement.getAttribute('aria-hidden') === 'true'
) {
return true
}
currentElement = currentElement.parentElement
}

return false
}

const window = element.ownerDocument.defaultView
// since visibility is inherited we can exit early
if (window.getComputedStyle(element).visibility === 'hidden') {
Expand Down
Loading