diff --git a/README.md b/README.md index 67dbd06b..e74bf237 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,9 @@ perspective. It matches if the element is a form control and the `disabled` attribute is specified on this element or the element is a descendant of a form element with -a `disabled` attribute. +a `disabled` attribute. It also matches if `aria-disabled` attribute of `"true"` +is specified on the element or the element is a descendant of any element with +`aria-disabled="true"` and the element itself is a form control. According to the specification, the following elements can be [actually disabled](https://html.spec.whatwg.org/multipage/semantics-other.html#disabled-elements): @@ -150,12 +152,18 @@ According to the specification, the following elements can be
link +
generic
+
+
``` ```javascript expect(getByTestId('button')).toBeDisabled() expect(getByTestId('input')).toBeDisabled() expect(getByText('link')).not.toBeDisabled() +expect(getByText('generic')).toBeDisabled() +expect(getByTestId('child-input')).toBeDisabled() +expect(getByTestId('non-focusable-child')).not.toBeDisabled() ```
@@ -239,9 +247,7 @@ This allows you to assert whether an element is present in the document or not. expect( getByTestId(document.documentElement, 'html-element'), ).toBeInTheDocument() -expect( - getByTestId(document.documentElement, 'svg-element'), -).toBeInTheDocument() +expect(getByTestId(document.documentElement, 'svg-element')).toBeInTheDocument() expect( queryByTestId(document.documentElement, 'does-not-exist'), ).not.toBeInTheDocument() @@ -1155,6 +1161,7 @@ Thanks goes to these people ([emoji key][emojis]): + This project follows the [all-contributors][all-contributors] specification. @@ -1205,5 +1212,6 @@ MIT [emojis]: https://allcontributors.org/docs/en/emoji-key [all-contributors]: https://github.com/all-contributors/all-contributors [guiding-principle]: https://testing-library.com/docs/guiding-principles -[discord-badge]: https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square +[discord-badge]: + https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square [discord]: https://discord.gg/c6JN9fM diff --git a/src/__tests__/to-be-disabled.js b/src/__tests__/to-be-disabled.js index db0cde56..6174157b 100644 --- a/src/__tests__/to-be-disabled.js +++ b/src/__tests__/to-be-disabled.js @@ -1,61 +1,151 @@ import {render} from './helpers/test-utils' -test('.toBeDisabled', () => { - const {queryByTestId} = render(` -
- - - +describe('.toBeDisabled', () => { + test('handles element with disabled attribute and element with disabled parent', () => { + const {queryByTestId} = render(` +
+ + + + +
+ +
-
- -
+
+ +
+ +
+
+ + + +
+ x +
-
- + x
+ `) + + expect(queryByTestId('button-element')).toBeDisabled() + expect(() => + expect(queryByTestId('button-element')).not.toBeDisabled(), + ).toThrowError() + expect(queryByTestId('textarea-element')).toBeDisabled() + expect(queryByTestId('input-element')).toBeDisabled() + + expect(queryByTestId('fieldset-element')).toBeDisabled() + expect(queryByTestId('fieldset-child-element')).toBeDisabled() + + expect(queryByTestId('div-element')).not.toBeDisabled() + expect(queryByTestId('div-child-element')).not.toBeDisabled() + + expect(queryByTestId('nested-form-element')).toBeDisabled() + expect(queryByTestId('deep-select-element')).toBeDisabled() + expect(queryByTestId('deep-optgroup-element')).toBeDisabled() + expect(queryByTestId('deep-option-element')).toBeDisabled() + + expect(queryByTestId('a-element')).not.toBeDisabled() + expect(queryByTestId('deep-a-element')).not.toBeDisabled() + expect(() => + expect(queryByTestId('a-element')).toBeDisabled(), + ).toThrowError() + expect(() => + expect(queryByTestId('deep-a-element')).toBeDisabled(), + ).toThrowError() + }) + + test('handles element with aria-disabled attribute', () => { + const {queryByTestId} = render(` +
+
+ `) + expect(queryByTestId(`aria-div-disabled`)).toBeDisabled() + expect(queryByTestId(`aria-div-enabled`)).not.toBeDisabled() + }) -
-
- - - + test('throws when element with aria-disabled attribute is disabled but expected not to be', () => { + const {queryByTestId} = render(` +
+ `) + expect(() => + expect(queryByTestId(`aria-div-disabled`)).not.toBeDisabled(), + ).toThrowError() + }) + + test('throws when element with aria-disabled role is not disabled but expected to be', () => { + const {queryByTestId} = render(` +
+ `) + expect(() => + expect(queryByTestId(`aria-div-enabled`)).toBeDisabled(), + ).toThrowError() + }) + + test('handles inheritance of the disabled state from a parent with aria-disabled attribute', () => { + const {queryByTestId} = render(` +
+
+ +
+ +
+
- x -
- x -
- `) +
+ +
+ +
+
x +
+ +
+
+ - expect(queryByTestId('button-element')).toBeDisabled() - expect(() => - expect(queryByTestId('button-element')).not.toBeDisabled(), - ).toThrowError() - expect(queryByTestId('textarea-element')).toBeDisabled() - expect(queryByTestId('input-element')).toBeDisabled() - - expect(queryByTestId('fieldset-element')).toBeDisabled() - expect(queryByTestId('fieldset-child-element')).toBeDisabled() - - expect(queryByTestId('div-element')).not.toBeDisabled() - expect(queryByTestId('div-child-element')).not.toBeDisabled() - - expect(queryByTestId('nested-form-element')).toBeDisabled() - expect(queryByTestId('deep-select-element')).toBeDisabled() - expect(queryByTestId('deep-optgroup-element')).toBeDisabled() - expect(queryByTestId('deep-option-element')).toBeDisabled() - - expect(queryByTestId('a-element')).not.toBeDisabled() - expect(queryByTestId('deep-a-element')).not.toBeDisabled() - expect(() => expect(queryByTestId('a-element')).toBeDisabled()).toThrowError() - expect(() => - expect(queryByTestId('deep-a-element')).toBeDisabled(), - ).toThrowError() + +
+ x +
+ + x +
+ `) + + expect(queryByTestId('fieldset-element')).not.toBeDisabled() + expect(queryByTestId('fieldset-child-element')).not.toBeDisabled() + expect(queryByTestId('div-element')).toBeDisabled() + expect(queryByTestId('div-child-element')).toBeDisabled() + expect(queryByTestId('div-enabled-element')).not.toBeDisabled() + expect(queryByTestId('div-enabled-child-element')).not.toBeDisabled() + expect(queryByTestId('div-element-2')).toBeDisabled() + expect(queryByTestId('non-focusable-child-element')).not.toBeDisabled() + expect(queryByTestId('nested-form-element')).toBeDisabled() + expect(queryByTestId('deep-select-element')).toBeDisabled() + expect(queryByTestId('deep-optgroup-element')).toBeDisabled() + expect(queryByTestId('deep-option-element')).toBeDisabled() + + expect(queryByTestId('a-element')).not.toBeDisabled() + expect(queryByTestId('deep-a-element')).not.toBeDisabled() + expect(() => + expect(queryByTestId('a-element')).toBeDisabled(), + ).toThrowError() + expect(() => + expect(queryByTestId('deep-a-element')).toBeDisabled(), + ).toThrowError() + }) }) test('.toBeDisabled fieldset>legend', () => { @@ -109,80 +199,168 @@ test('.toBeDisabled fieldset>legend', () => { expect(queryByTestId('outer-fieldset-element')).toBeDisabled() }) -test('.toBeEnabled', () => { - const {queryByTestId} = render(` -
- - - +describe('.toBeEnabled', () => { + test('handles element with disabled attribute and element with disabled parent', () => { + const {queryByTestId} = render(` +
+ + + -
- -
+
+ +
+ +
+ +
+ +
+
+ + + +
+ x +
-
- + x
+ `) + + expect(() => { + expect(queryByTestId('button-element')).toBeEnabled() + }).toThrowError() + expect(queryByTestId('button-element')).not.toBeEnabled() + expect(() => { + expect(queryByTestId('textarea-element')).toBeEnabled() + }).toThrowError() + expect(() => { + expect(queryByTestId('input-element')).toBeEnabled() + }).toThrowError() + + expect(() => { + expect(queryByTestId('fieldset-element')).toBeEnabled() + }).toThrowError() + expect(() => { + expect(queryByTestId('fieldset-child-element')).toBeEnabled() + }).toThrowError() + + expect(queryByTestId('div-element')).toBeEnabled() + expect(queryByTestId('div-child-element')).toBeEnabled() + + expect(() => { + expect(queryByTestId('nested-form-element')).toBeEnabled() + }).toThrowError() + expect(() => { + expect(queryByTestId('deep-select-element')).toBeEnabled() + }).toThrowError() + expect(() => { + expect(queryByTestId('deep-optgroup-element')).toBeEnabled() + }).toThrowError() + expect(() => { + expect(queryByTestId('deep-option-element')).toBeEnabled() + }).toThrowError() + + expect(queryByTestId('a-element')).toBeEnabled() + expect(() => + expect(queryByTestId('a-element')).not.toBeEnabled(), + ).toThrowError() + expect(queryByTestId('deep-a-element')).toBeEnabled() + expect(() => + expect(queryByTestId('deep-a-element')).not.toBeEnabled(), + ).toThrowError() + }) + + test('handles element with aria-disabled attribute', () => { + const {queryByTestId} = render(` +
+
+ `) + expect(queryByTestId(`aria-div-disabled`)).not.toBeEnabled() + expect(queryByTestId(`aria-div-enabled`)).toBeEnabled() + }) -
-
- - - + test('throws when element with aria-disabled attribute is enabled but expected not to be', () => { + const {queryByTestId} = render(` +
+ `) + expect(() => + expect(queryByTestId(`aria-div-enabled`)).not.toBeEnabled(), + ).toThrowError() + }) + + test('throws when element with aria-disabled role is not enabled but expected to be', () => { + const {queryByTestId} = render(` +
+ `) + expect(() => + expect(queryByTestId(`aria-div-disabled`)).toBeEnabled(), + ).toThrowError() + }) + + test('handles inheritance of the disabled state from a parent with aria-disabled attribute', () => { + const {queryByTestId} = render(` +
+
+ +
+ +
+
- x -
- x -
- `) +
+ +
- expect(() => { - expect(queryByTestId('button-element')).toBeEnabled() - }).toThrowError() - expect(queryByTestId('button-element')).not.toBeEnabled() - expect(() => { - expect(queryByTestId('textarea-element')).toBeEnabled() - }).toThrowError() - expect(() => { - expect(queryByTestId('input-element')).toBeEnabled() - }).toThrowError() +
+
x +
- expect(() => { - expect(queryByTestId('fieldset-element')).toBeEnabled() - }).toThrowError() - expect(() => { - expect(queryByTestId('fieldset-child-element')).toBeEnabled() - }).toThrowError() +
+
+ - expect(queryByTestId('div-element')).toBeEnabled() - expect(queryByTestId('div-child-element')).toBeEnabled() + +
+ x +
- expect(() => { - expect(queryByTestId('nested-form-element')).toBeEnabled() - }).toThrowError() - expect(() => { - expect(queryByTestId('deep-select-element')).toBeEnabled() - }).toThrowError() - expect(() => { - expect(queryByTestId('deep-optgroup-element')).toBeEnabled() - }).toThrowError() - expect(() => { - expect(queryByTestId('deep-option-element')).toBeEnabled() - }).toThrowError() + x +
+ `) - expect(queryByTestId('a-element')).toBeEnabled() - expect(() => - expect(queryByTestId('a-element')).not.toBeEnabled(), - ).toThrowError() - expect(queryByTestId('deep-a-element')).toBeEnabled() - expect(() => - expect(queryByTestId('deep-a-element')).not.toBeEnabled(), - ).toThrowError() + expect(queryByTestId('fieldset-element')).toBeEnabled() + expect(queryByTestId('fieldset-child-element')).toBeEnabled() + expect(queryByTestId('div-element')).not.toBeEnabled() + expect(queryByTestId('div-child-element')).not.toBeEnabled() + expect(queryByTestId('div-enabled-element')).toBeEnabled() + expect(queryByTestId('div-enabled-child-element')).toBeEnabled() + expect(queryByTestId('div-element-2')).not.toBeEnabled() + expect(queryByTestId('non-focusable-child-element')).toBeEnabled() + expect(queryByTestId('nested-form-element')).not.toBeEnabled() + expect(queryByTestId('deep-select-element')).not.toBeEnabled() + expect(queryByTestId('deep-optgroup-element')).not.toBeEnabled() + expect(queryByTestId('deep-option-element')).not.toBeEnabled() + + expect(queryByTestId('a-element')).toBeEnabled() + expect(queryByTestId('deep-a-element')).toBeEnabled() + expect(() => + expect(queryByTestId('a-element')).not.toBeEnabled(), + ).toThrowError() + expect(() => + expect(queryByTestId('deep-a-element')).not.toBeEnabled(), + ).toThrowError() + }) }) test('.toBeEnabled fieldset>legend', () => { diff --git a/src/to-be-disabled.js b/src/to-be-disabled.js index 9efe6f98..80778f1a 100644 --- a/src/to-be-disabled.js +++ b/src/to-be-disabled.js @@ -41,7 +41,11 @@ function canElementBeDisabled(element) { } function isElementDisabled(element) { - return canElementBeDisabled(element) && element.hasAttribute('disabled') + if (canElementBeDisabled(element)) { + return element.hasAttribute('disabled') + } else { + return element.getAttribute('aria-disabled') === 'true' + } } function isAncestorDisabled(element) { @@ -54,8 +58,8 @@ function isAncestorDisabled(element) { function isElementOrAncestorDisabled(element) { return ( - canElementBeDisabled(element) && - (isElementDisabled(element) || isAncestorDisabled(element)) + isElementDisabled(element) || + (canElementBeDisabled(element) && isAncestorDisabled(element)) ) }