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
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { render } from '../../testing'
import { assertElementIsDisabled } from './assertElementIsDisabled'
import { roles } from 'aria-query'

const supportedRoles = roles
.entries()
.reduce<string[]>((acc, [role, { props }]) => {
if (props['aria-disabled'] !== undefined) acc.push(role)
return acc
}, [])

it('should check the element', () => {
// @ts-expect-error - in case the client code is using javascript
expect(assertElementIsDisabled(document)).toEqual({
pass: false,
message: expect.any(String),
negatedMessage: expect.any(String),
expected: 'HTMLElement or SVGElement',
received: expect.any(String),
})
})

it('should pass for disabled checkbox and radio inputs', () => {
const { getByRole } = render(`
<input type="checkbox" disabled/>
<input type="radio" disabled />
`)

const checkbox = getByRole('checkbox')
const radio = getByRole('radio')

expect(assertElementIsDisabled(checkbox).pass).toEqual(true)
expect(assertElementIsDisabled(radio).pass).toEqual(true)
})

it('should fail for enable checkbox and radio inputs', () => {
const { getByRole } = render(`
<input type="checkbox"/>
<input type="radio"/>
`)

const checkbox = getByRole('checkbox')
const radio = getByRole('radio')

expect(assertElementIsDisabled(checkbox)).toEqual({
pass: false,
message: 'Expected the <input> element to be disabled',
negatedMessage: 'Expected the <input> element not to be enabled',
expected: '',
received: 'Element is enabled',
})
expect(assertElementIsDisabled(radio)).toEqual({
pass: false,
message: 'Expected the <input> element to be disabled',
negatedMessage: 'Expected the <input> element not to be enabled',
expected: '',
received: 'Element is enabled',
})
})

it.each(supportedRoles)(
'should pass for elements with role `%s` and `aria-disabled="true"`',
(role) => {
const { getByRole } = render(`<div role="${role}" aria-disabled="true"/>`)

expect(assertElementIsDisabled(getByRole(role)).pass).toEqual(true)
},
)

it.each(supportedRoles)(
'should fail for elements with role `%s` and `aria-disabled="false"',
(role) => {
const { getByRole } = render(`<div role="${role}" aria-disabled="false"/>`)

expect(assertElementIsDisabled(getByRole(role))).toEqual({
pass: false,
message: 'Expected the <div> element to be disabled',
negatedMessage: 'Expected the <div> element not to be enabled',
expected: '',
received: 'Element is enabled',
})
},
)
it('should pass for disabled group of elements disabled by their parent', () => {
const { getByRole } = render(`
<form>
<fieldset disabled>
<input type="checkbox"/>
<input type="radio"/>
</fieldset>
</form>
`)

const checkbox = getByRole('checkbox')
const radio = getByRole('radio')

expect(assertElementIsDisabled(checkbox).pass).toEqual(true)
expect(assertElementIsDisabled(radio).pass).toEqual(true)
})

it.only('should pass for `fieldset` tag descendant element excluding the first legend', () => {
const { getByRole } = render(`
<fieldset disabled>
<legend>
<input type="checkbox"/>
<input type="radio"/>
</legend>
</fieldset>
`)

const checkbox = getByRole('checkbox')
const radio = getByRole('radio')

expect(assertElementIsDisabled(checkbox).pass).toEqual(false)
expect(assertElementIsDisabled(radio).pass).toEqual(false)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { roles } from 'aria-query'
import { assertIsHTMLOrSVGElement } from '../assertIsHTMLOrSVGElement/assertIsHTMLOrSVGElement'
import { elementToString } from '../utils'

const supportedRoles = roles
.entries()
.reduce<string[]>((acc, [role, { props }]) => {
if (props['aria-disabled'] !== undefined) acc.push(role)
return acc
}, [])

export function assertElementIsDisabled(htmlElement: HTMLElement) {
const elementCheckResult = assertIsHTMLOrSVGElement(htmlElement)
if (!elementCheckResult.pass) {
return elementCheckResult
}
const elementName = elementToString(htmlElement)

return {
pass: isDisabled(htmlElement),
message: `Expected the ${elementName} element to be disabled`,
negatedMessage: `Expected the ${elementName} element not to be enabled`,
expected: '',
received: `Element is ${isDisabled(htmlElement) ? 'disabled' : 'enabled'}`,
}
}

function canBeDisabled(
htmlElement: HTMLElement,
): htmlElement is HTMLInputElement {
return [
'fieldset',
'input',
'select',
'optgroup',
'option',
'button',
'textarea',
].includes(htmlElement.tagName.toLowerCase())
}

function elementIsDisabled(htmlElement: HTMLElement) {
if (canBeDisabled(htmlElement)) return htmlElement.disabled

return htmlElement.getAttribute('aria-disabled') === 'true'
}

function isDisabledByParent(htmlElementParent: HTMLElement) {
return elementIsDisabled(htmlElementParent) || isDisabled(htmlElementParent)
}

function isDisabled(htmlElement: HTMLElement): boolean {
const parentElement = htmlElement.parentElement

if (parentElement && canBeDisabled(parentElement)) {
return isDisabledByParent(parentElement)
}

return elementIsDisabled(htmlElement)
}