| 1 | /**
|
|---|
| 2 | * @fileoverview Enforce aria role attribute is valid.
|
|---|
| 3 | * @author Ethan Cohen
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | // -----------------------------------------------------------------------------
|
|---|
| 7 | // Requirements
|
|---|
| 8 | // -----------------------------------------------------------------------------
|
|---|
| 9 |
|
|---|
| 10 | import { roles } from 'aria-query';
|
|---|
| 11 | import { RuleTester } from 'eslint';
|
|---|
| 12 | import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|---|
| 13 | import parsers from '../../__util__/helpers/parsers';
|
|---|
| 14 | import rule from '../../../src/rules/aria-role';
|
|---|
| 15 |
|
|---|
| 16 | // -----------------------------------------------------------------------------
|
|---|
| 17 | // Tests
|
|---|
| 18 | // -----------------------------------------------------------------------------
|
|---|
| 19 |
|
|---|
| 20 | const ruleTester = new RuleTester();
|
|---|
| 21 |
|
|---|
| 22 | const errorMessage = {
|
|---|
| 23 | message: 'Elements with ARIA roles must use a valid, non-abstract ARIA role.',
|
|---|
| 24 | type: 'JSXAttribute',
|
|---|
| 25 | };
|
|---|
| 26 |
|
|---|
| 27 | const roleKeys = roles.keys();
|
|---|
| 28 |
|
|---|
| 29 | const validRoles = roleKeys.filter((role) => roles.get(role).abstract === false);
|
|---|
| 30 | const invalidRoles = roleKeys.filter((role) => roles.get(role).abstract === true);
|
|---|
| 31 |
|
|---|
| 32 | const createTests = (roleNames) => roleNames.map((role) => ({
|
|---|
| 33 | code: `<div role="${role.toLowerCase()}" />`,
|
|---|
| 34 | }));
|
|---|
| 35 |
|
|---|
| 36 | const validTests = createTests(validRoles);
|
|---|
| 37 | const invalidTests = createTests(invalidRoles).map((test) => {
|
|---|
| 38 | const invalidTest = { ...test };
|
|---|
| 39 | invalidTest.errors = [errorMessage];
|
|---|
| 40 | return invalidTest;
|
|---|
| 41 | });
|
|---|
| 42 |
|
|---|
| 43 | const allowedInvalidRoles = [{
|
|---|
| 44 | allowedInvalidRoles: ['invalid-role', 'other-invalid-role'],
|
|---|
| 45 | }];
|
|---|
| 46 |
|
|---|
| 47 | const ignoreNonDOMSchema = [{
|
|---|
| 48 | ignoreNonDOM: true,
|
|---|
| 49 | }];
|
|---|
| 50 |
|
|---|
| 51 | const customDivSettings = {
|
|---|
| 52 | 'jsx-a11y': {
|
|---|
| 53 | polymorphicPropName: 'asChild',
|
|---|
| 54 | components: {
|
|---|
| 55 | Div: 'div',
|
|---|
| 56 | },
|
|---|
| 57 | },
|
|---|
| 58 | };
|
|---|
| 59 |
|
|---|
| 60 | ruleTester.run('aria-role', rule, {
|
|---|
| 61 | valid: parsers.all([].concat(
|
|---|
| 62 | // Variables should pass, as we are only testing literals.
|
|---|
| 63 | { code: '<div />' },
|
|---|
| 64 | { code: '<div></div>' },
|
|---|
| 65 | { code: '<div role={role} />' },
|
|---|
| 66 | { code: '<div role={role || "button"} />' },
|
|---|
| 67 | { code: '<div role={role || "foobar"} />' },
|
|---|
| 68 | { code: '<div role="tabpanel row" />' },
|
|---|
| 69 | { code: '<div role="switch" />' },
|
|---|
| 70 | { code: '<div role="doc-abstract" />' },
|
|---|
| 71 | { code: '<div role="doc-appendix doc-bibliography" />' },
|
|---|
| 72 | { code: '<Bar baz />' },
|
|---|
| 73 | { code: '<img role="invalid-role" />', options: allowedInvalidRoles },
|
|---|
| 74 | { code: '<img role="invalid-role tabpanel" />', options: allowedInvalidRoles },
|
|---|
| 75 | { code: '<img role="invalid-role other-invalid-role" />', options: allowedInvalidRoles },
|
|---|
| 76 | { code: '<Foo role="bar" />', options: ignoreNonDOMSchema },
|
|---|
| 77 | { code: '<fakeDOM role="bar" />', options: ignoreNonDOMSchema },
|
|---|
| 78 | { code: '<img role="presentation" />', options: ignoreNonDOMSchema },
|
|---|
| 79 | {
|
|---|
| 80 | code: '<Div role="button" />',
|
|---|
| 81 | settings: customDivSettings,
|
|---|
| 82 | },
|
|---|
| 83 | {
|
|---|
| 84 | code: '<Box asChild="div" role="button" />',
|
|---|
| 85 | settings: customDivSettings,
|
|---|
| 86 | },
|
|---|
| 87 | { code: '<svg role="graphics-document document" />' },
|
|---|
| 88 | { code: '<svg role="img" />' },
|
|---|
| 89 | )).concat(validTests).map(parserOptionsMapper),
|
|---|
| 90 |
|
|---|
| 91 | invalid: parsers.all([].concat(
|
|---|
| 92 | { code: '<div role="foobar" />', errors: [errorMessage] },
|
|---|
| 93 | { code: '<div role="datepicker"></div>', errors: [errorMessage] },
|
|---|
| 94 | { code: '<div role="range"></div>', errors: [errorMessage] },
|
|---|
| 95 | { code: '<div role="Button"></div>', errors: [errorMessage] },
|
|---|
| 96 | { code: '<div role=""></div>', errors: [errorMessage] },
|
|---|
| 97 | { code: '<div role="tabpanel row foobar"></div>', errors: [errorMessage] },
|
|---|
| 98 | { code: '<div role="tabpanel row range"></div>', errors: [errorMessage] },
|
|---|
| 99 | { code: '<div role="doc-endnotes range"></div>', errors: [errorMessage] },
|
|---|
| 100 | { code: '<div role />', errors: [errorMessage] },
|
|---|
| 101 | { code: '<div role="unknown-invalid-role" />', errors: [errorMessage], options: allowedInvalidRoles },
|
|---|
| 102 | { code: '<div role={null}></div>', errors: [errorMessage] },
|
|---|
| 103 | { code: '<Foo role="datepicker" />', errors: [errorMessage] },
|
|---|
| 104 | { code: '<Foo role="Button" />', errors: [errorMessage] },
|
|---|
| 105 | { code: '<Div role="Button" />', errors: [errorMessage], settings: customDivSettings },
|
|---|
| 106 | {
|
|---|
| 107 | code: '<Div role="Button" />',
|
|---|
| 108 | errors: [errorMessage],
|
|---|
| 109 | options: ignoreNonDOMSchema,
|
|---|
| 110 | settings: customDivSettings,
|
|---|
| 111 | },
|
|---|
| 112 | {
|
|---|
| 113 | code: '<Box asChild="div" role="Button" />',
|
|---|
| 114 | settings: customDivSettings,
|
|---|
| 115 | errors: [errorMessage],
|
|---|
| 116 | },
|
|---|
| 117 | )).concat(invalidTests).map(parserOptionsMapper),
|
|---|
| 118 | });
|
|---|