diff --git a/package.json b/package.json index 44e1d274..017169a5 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "dependencies": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^4.2.2", "chalk": "^3.0.0", "css": "^2.2.4", "css.escape": "^1.5.1", @@ -55,7 +56,9 @@ }, "overrides": [ { - "files": ["src/__tests__/*.js"], + "files": [ + "src/__tests__/*.js" + ], "rules": { "max-lines-per-function": "off" } diff --git a/src/__tests__/to-be-checked.js b/src/__tests__/to-be-checked.js index 8dbffd35..365958d9 100644 --- a/src/__tests__/to-be-checked.js +++ b/src/__tests__/to-be-checked.js @@ -51,6 +51,16 @@ describe('.toBeChecked', () => { expect(queryByTestId('aria-switch-unchecked')).not.toBeChecked() }) + test('handles element with role="menuitemcheckbox"', () => { + const {queryByTestId} = render(` +
+
+ `) + + expect(queryByTestId('aria-menuitemcheckbox-checked')).toBeChecked() + expect(queryByTestId('aria-menuitemcheckbox-unchecked')).not.toBeChecked() + }) + test('throws when checkbox input is checked but expected not to be', () => { const {queryByTestId} = render( ``, @@ -159,7 +169,7 @@ describe('.toBeChecked', () => { expect(() => expect(queryByTestId('aria-checkbox-invalid')).toBeChecked(), ).toThrowError( - 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox", role="radio" or role="switch" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead', + /only inputs with .* a valid aria-checked attribute can be used/, ) }) @@ -171,7 +181,7 @@ describe('.toBeChecked', () => { expect(() => expect(queryByTestId('aria-radio-invalid')).toBeChecked(), ).toThrowError( - 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox", role="radio" or role="switch" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead', + /only inputs with .* a valid aria-checked attribute can be used/, ) }) @@ -183,14 +193,14 @@ describe('.toBeChecked', () => { expect(() => expect(queryByTestId('aria-switch-invalid')).toBeChecked(), ).toThrowError( - 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox", role="radio" or role="switch" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead', + /only inputs with .* a valid aria-checked attribute can be used/, ) }) test('throws when the element is not an input', () => { const {queryByTestId} = render(``) expect(() => expect(queryByTestId('select')).toBeChecked()).toThrowError( - 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox", role="radio" or role="switch" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead', + /only inputs with type="checkbox" or type="radio" or elements with.* role="checkbox".* role="menuitemcheckbox".* role="radio".* role="switch" .* can be used/, ) }) }) diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js index 5eae7f20..58733365 100644 --- a/src/__tests__/utils.js +++ b/src/__tests__/utils.js @@ -3,6 +3,7 @@ import { checkHtmlElement, HtmlElementTypeError, parseJStoCSS, + toSentence, } from '../utils' import document from './helpers/document' @@ -116,3 +117,29 @@ describe('parseJStoCSS', () => { }) }) }) + +describe('toSentence', () => { + it('turns array into string of comma separated list with default last word connector', () => { + expect(toSentence(['one', 'two', 'three'])).toBe('one, two and three') + }) + + it('supports custom word connector', () => { + expect(toSentence(['one', 'two', 'three'], {wordConnector: '; '})).toBe( + 'one; two and three', + ) + }) + + it('supports custom last word connector', () => { + expect( + toSentence(['one', 'two', 'three'], {lastWordConnector: ' or '}), + ).toBe('one, two or three') + }) + + it('turns one element array into string containing first element', () => { + expect(toSentence(['one'])).toBe('one') + }) + + it('turns empty array into empty string', () => { + expect(toSentence([])).toBe('') + }) +}) diff --git a/src/to-be-checked.js b/src/to-be-checked.js index f7bafff8..2f44add7 100644 --- a/src/to-be-checked.js +++ b/src/to-be-checked.js @@ -1,5 +1,6 @@ +import {roles} from 'aria-query' import {matcherHint, printReceived} from 'jest-matcher-utils' -import {checkHtmlElement} from './utils' +import {checkHtmlElement, toSentence} from './utils' export function toBeChecked(element) { checkHtmlElement(element, toBeChecked, this) @@ -13,7 +14,7 @@ export function toBeChecked(element) { const isValidAriaElement = () => { return ( - ['checkbox', 'radio', 'switch'].includes(element.getAttribute('role')) && + roleSupportsChecked(element.getAttribute('role')) && ['true', 'false'].includes(element.getAttribute('aria-checked')) ) } @@ -22,7 +23,7 @@ export function toBeChecked(element) { return { pass: false, message: () => - 'only inputs with type="checkbox" or type="radio" or elements with role="checkbox", role="radio" or role="switch" and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead', + `only inputs with type="checkbox" or type="radio" or elements with ${supportedRolesSentence()} and a valid aria-checked attribute can be used with .toBeChecked(). Use .toHaveValue() instead`, } } @@ -44,3 +45,18 @@ export function toBeChecked(element) { }, } } + +function supportedRolesSentence() { + return toSentence( + supportedRoles().map(role => `role="${role}"`), + {lastWordConnector: ' or '}, + ) +} + +function supportedRoles() { + return Array.from(roles.keys()).filter(roleSupportsChecked) +} + +function roleSupportsChecked(role) { + return roles.get(role)?.props['aria-checked'] !== undefined +} diff --git a/src/utils.js b/src/utils.js index c07a3037..c4fac2cb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -198,6 +198,15 @@ function parseJStoCSS(document, css) { return sandboxElement.style.cssText } +function toSentence( + array, + {wordConnector = ', ', lastWordConnector = ' and '} = {}, +) { + return [array.slice(0, -1).join(wordConnector), array[array.length - 1]].join( + array.length > 1 ? lastWordConnector : '', + ) +} + export { HtmlElementTypeError, checkHtmlElement, @@ -210,4 +219,5 @@ export { getSingleElementValue, compareArraysAsSet, parseJStoCSS, + toSentence, }