/**
* @fileoverview Enforce that an element does not have an unsupported ARIA attribute.
* @author Ethan Cohen
*/
// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------
import {
aria,
roles,
} from 'aria-query';
import { RuleTester } from 'eslint';
import { version as eslintVersion } from 'eslint/package.json';
import semver from 'semver';
import iterFrom from 'es-iterator-helpers/Iterator.from';
import filter from 'es-iterator-helpers/Iterator.prototype.filter';
import map from 'es-iterator-helpers/Iterator.prototype.map';
import toArray from 'es-iterator-helpers/Iterator.prototype.toArray';
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
import parsers from '../../__util__/helpers/parsers';
import rule from '../../../src/rules/role-supports-aria-props';
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester();
const generateErrorMessage = (attr, role, tag, isImplicit) => {
if (isImplicit) {
return `The attribute ${attr} is not supported by the role ${role}. This role is implicit on the element ${tag}.`;
}
return `The attribute ${attr} is not supported by the role ${role}.`;
};
const errorMessage = (attr, role, tag, isImplicit) => ({
message: generateErrorMessage(attr, role, tag, isImplicit),
type: 'JSXOpeningElement',
});
const componentsSettings = {
'jsx-a11y': {
components: {
Link: 'a',
},
},
};
const nonAbstractRoles = toArray(filter(iterFrom(roles.keys()), (role) => roles.get(role).abstract === false));
const createTests = (rolesNames) => rolesNames.reduce((tests, role) => {
const {
props: propKeyValues,
} = roles.get(role);
const validPropsForRole = Object.keys(propKeyValues);
const invalidPropsForRole = filter(
map(iterFrom(aria.keys()), (attribute) => attribute.toLowerCase()),
(attribute) => validPropsForRole.indexOf(attribute) === -1,
);
const normalRole = role.toLowerCase();
return [
tests[0].concat(validPropsForRole.map((prop) => ({
code: `
`,
}))),
tests[1].concat(toArray(map(invalidPropsForRole, (prop) => ({
code: ``,
errors: [errorMessage(prop.toLowerCase(), normalRole, 'div', false)],
})))),
];
}, [[], []]);
const [validTests, invalidTests] = createTests(nonAbstractRoles);
ruleTester.run('role-supports-aria-props', rule, {
valid: parsers.all([].concat(
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// IMPLICIT ROLE TESTS
// A TESTS - implicit role is `link`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// this will have global
{ code: '' },
// AREA TESTS - implicit role is `link`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// this will have global
{ code: '' },
// LINK TESTS - implicit role is `link`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// this will have global
{ code: '' },
// IMG TESTS - no implicit role
{ code: '
' },
// this will have role of `img`
{ code: '
' },
// MENU TESTS - implicit role is `toolbar` when `type="toolbar"`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// this will have global
{ code: '' },
// MENUITEM TESTS
// when `type="command`, the implicit role is `menuitem`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="checkbox`, the implicit role is `menuitemcheckbox`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="radio`, the implicit role is `menuitemradio`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// these will have global
{ code: '' },
{ code: '' },
// INPUT TESTS
// when `type="button"`, the implicit role is `button`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="image"`, the implicit role is `button`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="reset"`, the implicit role is `button`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="submit"`, the implicit role is `button`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="checkbox"`, the implicit role is `checkbox`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="radio"`, the implicit role is `radio`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// when `type="range"`, the implicit role is `slider`
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// these will have role of `textbox`,
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
// Allow null/undefined values regardless of role
{ code: '' },
{ code: '' },
// OTHER TESTS
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '
' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
semver.satisfies(eslintVersion, '>= 6') ? {
code: `
const HelloThere = () => (
Hello
There
>
}
/>
);
const Hello = (props) => {props.frag}
;
`,
} : [],
validTests,
)).map(parserOptionsMapper),
invalid: parsers.all([].concat(
// implicit basic checks
{
code: '',
errors: [errorMessage('aria-checked', 'link', 'a', true)],
},
{
code: '',
errors: [errorMessage('aria-checked', 'link', 'area', true)],
},
{
code: '',
errors: [errorMessage('aria-checked', 'link', 'link', true)],
},
{
code: '
',
errors: [errorMessage('aria-checked', 'img', 'img', true)],
},
{
code: '',
errors: [errorMessage('aria-checked', 'toolbar', 'menu', true)],
},
{
code: '',
errors: [errorMessage('aria-checked', 'complementary', 'aside', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'list', 'ul', true)],
},
{
code: ' ',
errors: [errorMessage('aria-expanded', 'group', 'details', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'dialog', 'dialog', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'complementary', 'aside', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'article', 'article', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'document', 'body', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'listitem', 'li', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'navigation', 'nav', true)],
},
{
code: '
',
errors: [errorMessage('aria-expanded', 'list', 'ol', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'status', 'output', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'region', 'section', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'rowgroup', 'tbody', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'rowgroup', 'tfoot', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'rowgroup', 'thead', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'radio', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-selected', 'radio', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-haspopup', 'radio', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-haspopup', 'checkbox', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'button', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'button', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'button', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'button', 'input', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'menuitem', 'menuitem', true)],
},
{
code: '',
errors: [errorMessage('aria-selected', 'menuitemradio', 'menuitem', true)],
},
{
code: '',
errors: [errorMessage('aria-haspopup', 'toolbar', 'menu', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'toolbar', 'menu', true)],
},
{
code: '',
errors: [errorMessage('aria-expanded', 'toolbar', 'menu', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'link', 'link', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'link', 'area', true)],
},
{
code: '',
errors: [errorMessage('aria-invalid', 'link', 'a', true)],
},
{
code: '',
errors: [errorMessage('aria-checked', 'link', 'a', true)],
settings: componentsSettings,
},
)).concat(invalidTests).map(parserOptionsMapper),
});