diff --git a/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.test.ts b/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.test.ts new file mode 100644 index 0000000..4e96053 --- /dev/null +++ b/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.test.ts @@ -0,0 +1,116 @@ +import { render } from '../../testing' +import { assertElementIsDisabled } from './assertElementIsDisabled' +import { roles } from 'aria-query' + +const supportedRoles = roles + .entries() + .reduce((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(` + + + `) + + 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(` + + + `) + + const checkbox = getByRole('checkbox') + const radio = getByRole('radio') + + expect(assertElementIsDisabled(checkbox)).toEqual({ + pass: false, + message: 'Expected the element to be disabled', + negatedMessage: 'Expected the element not to be enabled', + expected: '', + received: 'Element is enabled', + }) + expect(assertElementIsDisabled(radio)).toEqual({ + pass: false, + message: 'Expected the element to be disabled', + negatedMessage: 'Expected the 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(`
`) + + 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(`
`) + + expect(assertElementIsDisabled(getByRole(role))).toEqual({ + pass: false, + message: 'Expected the
element to be disabled', + negatedMessage: 'Expected the
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(` +
+
+ + +
+
+ `) + + 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(` +
+ + + + +
+ `) + + const checkbox = getByRole('checkbox') + const radio = getByRole('radio') + + expect(assertElementIsDisabled(checkbox).pass).toEqual(false) + expect(assertElementIsDisabled(radio).pass).toEqual(false) +}) diff --git a/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.ts b/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.ts new file mode 100644 index 0000000..7dfef38 --- /dev/null +++ b/packages/dom-assertions/src/assertElementIsDisabled/assertElementIsDisabled.ts @@ -0,0 +1,60 @@ +import { roles } from 'aria-query' +import { assertIsHTMLOrSVGElement } from '../assertIsHTMLOrSVGElement/assertIsHTMLOrSVGElement' +import { elementToString } from '../utils' + +const supportedRoles = roles + .entries() + .reduce((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) +}