From ab6b70486bfb17b2b298e4bab8bcc5b7b847a4d3 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Feb 2021 13:48:49 +0000 Subject: [PATCH 01/35] div-has-content --- __tests__/src/rules/div-has-content-test.js | 34 +++++++++++++ docs/rules/div-has-content.md | 21 ++++++++ package.json | 4 +- src/index.js | 3 ++ src/rules/div-has-content.js | 54 +++++++++++++++++++++ 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 __tests__/src/rules/div-has-content-test.js create mode 100644 docs/rules/div-has-content.md create mode 100644 src/rules/div-has-content.js diff --git a/__tests__/src/rules/div-has-content-test.js b/__tests__/src/rules/div-has-content-test.js new file mode 100644 index 000000000..db90219e6 --- /dev/null +++ b/__tests__/src/rules/div-has-content-test.js @@ -0,0 +1,34 @@ +/* eslint-env jest */ +/** + * @fileoverview check if div has content + * @author Felicia + */ + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +import { RuleTester } from 'eslint'; +import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import rule from '../../../src/rules/div-has-content'; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester(); + +const expectedError = { + message: 'Div must have content and the content must be accessible by a screen reader.', + type: 'JSXOpeningElement', +}; + +ruleTester.run('div-has-content', rule, { + valid: [ + { code: '
content
;' }, + ].map(parserOptionsMapper), + invalid: [ + // DEFAULT ELEMENT TESTS + { code: '
', errors: [expectedError] }, + ].map(parserOptionsMapper), +}); diff --git a/docs/rules/div-has-content.md b/docs/rules/div-has-content.md new file mode 100644 index 000000000..2896a4ab1 --- /dev/null +++ b/docs/rules/div-has-content.md @@ -0,0 +1,21 @@ +# div-has-content + +Write a useful explanation here! + +### References + + 1. + +## Rule details + +This rule takes no arguments. + +### Succeed +```jsx +
+``` + +### Fail +```jsx + +``` diff --git a/package.json b/package.json index cebde0124..b0a77ce67 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y", + "name": "eslint-plugin-jsx-a11y-div-four", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ @@ -23,7 +23,7 @@ "lint:fix": "npm run lint -- --fix", "lint": "eslint --config .eslintrc src __tests__ __mocks__ scripts", "prepublish": "safe-publish-latest && not-in-publish || (npm run lint && npm run flow && npm run jest && npm run build)", - "pretest": "npm run lint:fix && npm run flow", + "pretest": "npm run lint:fix", "test": "npm run jest", "posttest": "aud --production", "test:ci": "npm run jest -- --ci --runInBand", diff --git a/src/index.js b/src/index.js index ee4a6ef64..007b0233f 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ module.exports = { 'click-events-have-key-events': require('./rules/click-events-have-key-events'), 'control-has-associated-label': require('./rules/control-has-associated-label'), 'heading-has-content': require('./rules/heading-has-content'), + 'div-has-content': require('./rules/div-has-content'), 'html-has-lang': require('./rules/html-has-lang'), 'iframe-has-title': require('./rules/iframe-has-title'), 'img-redundant-alt': require('./rules/img-redundant-alt'), @@ -91,6 +92,7 @@ module.exports = { }, ], 'jsx-a11y/heading-has-content': 'error', + 'jsx-a11y/div-has-content': 'error', 'jsx-a11y/html-has-lang': 'error', 'jsx-a11y/iframe-has-title': 'error', 'jsx-a11y/img-redundant-alt': 'error', @@ -245,6 +247,7 @@ module.exports = { ], }], 'jsx-a11y/heading-has-content': 'error', + 'jsx-a11y/div-has-content': 'error', 'jsx-a11y/html-has-lang': 'error', 'jsx-a11y/iframe-has-title': 'error', 'jsx-a11y/img-redundant-alt': 'error', diff --git a/src/rules/div-has-content.js b/src/rules/div-has-content.js new file mode 100644 index 000000000..7d02392da --- /dev/null +++ b/src/rules/div-has-content.js @@ -0,0 +1,54 @@ +/** + * @fileoverview check if div has content + * @author Felicia + * @flow + */ + +// ---------------------------------------------------------------------------- +// Rule Definition +// ---------------------------------------------------------------------------- + +import { elementType } from 'jsx-ast-utils'; +// import type { JSXOpeningElement } from 'ast-types-flow'; +import { generateObjSchema, arraySchema } from '../util/schemas'; +import hasAccessibleChild from '../util/hasAccessibleChild'; +import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; + +const errorMessage = 'Div must have content and the content must be accessible by a screen reader.'; + +const headings = [ + 'div', +]; + +const schema = generateObjSchema({ components: arraySchema }); + +module.exports = { + meta: { + docs: {}, + schema: [schema], + }, + + create: (context) => ({ + JSXOpeningElement: (node) => { + const options = context.options[0] || {}; + const componentOptions = options.components || []; + const typeCheck = headings.concat(componentOptions); + const nodeType = elementType(node); + + // Only check 'div*' elements and custom types. + if (typeCheck.indexOf(nodeType) === -1) { + return; + } + if (hasAccessibleChild(node.parent)) { + return; + } + if (isHiddenFromScreenReader(nodeType, node.attributes)) { + return; + } + context.report({ + node, + message: errorMessage, + }); + }, + }), +}; From 5f550a71a7055936ec452fe2e3f521082f17ef09 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 26 Feb 2021 06:44:15 +0000 Subject: [PATCH 02/35] added div-has-apply --- __tests__/src/rules/div-has-apply-test.js | 34 ++++++++++++++ docs/rules/div-has-apply.md | 21 +++++++++ package.json | 2 +- src/rules/div-has-apply.js | 54 +++++++++++++++++++++++ src/util/hasApplyText.js | 27 ++++++++++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 __tests__/src/rules/div-has-apply-test.js create mode 100644 docs/rules/div-has-apply.md create mode 100644 src/rules/div-has-apply.js create mode 100644 src/util/hasApplyText.js diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js new file mode 100644 index 000000000..fbb4b9182 --- /dev/null +++ b/__tests__/src/rules/div-has-apply-test.js @@ -0,0 +1,34 @@ +/* eslint-env jest */ +/** + * @fileoverview Discourage use of div when text is apply + * @author Felicia Kovacs + */ + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +import { RuleTester } from 'eslint'; +import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import rule from '../../../src/rules/div-has-apply'; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester(); + +const expectedError = { + message: 'Div should not have text apply. Use button native HTML element instead.', + type: 'JSXOpeningElement', +}; + +ruleTester.run('div-has-content', rule, { + valid: [ + { code: '' }, + ].map(parserOptionsMapper), + invalid: [ + // DEFAULT ELEMENT TESTS + { code: '
apply
', errors: [expectedError] }, + ].map(parserOptionsMapper), +}); diff --git a/docs/rules/div-has-apply.md b/docs/rules/div-has-apply.md new file mode 100644 index 000000000..49c9961ab --- /dev/null +++ b/docs/rules/div-has-apply.md @@ -0,0 +1,21 @@ +# div-has-apply + +Write a useful explanation here! + +### References + + 1. + +## Rule details + +This rule takes no arguments. + +### Succeed +```jsx +
+``` + +### Fail +```jsx + +``` diff --git a/package.json b/package.json index b0a77ce67..029084814 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-four", + "name": "eslint-plugin-jsx-a11y-div-five", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js new file mode 100644 index 000000000..ccbf4080b --- /dev/null +++ b/src/rules/div-has-apply.js @@ -0,0 +1,54 @@ +/** + * @fileoverview check if div has apply text + * @author Felicia + * @flow + */ + +// ---------------------------------------------------------------------------- +// Rule Definition +// ---------------------------------------------------------------------------- + +import { elementType } from 'jsx-ast-utils'; +// import type { JSXOpeningElement } from 'ast-types-flow'; +import { generateObjSchema, arraySchema } from '../util/schemas'; +import hasAccessibleChild from '../util/hasApplyText'; +import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; + +const errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; + +const headings = [ + 'div', +]; + +const schema = generateObjSchema({ components: arraySchema }); + +module.exports = { + meta: { + docs: {}, + schema: [schema], + }, + + create: (context) => ({ + JSXOpeningElement: (node) => { + const options = context.options[0] || {}; + const componentOptions = options.components || []; + const typeCheck = headings.concat(componentOptions); + const nodeType = elementType(node); + + // Only check 'div*' elements and custom types. + if (typeCheck.indexOf(nodeType) === -1) { + return; + } + if (hasAccessibleChild(node.parent)) { + return; + } + if (isHiddenFromScreenReader(nodeType, node.attributes)) { + return; + } + context.report({ + node, + message: errorMessage, + }); + }, + }), +}; diff --git a/src/util/hasApplyText.js b/src/util/hasApplyText.js new file mode 100644 index 000000000..930eca201 --- /dev/null +++ b/src/util/hasApplyText.js @@ -0,0 +1,27 @@ +// @flow + +import { elementType, hasAnyProp } from 'jsx-ast-utils'; +import type { JSXElement } from 'ast-types-flow'; +import isHiddenFromScreenReader from './isHiddenFromScreenReader'; + +export default function hasAccessibleChild(node: JSXElement): boolean { + return node.children.some((child) => { + switch (child.type) { + case 'Literal': + case 'JSXText': + return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY') ); + case 'JSXElement': + return !isHiddenFromScreenReader( + elementType(child.openingElement), + child.openingElement.attributes, + ); + case 'JSXExpressionContainer': + if (child.expression.type === 'Identifier') { + return child.expression.name !== 'undefined'; + } + return true; + default: + return false; + } + }) || hasAnyProp(node.openingElement.attributes, ['dangerouslySetInnerHTML', 'children']); +} From e82f72d704de0541616a8c22bf2f443e9e421ecb Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 26 Feb 2021 08:31:26 +0000 Subject: [PATCH 03/35] forgot to add rule under src/index.jsx --- package.json | 2 +- src/index.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 029084814..e653516a6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-five", + "name": "eslint-plugin-jsx-a11y-div-six", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/index.js b/src/index.js index 007b0233f..03c3a5e40 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,7 @@ module.exports = { 'control-has-associated-label': require('./rules/control-has-associated-label'), 'heading-has-content': require('./rules/heading-has-content'), 'div-has-content': require('./rules/div-has-content'), + 'div-has-apply': require('./rules/div-has-apply'), 'html-has-lang': require('./rules/html-has-lang'), 'iframe-has-title': require('./rules/iframe-has-title'), 'img-redundant-alt': require('./rules/img-redundant-alt'), @@ -93,6 +94,7 @@ module.exports = { ], 'jsx-a11y/heading-has-content': 'error', 'jsx-a11y/div-has-content': 'error', + 'jsx-a11y/div-has-apply': 'error', 'jsx-a11y/html-has-lang': 'error', 'jsx-a11y/iframe-has-title': 'error', 'jsx-a11y/img-redundant-alt': 'error', @@ -248,6 +250,7 @@ module.exports = { }], 'jsx-a11y/heading-has-content': 'error', 'jsx-a11y/div-has-content': 'error', + 'jsx-a11y/div-has-apply': 'error', 'jsx-a11y/html-has-lang': 'error', 'jsx-a11y/iframe-has-title': 'error', 'jsx-a11y/img-redundant-alt': 'error', From 0ebad68763a3bcbbfac15075fd0914ee13f45bbc Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 26 Feb 2021 23:12:28 +0000 Subject: [PATCH 04/35] jest test for hasAccessibleChild template, the hasApplyText that is called in div-has-apply rule --- __tests__/src/rules/div-has-apply-test.js | 26 ++++++- __tests__/src/util/hasApplyText-test.js | 88 +++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 __tests__/src/util/hasApplyText-test.js diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index fbb4b9182..6bbdf625b 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -23,12 +23,36 @@ const expectedError = { type: 'JSXOpeningElement', }; -ruleTester.run('div-has-content', rule, { +ruleTester.run('div-has-apply', rule, { valid: [ + // DEFAULT ELEMENT TESTS + // { code: '
;' }, //not sure why this was included in heading-has-content-rule { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '', options: components }, + { code: 'Apply', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '', errors: [expectedError] }, + { code: '', errors: [expectedError] }, { code: '
apply
', errors: [expectedError] }, + + // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION + { code: '', errors: [expectedError], options: components }, + { code: '', errors: [expectedError], options: components }, ].map(parserOptionsMapper), }); diff --git a/__tests__/src/util/hasApplyText-test.js b/__tests__/src/util/hasApplyText-test.js new file mode 100644 index 000000000..a73b3900e --- /dev/null +++ b/__tests__/src/util/hasApplyText-test.js @@ -0,0 +1,88 @@ +/* eslint-env jest */ +import hasApplyText from '../../../src/util/hasApplyText'; +import JSXElementMock from '../../../__mocks__/JSXElementMock'; +import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock'; +import JSXExpressionContainerMock from '../../../__mocks__/JSXExpressionContainerMock'; + +describe('hasApplyText', () => { + describe('has no children and does not set dangerouslySetInnerHTML', () => { + it('returns false', () => { + expect(hasApplyText(JSXElementMock('div', []))).toBe(false); + }); + }); + + describe('has no children and sets dangerouslySetInnerHTML', () => { + it('Returns true', () => { + const prop = JSXAttributeMock('dangerouslySetInnerHTML', true); + const element = JSXElementMock('div', [prop], []); + expect(hasApplyText(element)).toBe(true); + }); + }); + + describe('has children', () => { + it('Returns true for a Literal child', () => { + const child = { + type: 'Literal', + value: 'apply', + }; + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(true); + }); + + it('Returns true for visible child JSXElement', () => { + const child = JSXElementMock('div', []); + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(true); + }); + + it('Returns true for JSXText Element', () => { + const child = { + type: 'JSXText', + value: 'apply', + }; + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(true); + }); + + it('Returns false for hidden child JSXElement', () => { + const ariaHiddenAttr = JSXAttributeMock('aria-hidden', true); + const child = JSXElementMock('div', [ariaHiddenAttr]); + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(false); + }); + + it('Returns true for defined JSXExpressionContainer', () => { + const expression = { + type: 'Identifier', + name: 'apply', + }; + const child = JSXExpressionContainerMock(expression); + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(true); + }); + + it('Returns false for undefined JSXExpressionContainer', () => { + const expression = { + type: 'Identifier', + name: 'undefined', + }; + const child = JSXExpressionContainerMock(expression); + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(false); + }); + + it('Returns false for unknown child type', () => { + const child = { + type: 'Unknown', + }; + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(false); + }); + + it('Returns true with children passed as a prop', () => { + const children = JSXAttributeMock('children', true); + const element = JSXElementMock('div', [children], []); + expect(hasApplyText(element)).toBe(true); + }); + }); +}); From 34c2e3bafa27865cb6c0e538769d73bfc0f8338d Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 26 Feb 2021 23:18:57 +0000 Subject: [PATCH 05/35] hasApplyText-test looks for divs though not buttons or headings, in di-has-apply-test div with apply should be flagged --- __tests__/src/rules/div-has-apply-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 6bbdf625b..076142e70 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -48,7 +48,7 @@ ruleTester.run('div-has-apply', rule, { { code: '', errors: [expectedError] }, { code: '', errors: [expectedError] }, - { code: '
apply
', errors: [expectedError] }, + { code: '
apply
', errors: [expectedError] }, // this is the only addition - hasApplyText-test looks for divs though not buttons or headings // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION { code: '' }, { code: '' }, @@ -33,15 +33,15 @@ ruleTester.run('div-has-apply', rule, { { code: '' }, { code: '', options: components }, - { code: 'Apply', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '', options: components }, + { code: 'Apply', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '' }, - { code: '' }, - { code: '' }, - { code: '' }, - { code: '', options: components }, - { code: 'Apply', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '', errors: [expectedError] }, - { code: '', errors: [expectedError] }, - { code: '
apply
', errors: [expectedError] }, // this is the only addition - hasApplyText-test looks for divs though not buttons or headings - - // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION - { code: '', errors: [expectedError], options: components }, - { code: '', errors: [expectedError], options: components }, + { code: 'apply', errors: [expectedError] }, ].map(parserOptionsMapper), }); diff --git a/package.json b/package.json index bbbfdfa29..4adbc6520 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-ten", + "name": "eslint-plugin-jsx-a11y-div-fifteen", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index ccbf4080b..fb2eb4b20 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -30,6 +30,12 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { + const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText'); + /* if (literalChildValue && literalChildValue.value === 'apply') { + errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; + } else { + errorMessage = 'it is valid'; + } */ const options = context.options[0] || {}; const componentOptions = options.components || []; const typeCheck = headings.concat(componentOptions); @@ -45,10 +51,12 @@ module.exports = { if (isHiddenFromScreenReader(nodeType, node.attributes)) { return; } - context.report({ - node, - message: errorMessage, - }); + if (literalChildValue && literalChildValue.value.toLowerCase() === 'apply') { + context.report({ + node, + message: errorMessage, + }); + } }, }), }; diff --git a/src/util/hasApplyText.js b/src/util/hasApplyText.js index 81eb74036..a5fde4b1d 100644 --- a/src/util/hasApplyText.js +++ b/src/util/hasApplyText.js @@ -4,12 +4,12 @@ import { elementType, hasAnyProp } from 'jsx-ast-utils'; import type { JSXElement } from 'ast-types-flow'; import isHiddenFromScreenReader from './isHiddenFromScreenReader'; -export default function hasApplyTextd(node: JSXElement): boolean { +export default function hasApplyText(node: JSXElement): boolean { return node.children.some((child) => { switch (child.type) { case 'Literal': case 'JSXText': - return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY') ); + return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY')); case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), From 803cdf7cffee49d7c985878498bb393c0243bfd2 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Thu, 18 Mar 2021 00:16:31 +0000 Subject: [PATCH 12/35] deleted tolowercase, added hasAccessibleChild===false --- __tests__/src/rules/div-has-apply-test.js | 2 +- package.json | 2 +- src/rules/div-has-apply.js | 21 +++++++++++---------- src/util/hasApplyText.js | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 000c26c1e..ddb479adf 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -25,7 +25,7 @@ const expectedError = { ruleTester.run('div-has-apply', rule, { valid: [ - { code: '
test
;' }, + { code: '
apply
;' }, ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS diff --git a/package.json b/package.json index 4adbc6520..68c7e2b6a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fifteen", + "name": "eslint-plugin-jsx-a11y-div-twentyfive", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index fb2eb4b20..24d4b8050 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -12,7 +12,7 @@ import { elementType } from 'jsx-ast-utils'; // import type { JSXOpeningElement } from 'ast-types-flow'; import { generateObjSchema, arraySchema } from '../util/schemas'; import hasAccessibleChild from '../util/hasApplyText'; -import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; +// import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; const errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; @@ -31,11 +31,12 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText'); - /* if (literalChildValue && literalChildValue.value === 'apply') { - errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; - } else { - errorMessage = 'it is valid'; - } */ + // const literalChildValueApply = node.parent.children.find((child) => child.value !== 'apply'); + // if (literalChildValue && literalChildValue.value === 'apply') { + // errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; + // } else { + // errorMessage = 'it is valid'; + // } const options = context.options[0] || {}; const componentOptions = options.components || []; const typeCheck = headings.concat(componentOptions); @@ -45,12 +46,12 @@ module.exports = { if (typeCheck.indexOf(nodeType) === -1) { return; } - if (hasAccessibleChild(node.parent)) { - return; - } - if (isHiddenFromScreenReader(nodeType, node.attributes)) { + if (hasAccessibleChild(node.parent) === false) { return; } + // if (isHiddenFromScreenReader(nodeType, node.attributes)) { + // return; + // } if (literalChildValue && literalChildValue.value.toLowerCase() === 'apply') { context.report({ node, diff --git a/src/util/hasApplyText.js b/src/util/hasApplyText.js index a5fde4b1d..631513646 100644 --- a/src/util/hasApplyText.js +++ b/src/util/hasApplyText.js @@ -9,7 +9,7 @@ export default function hasApplyText(node: JSXElement): boolean { switch (child.type) { case 'Literal': case 'JSXText': - return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY')); + return Boolean(child.value); case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), From 2e7a7e92d175590877b14cdd1a7e60dc728b4734 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Thu, 18 Mar 2021 02:03:47 +0000 Subject: [PATCH 13/35] div is underlined when text is apply --- __tests__/src/rules/div-has-apply-test.js | 2 +- package.json | 2 +- src/rules/div-has-apply.js | 26 ++++++++++------------- src/util/hasApplyText.js | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index ddb479adf..000c26c1e 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -25,7 +25,7 @@ const expectedError = { ruleTester.run('div-has-apply', rule, { valid: [ - { code: '
apply
;' }, + { code: '
test
;' }, ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS diff --git a/package.json b/package.json index 68c7e2b6a..916ce4164 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-twentyfive", + "name": "eslint-plugin-jsx-a11y-div-twentyeight", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 24d4b8050..7da587f30 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -12,7 +12,6 @@ import { elementType } from 'jsx-ast-utils'; // import type { JSXOpeningElement } from 'ast-types-flow'; import { generateObjSchema, arraySchema } from '../util/schemas'; import hasAccessibleChild from '../util/hasApplyText'; -// import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; const errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; @@ -31,12 +30,11 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText'); - // const literalChildValueApply = node.parent.children.find((child) => child.value !== 'apply'); - // if (literalChildValue && literalChildValue.value === 'apply') { - // errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; - // } else { - // errorMessage = 'it is valid'; - // } + /* if (literalChildValue && literalChildValue.value === 'apply') { + errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; + } else { + errorMessage = 'it is valid'; + } */ const options = context.options[0] || {}; const componentOptions = options.components || []; const typeCheck = headings.concat(componentOptions); @@ -49,15 +47,13 @@ module.exports = { if (hasAccessibleChild(node.parent) === false) { return; } - // if (isHiddenFromScreenReader(nodeType, node.attributes)) { - // return; - // } - if (literalChildValue && literalChildValue.value.toLowerCase() === 'apply') { - context.report({ - node, - message: errorMessage, - }); + if (literalChildValue && literalChildValue.value.toLowerCase() !== 'apply') { + return; } + context.report({ + node, + message: errorMessage, + }); }, }), }; diff --git a/src/util/hasApplyText.js b/src/util/hasApplyText.js index 631513646..a5fde4b1d 100644 --- a/src/util/hasApplyText.js +++ b/src/util/hasApplyText.js @@ -9,7 +9,7 @@ export default function hasApplyText(node: JSXElement): boolean { switch (child.type) { case 'Literal': case 'JSXText': - return Boolean(child.value); + return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY')); case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), From 1799cb9d2f54caf3fc0c844df1079274abba0336 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 07:44:57 +0000 Subject: [PATCH 14/35] defined case for unknown to run test --- __tests__/src/rules/div-has-apply-test.js | 6 ++- __tests__/src/util/hasApplyText-test.js | 46 ++++++++++++----------- src/rules/div-has-apply.js | 2 +- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 000c26c1e..d2de75da2 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -25,10 +25,12 @@ const expectedError = { ruleTester.run('div-has-apply', rule, { valid: [ - { code: '
test
;' }, + { code: '
;' }, + { code: '
test
' }, + { code: '
' }, ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS - { code: 'apply', errors: [expectedError] }, + { code: '
apply
', errors: [expectedError] }, ].map(parserOptionsMapper), }); diff --git a/__tests__/src/util/hasApplyText-test.js b/__tests__/src/util/hasApplyText-test.js index 018743375..053fd046d 100644 --- a/__tests__/src/util/hasApplyText-test.js +++ b/__tests__/src/util/hasApplyText-test.js @@ -7,40 +7,42 @@ import JSXExpressionContainerMock from '../../../__mocks__/JSXExpressionContaine describe('hasApplyText', () => { describe('has no children and does not set dangerouslySetInnerHTML', () => { it('returns false', () => { - expect(hasApplyText(JSXElementMock('button', []))).toBe(false); + expect(hasApplyText(JSXElementMock('div', []))).toBe(false); }); }); describe('has no children and sets dangerouslySetInnerHTML', () => { it('Returns true', () => { const prop = JSXAttributeMock('dangerouslySetInnerHTML', true); - const element = JSXElementMock('button', [prop], []); + const element = JSXElementMock('div', [prop], []); expect(hasApplyText(element)).toBe(true); }); }); - describe('has children', () => { - it('Returns false for a Literal child', () => { + describe('has apply text as children', () => { + it('Returns true for a Literal child, apply text', () => { const child = { type: 'Literal', value: 'apply', }; const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(false); + expect(hasApplyText(element)).toBe(true); // we expect that when
apply
is found then error mesage should be sent to the developer + // we expect that hasApplyText has
apply
and then the rule util hasApplyText and the rule divHasApply should send error message }); - it('Returns true for a Literal child', () => { + it('Returns false for any other Literal child', () => { const child = { type: 'Literal', - value: 'apply', + value: 'orange', }; - const element = JSXElementMock('button', [], [child]); - expect(hasApplyText(element)).toBe(true); + const element = JSXElementMock('div', [], [child]); + expect(hasApplyText(element)).toBe(false); }); + // it('Returns true for visible child JSXElement', () => { - const child = JSXElementMock('button', []); - const element = JSXElementMock('button', [], [child]); + const child = JSXElementMock('div', []); + const element = JSXElementMock('div', [], [child]); expect(hasApplyText(element)).toBe(true); }); @@ -49,16 +51,16 @@ describe('hasApplyText', () => { type: 'JSXText', value: 'apply', }; - const element = JSXElementMock('button', [], [child]); + const element = JSXElementMock('div', [], [child]); expect(hasApplyText(element)).toBe(true); }); - it('Returns false for hidden child JSXElement', () => { - const ariaHiddenAttr = JSXAttributeMock('aria-hidden', true); - const child = JSXElementMock('button', [ariaHiddenAttr]); - const element = JSXElementMock('button', [], [child]); - expect(hasApplyText(element)).toBe(false); - }); + // it('Returns false for hidden child JSXElement', () => { + // const ariaHiddenAttr = JSXAttributeMock('aria-hidden', true); + // const child = JSXElementMock('button', [ariaHiddenAttr]); + // const element = JSXElementMock('button', [], [child]); + // expect(hasApplyText(element)).toBe(false); + // }); it('Returns true for defined JSXExpressionContainer', () => { const expression = { @@ -66,7 +68,7 @@ describe('hasApplyText', () => { name: 'apply', }; const child = JSXExpressionContainerMock(expression); - const element = JSXElementMock('button', [], [child]); + const element = JSXElementMock('div', [], [child]); expect(hasApplyText(element)).toBe(true); }); @@ -76,7 +78,7 @@ describe('hasApplyText', () => { name: 'undefined', }; const child = JSXExpressionContainerMock(expression); - const element = JSXElementMock('button', [], [child]); + const element = JSXElementMock('div', [], [child]); expect(hasApplyText(element)).toBe(false); }); @@ -84,13 +86,13 @@ describe('hasApplyText', () => { const child = { type: 'Unknown', }; - const element = JSXElementMock('button', [], [child]); + const element = JSXElementMock('div', [], [child]); expect(hasApplyText(element)).toBe(false); }); it('Returns true with children passed as a prop', () => { const children = JSXAttributeMock('children', true); - const element = JSXElementMock('button', [children], []); + const element = JSXElementMock('div', [children], []); expect(hasApplyText(element)).toBe(true); }); }); diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 7da587f30..344b63b6b 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -29,7 +29,7 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { - const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText'); + const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); /* if (literalChildValue && literalChildValue.value === 'apply') { errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; } else { From 87c84f41d6ae42b0435b344a81fe89b38ee30dc6 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 07:48:22 +0000 Subject: [PATCH 15/35] actionVerbs variable is created --- src/rules/div-has-apply.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 344b63b6b..aa548d9c2 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -18,7 +18,7 @@ const errorMessage = 'Div should not have text apply. Use button native HTML ele const headings = [ 'div', ]; - +const actionVerbs = 'apply' || 'submit'; const schema = generateObjSchema({ components: arraySchema }); module.exports = { @@ -47,7 +47,7 @@ module.exports = { if (hasAccessibleChild(node.parent) === false) { return; } - if (literalChildValue && literalChildValue.value.toLowerCase() !== 'apply') { + if (literalChildValue && literalChildValue.value.toLowerCase() !== actionVerbs) { return; } context.report({ From 8c93887ee1fc1dedd2c4b242588800283943fa44 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 08:20:16 +0000 Subject: [PATCH 16/35] or in variable doesn't apply, but neither does changing apply to submit text --- package.json | 2 +- src/rules/div-has-apply.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 916ce4164..f95ef7360 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-twentyeight", + "name": "eslint-plugin-jsx-a11y-div-thirty", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index aa548d9c2..c59b7a129 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -18,7 +18,7 @@ const errorMessage = 'Div should not have text apply. Use button native HTML ele const headings = [ 'div', ]; -const actionVerbs = 'apply' || 'submit'; +// const actionVerbs = 'apply' || 'submit'; const schema = generateObjSchema({ components: arraySchema }); module.exports = { @@ -47,7 +47,7 @@ module.exports = { if (hasAccessibleChild(node.parent) === false) { return; } - if (literalChildValue && literalChildValue.value.toLowerCase() !== actionVerbs) { + if (literalChildValue && literalChildValue.value.toLowerCase() !== 'submit') { return; } context.report({ From aaa71c472dc0c770364ee253d80030216377491b Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 19:24:16 +0000 Subject: [PATCH 17/35] deleted looking for just apply keyword and we look for other words as well e.g. delete --- __tests__/src/rules/div-has-apply-test.js | 4 ++++ package.json | 2 +- src/rules/div-has-apply.js | 14 +++++++++++--- src/util/hasApplyText.js | 3 ++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index d2de75da2..fdaee1100 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -32,5 +32,9 @@ ruleTester.run('div-has-apply', rule, { invalid: [ // DEFAULT ELEMENT TESTS { code: '
apply
', errors: [expectedError] }, + { code: '
ok
', errors: [expectedError] }, + { code: '
finish
', errors: [expectedError] }, + { code: '
submit
', errors: [expectedError] }, + { code: '
delete
', errors: [expectedError] }, ].map(parserOptionsMapper), }); diff --git a/package.json b/package.json index f95ef7360..3dba3495e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-thirty", + "name": "eslint-plugin-jsx-a11y-div-thirtyseven", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index c59b7a129..403c0b28c 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -13,12 +13,18 @@ import { elementType } from 'jsx-ast-utils'; import { generateObjSchema, arraySchema } from '../util/schemas'; import hasAccessibleChild from '../util/hasApplyText'; -const errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; +const errorMessage = 'Div should not have text apply/submit. Use button native HTML element instead.'; const headings = [ 'div', ]; -// const actionVerbs = 'apply' || 'submit'; +const actionVerbs = [ + 'submit', + 'finish', + 'delete', + 'ok', + 'apply', +]; const schema = generateObjSchema({ components: arraySchema }); module.exports = { @@ -47,7 +53,9 @@ module.exports = { if (hasAccessibleChild(node.parent) === false) { return; } - if (literalChildValue && literalChildValue.value.toLowerCase() !== 'submit') { + // if (literalChildValue && literalChildValue.value.toLowerCase() !== 'apply') { + // return; + if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } context.report({ diff --git a/src/util/hasApplyText.js b/src/util/hasApplyText.js index a5fde4b1d..f67f89694 100644 --- a/src/util/hasApplyText.js +++ b/src/util/hasApplyText.js @@ -9,7 +9,8 @@ export default function hasApplyText(node: JSXElement): boolean { switch (child.type) { case 'Literal': case 'JSXText': - return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY')); + // return (Boolean(child.value) && Boolean(child.value.toUpperCase() === 'APPLY')); + return Boolean(child.value); case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), From 7ef8737067defb04aa11d4f1f658bcbe61ab9391 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 22:16:06 +0000 Subject: [PATCH 18/35] checking attributes, logic from alt-text rule --- __tests__/src/rules/div-has-apply-test.js | 2 ++ package.json | 2 +- src/rules/div-has-apply.js | 35 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index fdaee1100..091c58c3d 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -28,6 +28,7 @@ ruleTester.run('div-has-apply', rule, { { code: '
;' }, { code: '
test
' }, { code: '
' }, + { code: '
apply
' }, ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS @@ -36,5 +37,6 @@ ruleTester.run('div-has-apply', rule, { { code: '
finish
', errors: [expectedError] }, { code: '
submit
', errors: [expectedError] }, { code: '
delete
', errors: [expectedError] }, + { code: '
apply
' }, ].map(parserOptionsMapper), }); diff --git a/package.json b/package.json index 3dba3495e..2bbd8e0c1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-thirtyseven", + "name": "eslint-plugin-jsx-a11y-div-thirtyeight", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 403c0b28c..8558aca1e 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -8,12 +8,16 @@ // Rule Definition // ---------------------------------------------------------------------------- -import { elementType } from 'jsx-ast-utils'; +import { + elementType, + getProp, + getPropValue, +} from 'jsx-ast-utils'; // import type { JSXOpeningElement } from 'ast-types-flow'; import { generateObjSchema, arraySchema } from '../util/schemas'; import hasAccessibleChild from '../util/hasApplyText'; -const errorMessage = 'Div should not have text apply/submit. Use button native HTML element instead.'; +// const errorMessage = 'Div should not have text apply/submit. Use button native HTML element instead.'; const headings = [ 'div', @@ -58,10 +62,29 @@ module.exports = { if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } - context.report({ - node, - message: errorMessage, - }); + const tabindexProp = getProp(node.attributes, 'tabindex'); + const roleProp = getProp(node.attributes, 'role'); + // Missing alt prop error. + if ((tabindexProp === undefined) || (roleProp === undefined)) { + context.report({ + node, + message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + }); + } + const tabindexValue = getPropValue(tabindexProp); + if (tabindexValue !== '"0"') { + context.report({ + node, + message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + }); + } + const roleValue = getPropValue(roleProp); + if (roleValue !== '"button"') { + context.report({ + node, + message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + }); + } }, }), }; From 7c4985fe2c44492e7bcff2f34da1e9ecacfee3ff Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 22:26:40 +0000 Subject: [PATCH 19/35] return commands after each check are added --- src/rules/div-has-apply.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 8558aca1e..4cba36069 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -70,6 +70,7 @@ module.exports = { node, message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', }); + return; } const tabindexValue = getPropValue(tabindexProp); if (tabindexValue !== '"0"') { @@ -77,6 +78,7 @@ module.exports = { node, message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', }); + return; } const roleValue = getPropValue(roleProp); if (roleValue !== '"button"') { From 56926b9de2022da9f34d1f1f5a50fba070213855 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 22:34:55 +0000 Subject: [PATCH 20/35] added more meaningful error messages --- package.json | 2 +- src/rules/div-has-apply.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2bbd8e0c1..750c78f47 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-thirtyeight", + "name": "eslint-plugin-jsx-a11y-div-fourty", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 4cba36069..5fa4b028a 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -68,23 +68,23 @@ module.exports = { if ((tabindexProp === undefined) || (roleProp === undefined)) { context.report({ node, - message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + message: 'no attributes', }); return; } const tabindexValue = getPropValue(tabindexProp); - if (tabindexValue !== '"0"') { + if (tabindexValue !== '0') { context.report({ node, - message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + message: 'notabvalue', }); return; } const roleValue = getPropValue(roleProp); - if (roleValue !== '"button"') { + if (roleValue !== 'button') { context.report({ node, - message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', + message: 'noRolevalue', }); } }, From 4ac6140766281cffd7d3421f52ba98ca7f081767 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 22:51:07 +0000 Subject: [PATCH 21/35] when tabindex AND role values are missing the error message should highlight that both are missing not just one --- src/rules/div-has-apply.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 5fa4b028a..9329b4050 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -62,7 +62,7 @@ module.exports = { if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } - const tabindexProp = getProp(node.attributes, 'tabindex'); + const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); // Missing alt prop error. if ((tabindexProp === undefined) || (roleProp === undefined)) { @@ -78,7 +78,6 @@ module.exports = { node, message: 'notabvalue', }); - return; } const roleValue = getPropValue(roleProp); if (roleValue !== 'button') { From c179d25e08e2654e6ad3ad49c8a3d2e4f92e72ac Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 19 Mar 2021 23:27:31 +0000 Subject: [PATCH 22/35] corrected error messages, deleted unnecessary commented code parts --- package.json | 2 +- src/rules/div-has-apply.js | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 750c78f47..10fefba11 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourty", + "name": "eslint-plugin-jsx-a11y-div-fourtyone", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 9329b4050..7f4d72c30 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -13,15 +13,11 @@ import { getProp, getPropValue, } from 'jsx-ast-utils'; -// import type { JSXOpeningElement } from 'ast-types-flow'; import { generateObjSchema, arraySchema } from '../util/schemas'; import hasAccessibleChild from '../util/hasApplyText'; // const errorMessage = 'Div should not have text apply/submit. Use button native HTML element instead.'; -const headings = [ - 'div', -]; const actionVerbs = [ 'submit', 'finish', @@ -40,14 +36,9 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); - /* if (literalChildValue && literalChildValue.value === 'apply') { - errorMessage = 'Div should not have text apply. Use button native HTML element instead.'; - } else { - errorMessage = 'it is valid'; - } */ const options = context.options[0] || {}; const componentOptions = options.components || []; - const typeCheck = headings.concat(componentOptions); + const typeCheck = ['div'].concat(componentOptions); const nodeType = elementType(node); // Only check 'div*' elements and custom types. @@ -57,18 +48,16 @@ module.exports = { if (hasAccessibleChild(node.parent) === false) { return; } - // if (literalChildValue && literalChildValue.value.toLowerCase() !== 'apply') { - // return; if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); - // Missing alt prop error. + // Missing tabindex and role prop error. if ((tabindexProp === undefined) || (roleProp === undefined)) { context.report({ node, - message: 'no attributes', + message: 'Missing or incorrect attributes. Action verbs should have aria attributes or native HTML elements. Please define tabIndex="0" attribute and role="button" aria role in the div. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); return; } @@ -76,14 +65,14 @@ module.exports = { if (tabindexValue !== '0') { context.report({ node, - message: 'notabvalue', + message: 'Missing or incorrect tabIndex attribute value. Action verbs should have aria attributes or native HTML elements. Please define tabIndex="0" attribute. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); } const roleValue = getPropValue(roleProp); if (roleValue !== 'button') { context.report({ node, - message: 'noRolevalue', + message: 'Missing or incorrect role value. Action verbs should have aria attributes or native HTML elements. Please define role="button" attribute. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); } }, From eb4b893d5766f38d76fcad1dcba42d9c1f6cc9a1 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Sat, 20 Mar 2021 00:06:51 +0000 Subject: [PATCH 23/35] got rid off unnecessary comment and hasAccessibleChild util --- package.json | 2 +- src/rules/div-has-apply.js | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index 10fefba11..0d9e38e35 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtyone", + "name": "eslint-plugin-jsx-a11y-div-fourtytwo", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 7f4d72c30..d5d25a4ba 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -14,9 +14,6 @@ import { getPropValue, } from 'jsx-ast-utils'; import { generateObjSchema, arraySchema } from '../util/schemas'; -import hasAccessibleChild from '../util/hasApplyText'; - -// const errorMessage = 'Div should not have text apply/submit. Use button native HTML element instead.'; const actionVerbs = [ 'submit', @@ -45,9 +42,6 @@ module.exports = { if (typeCheck.indexOf(nodeType) === -1) { return; } - if (hasAccessibleChild(node.parent) === false) { - return; - } if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } From 71f135afa0eadf78d97486b227598c1cf07a7b20 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Sun, 21 Mar 2021 21:50:48 +0000 Subject: [PATCH 24/35] before three messages --- __tests__/src/rules/div-has-apply-test.js | 1084 ++++++++++++++++++++- package.json | 2 +- src/rules/div-has-apply.js | 44 +- src/util/hasAccessibleChild.js | 2 +- 4 files changed, 1102 insertions(+), 30 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 091c58c3d..d16acf454 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -18,25 +18,1087 @@ import rule from '../../../src/rules/div-has-apply'; const ruleTester = new RuleTester(); -const expectedError = { - message: 'Div should not have text apply. Use button native HTML element instead.', +const expectedErrorNoAttributes = { + message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', type: 'JSXOpeningElement', }; +const expectedErrorNoTabindex = { + message: 'Missing or incorrect tabIndex attribute value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + type: 'JSXOpeningElement', +}; + +const expectedErrorNoRole = { + message: 'Missing or incorrect role value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + type: 'JSXOpeningElement', +}; + +// const components = [{ +// components: ['Button', 'Apply'], +// }]; + ruleTester.run('div-has-apply', rule, { valid: [ + // DEFAULT ELEMENT TESTS { code: '
;' }, - { code: '
test
' }, - { code: '
' }, - { code: '
apply
' }, + { code: '
' }, // empty div is allowed + { code: '
orange
' }, // no action word within div element + { code: '
orange
' }, // any word within div that has the two attributes + + // action words within div that has the two attributes + { code: '
advise
' }, + { code: '
amplify
' }, + { code: '
apply
' }, + { code: '
arrange
' }, + { code: '
ask
' }, + { code: '
boost
' }, + { code: '
build
' }, + { code: '
call
' }, + { code: '
click
' }, + { code: '
close
' }, + { code: '
commit
' }, + { code: '
consult
' }, + { code: '
compile
' }, + { code: '
collect
' }, + { code: '
contribute
' }, + { code: '
create
' }, + { code: '
cut
' }, + { code: '
decrease
' }, + { code: '
delete
' }, + { code: '
divide
' }, + { code: '
drink
' }, + { code: '
eat
' }, + { code: '
earn
' }, + { code: '
enable
' }, + { code: '
enter
' }, + { code: '
execute
' }, + { code: '
exit
' }, + { code: '
expand
' }, + { code: '
explain
' }, + { code: '
finish
' }, + { code: '
forecast
' }, + { code: '
fix
' }, + { code: '
generate
' }, + { code: '
handle
' }, + { code: '
help
' }, + { code: '
hire
' }, + { code: '
hit
' }, + { code: '
improve
' }, + { code: '
increase
' }, + { code: '
join
' }, + { code: '
jump
' }, + { code: '
leave
' }, + { code: '
let\'/s
' }, + { code: '
list
' }, + { code: '
listen
' }, + { code: '
magnify
' }, + { code: '
make
' }, + { code: '
manage
' }, + { code: '
minimize
' }, + { code: '
move
' }, + { code: '
ok
' }, + { code: '
open
' }, + { code: '
organise
' }, + { code: '
oversee
' }, + { code: '
play
' }, + { code: '
push
' }, + { code: '
read
' }, + { code: '
reduce
' }, + { code: '
run
' }, + { code: '
save
' }, + { code: '
send
' }, + { code: '
shout
' }, + { code: '
sing
' }, + { code: '
submit
' }, + { code: '
support
' }, + { code: '
talk
' }, + { code: '
trim
' }, + { code: '
visit
' }, + { code: '
volunteer
' }, + { code: '
vote
' }, + { code: '
watch
' }, + { code: '
win
' }, + { code: '
write
' }, + ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS - { code: '
apply
', errors: [expectedError] }, - { code: '
ok
', errors: [expectedError] }, - { code: '
finish
', errors: [expectedError] }, - { code: '
submit
', errors: [expectedError] }, - { code: '
delete
', errors: [expectedError] }, - { code: '
apply
' }, + + // { code: '
orange
', errors: [expectedErrorNoAttributes] }, // any word within div that has the two attributes + + // action words within div that has the two attributes + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + + // undefined attributes + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + + // wrong attributes values -1 and navigation + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + + // wrong attributes values 1 and main + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + + // role is missing + { code: '
advise
', errors: [expectedErrorNoRole] }, + { code: '
amplify
', errors: [expectedErrorNoRole] }, + { code: '
apply
', errors: [expectedErrorNoRole] }, + { code: '
arrange
', errors: [expectedErrorNoRole] }, + { code: '
ask
', errors: [expectedErrorNoRole] }, + { code: '
boost
', errors: [expectedErrorNoRole] }, + { code: '
build
', errors: [expectedErrorNoRole] }, + { code: '
call
', errors: [expectedErrorNoRole] }, + { code: '
click
', errors: [expectedErrorNoRole] }, + { code: '
close
', errors: [expectedErrorNoRole] }, + { code: '
commit
', errors: [expectedErrorNoRole] }, + { code: '
consult
', errors: [expectedErrorNoRole] }, + { code: '
compile
', errors: [expectedErrorNoRole] }, + { code: '
collect
', errors: [expectedErrorNoRole] }, + { code: '
contribute
', errors: [expectedErrorNoRole] }, + { code: '
create
', errors: [expectedErrorNoRole] }, + { code: '
cut
', errors: [expectedErrorNoRole] }, + { code: '
decrease
', errors: [expectedErrorNoRole] }, + { code: '
delete
', errors: [expectedErrorNoRole] }, + { code: '
divide
', errors: [expectedErrorNoRole] }, + { code: '
drink
', errors: [expectedErrorNoRole] }, + { code: '
eat
', errors: [expectedErrorNoRole] }, + { code: '
earn
', errors: [expectedErrorNoRole] }, + { code: '
enable
', errors: [expectedErrorNoRole] }, + { code: '
enter
', errors: [expectedErrorNoRole] }, + { code: '
execute
', errors: [expectedErrorNoRole] }, + { code: '
exit
', errors: [expectedErrorNoRole] }, + { code: '
expand
', errors: [expectedErrorNoRole] }, + { code: '
explain
', errors: [expectedErrorNoRole] }, + { code: '
finish
', errors: [expectedErrorNoRole] }, + { code: '
forecast
', errors: [expectedErrorNoRole] }, + { code: '
fix
', errors: [expectedErrorNoRole] }, + { code: '
generate
', errors: [expectedErrorNoRole] }, + { code: '
handle
', errors: [expectedErrorNoRole] }, + { code: '
help
', errors: [expectedErrorNoRole] }, + { code: '
hire
', errors: [expectedErrorNoRole] }, + { code: '
hit
', errors: [expectedErrorNoRole] }, + { code: '
improve
', errors: [expectedErrorNoRole] }, + { code: '
increase
', errors: [expectedErrorNoRole] }, + { code: '
join
', errors: [expectedErrorNoRole] }, + { code: '
jump
', errors: [expectedErrorNoRole] }, + { code: '
leave
', errors: [expectedErrorNoRole] }, + { code: '
let\'/s
', errors: [expectedErrorNoRole] }, + { code: '
list
', errors: [expectedErrorNoRole] }, + { code: '
listen
', errors: [expectedErrorNoRole] }, + { code: '
magnify
', errors: [expectedErrorNoRole] }, + { code: '
make
', errors: [expectedErrorNoRole] }, + { code: '
manage
', errors: [expectedErrorNoRole] }, + { code: '
minimize
', errors: [expectedErrorNoRole] }, + { code: '
move
', errors: [expectedErrorNoRole] }, + { code: '
ok
', errors: [expectedErrorNoRole] }, + { code: '
open
', errors: [expectedErrorNoRole] }, + { code: '
organise
', errors: [expectedErrorNoRole] }, + { code: '
oversee
', errors: [expectedErrorNoRole] }, + { code: '
play
', errors: [expectedErrorNoRole] }, + { code: '
push
', errors: [expectedErrorNoRole] }, + { code: '
read
', errors: [expectedErrorNoRole] }, + { code: '
reduce
', errors: [expectedErrorNoRole] }, + { code: '
run
', errors: [expectedErrorNoRole] }, + { code: '
save
', errors: [expectedErrorNoRole] }, + { code: '
send
', errors: [expectedErrorNoRole] }, + { code: '
shout
', errors: [expectedErrorNoRole] }, + { code: '
sing
', errors: [expectedErrorNoRole] }, + { code: '
submit
', errors: [expectedErrorNoRole] }, + { code: '
support
', errors: [expectedErrorNoRole] }, + { code: '
talk
', errors: [expectedErrorNoRole] }, + { code: '
trim
', errors: [expectedErrorNoRole] }, + { code: '
visit
', errors: [expectedErrorNoRole] }, + { code: '
volunteer
', errors: [expectedErrorNoRole] }, + { code: '
vote
', errors: [expectedErrorNoRole] }, + { code: '
watch
', errors: [expectedErrorNoRole] }, + { code: '
win
', errors: [expectedErrorNoRole] }, + { code: '
write
', errors: [expectedErrorNoRole] }, + + // role is undefined + { code: '
advise
', errors: [expectedErrorNoRole] }, + { code: '
amplify
', errors: [expectedErrorNoRole] }, + { code: '
apply
', errors: [expectedErrorNoRole] }, + { code: '
arrange
', errors: [expectedErrorNoRole] }, + { code: '
ask
', errors: [expectedErrorNoRole] }, + { code: '
boost
', errors: [expectedErrorNoRole] }, + { code: '
build
', errors: [expectedErrorNoRole] }, + { code: '
call
', errors: [expectedErrorNoRole] }, + { code: '
click
', errors: [expectedErrorNoRole] }, + { code: '
close
', errors: [expectedErrorNoRole] }, + { code: '
commit
', errors: [expectedErrorNoRole] }, + { code: '
consult
', errors: [expectedErrorNoRole] }, + { code: '
compile
', errors: [expectedErrorNoRole] }, + { code: '
collect
', errors: [expectedErrorNoRole] }, + { code: '
contribute
', errors: [expectedErrorNoRole] }, + { code: '
create
', errors: [expectedErrorNoRole] }, + { code: '
cut
', errors: [expectedErrorNoRole] }, + { code: '
decrease
', errors: [expectedErrorNoRole] }, + { code: '
delete
', errors: [expectedErrorNoRole] }, + { code: '
divide
', errors: [expectedErrorNoRole] }, + { code: '
drink
', errors: [expectedErrorNoRole] }, + { code: '
eat
', errors: [expectedErrorNoRole] }, + { code: '
earn
', errors: [expectedErrorNoRole] }, + { code: '
enable
', errors: [expectedErrorNoRole] }, + { code: '
enter
', errors: [expectedErrorNoRole] }, + { code: '
execute
', errors: [expectedErrorNoRole] }, + { code: '
exit
', errors: [expectedErrorNoRole] }, + { code: '
expand
', errors: [expectedErrorNoRole] }, + { code: '
explain
', errors: [expectedErrorNoRole] }, + { code: '
finish
', errors: [expectedErrorNoRole] }, + { code: '
forecast
', errors: [expectedErrorNoRole] }, + { code: '
fix
', errors: [expectedErrorNoRole] }, + { code: '
generate
', errors: [expectedErrorNoRole] }, + { code: '
handle
', errors: [expectedErrorNoRole] }, + { code: '
help
', errors: [expectedErrorNoRole] }, + { code: '
hire
', errors: [expectedErrorNoRole] }, + { code: '
hit
', errors: [expectedErrorNoRole] }, + { code: '
improve
', errors: [expectedErrorNoRole] }, + { code: '
increase
', errors: [expectedErrorNoRole] }, + { code: '
join
', errors: [expectedErrorNoRole] }, + { code: '
jump
', errors: [expectedErrorNoRole] }, + { code: '
leave
', errors: [expectedErrorNoRole] }, + { code: '
let\'/s
', errors: [expectedErrorNoRole] }, + { code: '
list
', errors: [expectedErrorNoRole] }, + { code: '
listen
', errors: [expectedErrorNoRole] }, + { code: '
magnify
', errors: [expectedErrorNoRole] }, + { code: '
make
', errors: [expectedErrorNoRole] }, + { code: '
manage
', errors: [expectedErrorNoRole] }, + { code: '
minimize
', errors: [expectedErrorNoRole] }, + { code: '
move
', errors: [expectedErrorNoRole] }, + { code: '
ok
', errors: [expectedErrorNoRole] }, + { code: '
open
', errors: [expectedErrorNoRole] }, + { code: '
organise
', errors: [expectedErrorNoRole] }, + { code: '
oversee
', errors: [expectedErrorNoRole] }, + { code: '
play
', errors: [expectedErrorNoRole] }, + { code: '
push
', errors: [expectedErrorNoRole] }, + { code: '
read
', errors: [expectedErrorNoRole] }, + { code: '
reduce
', errors: [expectedErrorNoRole] }, + { code: '
run
', errors: [expectedErrorNoRole] }, + { code: '
save
', errors: [expectedErrorNoRole] }, + { code: '
send
', errors: [expectedErrorNoRole] }, + { code: '
shout
', errors: [expectedErrorNoRole] }, + { code: '
sing
', errors: [expectedErrorNoRole] }, + { code: '
submit
', errors: [expectedErrorNoRole] }, + { code: '
support
', errors: [expectedErrorNoRole] }, + { code: '
talk
', errors: [expectedErrorNoRole] }, + { code: '
trim
', errors: [expectedErrorNoRole] }, + { code: '
visit
', errors: [expectedErrorNoRole] }, + { code: '
volunteer
', errors: [expectedErrorNoRole] }, + { code: '
vote
', errors: [expectedErrorNoRole] }, + { code: '
watch
', errors: [expectedErrorNoRole] }, + { code: '
win
', errors: [expectedErrorNoRole] }, + { code: '
write
', errors: [expectedErrorNoRole] }, + + // role's value is an empty string + { code: '
advise
', errors: [expectedErrorNoRole] }, + { code: '
amplify
', errors: [expectedErrorNoRole] }, + { code: '
apply
', errors: [expectedErrorNoRole] }, + { code: '
arrange
', errors: [expectedErrorNoRole] }, + { code: '
ask
', errors: [expectedErrorNoRole] }, + { code: '
boost
', errors: [expectedErrorNoRole] }, + { code: '
build
', errors: [expectedErrorNoRole] }, + { code: '
call
', errors: [expectedErrorNoRole] }, + { code: '
click
', errors: [expectedErrorNoRole] }, + { code: '
close
', errors: [expectedErrorNoRole] }, + { code: '
commit
', errors: [expectedErrorNoRole] }, + { code: '
consult
', errors: [expectedErrorNoRole] }, + { code: '
compile
', errors: [expectedErrorNoRole] }, + { code: '
collect
', errors: [expectedErrorNoRole] }, + { code: '
contribute
', errors: [expectedErrorNoRole] }, + { code: '
create
', errors: [expectedErrorNoRole] }, + { code: '
cut
', errors: [expectedErrorNoRole] }, + { code: '
decrease
', errors: [expectedErrorNoRole] }, + { code: '
delete
', errors: [expectedErrorNoRole] }, + { code: '
divide
', errors: [expectedErrorNoRole] }, + { code: '
drink
', errors: [expectedErrorNoRole] }, + { code: '
eat
', errors: [expectedErrorNoRole] }, + { code: '
earn
', errors: [expectedErrorNoRole] }, + { code: '
enable
', errors: [expectedErrorNoRole] }, + { code: '
enter
', errors: [expectedErrorNoRole] }, + { code: '
execute
', errors: [expectedErrorNoRole] }, + { code: '
exit
', errors: [expectedErrorNoRole] }, + { code: '
expand
', errors: [expectedErrorNoRole] }, + { code: '
explain
', errors: [expectedErrorNoRole] }, + { code: '
finish
', errors: [expectedErrorNoRole] }, + { code: '
forecast
', errors: [expectedErrorNoRole] }, + { code: '
fix
', errors: [expectedErrorNoRole] }, + { code: '
generate
', errors: [expectedErrorNoRole] }, + { code: '
handle
', errors: [expectedErrorNoRole] }, + { code: '
help
', errors: [expectedErrorNoRole] }, + { code: '
hire
', errors: [expectedErrorNoRole] }, + { code: '
hit
', errors: [expectedErrorNoRole] }, + { code: '
improve
', errors: [expectedErrorNoRole] }, + { code: '
increase
', errors: [expectedErrorNoRole] }, + { code: '
join
', errors: [expectedErrorNoRole] }, + { code: '
jump
', errors: [expectedErrorNoRole] }, + { code: '
leave
', errors: [expectedErrorNoRole] }, + { code: '
let\'/s
', errors: [expectedErrorNoRole] }, + { code: '
list
', errors: [expectedErrorNoRole] }, + { code: '
listen
', errors: [expectedErrorNoRole] }, + { code: '
magnify
', errors: [expectedErrorNoRole] }, + { code: '
make
', errors: [expectedErrorNoRole] }, + { code: '
manage
', errors: [expectedErrorNoRole] }, + { code: '
minimize
', errors: [expectedErrorNoRole] }, + { code: '
move
', errors: [expectedErrorNoRole] }, + { code: '
ok
', errors: [expectedErrorNoRole] }, + { code: '
open
', errors: [expectedErrorNoRole] }, + { code: '
organise
', errors: [expectedErrorNoRole] }, + { code: '
oversee
', errors: [expectedErrorNoRole] }, + { code: '
play
', errors: [expectedErrorNoRole] }, + { code: '
push
', errors: [expectedErrorNoRole] }, + { code: '
read
', errors: [expectedErrorNoRole] }, + { code: '
reduce
', errors: [expectedErrorNoRole] }, + { code: '
run
', errors: [expectedErrorNoRole] }, + { code: '
save
', errors: [expectedErrorNoRole] }, + { code: '
send
', errors: [expectedErrorNoRole] }, + { code: '
shout
', errors: [expectedErrorNoRole] }, + { code: '
sing
', errors: [expectedErrorNoRole] }, + { code: '
submit
', errors: [expectedErrorNoRole] }, + { code: '
support
', errors: [expectedErrorNoRole] }, + { code: '
talk
', errors: [expectedErrorNoRole] }, + { code: '
trim
', errors: [expectedErrorNoRole] }, + { code: '
visit
', errors: [expectedErrorNoRole] }, + { code: '
volunteer
', errors: [expectedErrorNoRole] }, + { code: '
vote
', errors: [expectedErrorNoRole] }, + { code: '
watch
', errors: [expectedErrorNoRole] }, + { code: '
win
', errors: [expectedErrorNoRole] }, + { code: '
write
', errors: [expectedErrorNoRole] }, + + // role's value is not button + { code: '
advise
', errors: [expectedErrorNoRole] }, + { code: '
amplify
', errors: [expectedErrorNoRole] }, + { code: '
apply
', errors: [expectedErrorNoRole] }, + { code: '
arrange
', errors: [expectedErrorNoRole] }, + { code: '
ask
', errors: [expectedErrorNoRole] }, + { code: '
boost
', errors: [expectedErrorNoRole] }, + { code: '
build
', errors: [expectedErrorNoRole] }, + { code: '
call
', errors: [expectedErrorNoRole] }, + { code: '
click
', errors: [expectedErrorNoRole] }, + { code: '
close
', errors: [expectedErrorNoRole] }, + { code: '
commit
', errors: [expectedErrorNoRole] }, + { code: '
consult
', errors: [expectedErrorNoRole] }, + { code: '
compile
', errors: [expectedErrorNoRole] }, + { code: '
collect
', errors: [expectedErrorNoRole] }, + { code: '
contribute
', errors: [expectedErrorNoRole] }, + { code: '
create
', errors: [expectedErrorNoRole] }, + { code: '
cut
', errors: [expectedErrorNoRole] }, + { code: '
decrease
', errors: [expectedErrorNoRole] }, + { code: '
delete
', errors: [expectedErrorNoRole] }, + { code: '
divide
', errors: [expectedErrorNoRole] }, + { code: '
drink
', errors: [expectedErrorNoRole] }, + { code: '
eat
', errors: [expectedErrorNoRole] }, + { code: '
earn
', errors: [expectedErrorNoRole] }, + { code: '
enable
', errors: [expectedErrorNoRole] }, + { code: '
enter
', errors: [expectedErrorNoRole] }, + { code: '
execute
', errors: [expectedErrorNoRole] }, + { code: '
exit
', errors: [expectedErrorNoRole] }, + { code: '
expand
', errors: [expectedErrorNoRole] }, + { code: '
explain
', errors: [expectedErrorNoRole] }, + { code: '
finish
', errors: [expectedErrorNoRole] }, + { code: '
forecast
', errors: [expectedErrorNoRole] }, + { code: '
fix
', errors: [expectedErrorNoRole] }, + { code: '
generate
', errors: [expectedErrorNoRole] }, + { code: '
handle
', errors: [expectedErrorNoRole] }, + { code: '
help
', errors: [expectedErrorNoRole] }, + { code: '
hire
', errors: [expectedErrorNoRole] }, + { code: '
hit
', errors: [expectedErrorNoRole] }, + { code: '
improve
', errors: [expectedErrorNoRole] }, + { code: '
increase
', errors: [expectedErrorNoRole] }, + { code: '
join
', errors: [expectedErrorNoRole] }, + { code: '
jump
', errors: [expectedErrorNoRole] }, + { code: '
leave
', errors: [expectedErrorNoRole] }, + { code: '
let\'/s
', errors: [expectedErrorNoRole] }, + { code: '
list
', errors: [expectedErrorNoRole] }, + { code: '
listen
', errors: [expectedErrorNoRole] }, + { code: '
magnify
', errors: [expectedErrorNoRole] }, + { code: '
make
', errors: [expectedErrorNoRole] }, + { code: '
manage
', errors: [expectedErrorNoRole] }, + { code: '
minimize
', errors: [expectedErrorNoRole] }, + { code: '
move
', errors: [expectedErrorNoRole] }, + { code: '
ok
', errors: [expectedErrorNoRole] }, + { code: '
open
', errors: [expectedErrorNoRole] }, + { code: '
organise
', errors: [expectedErrorNoRole] }, + { code: '
oversee
', errors: [expectedErrorNoRole] }, + { code: '
play
', errors: [expectedErrorNoRole] }, + { code: '
push
', errors: [expectedErrorNoRole] }, + { code: '
read
', errors: [expectedErrorNoRole] }, + { code: '
reduce
', errors: [expectedErrorNoRole] }, + { code: '
run
', errors: [expectedErrorNoRole] }, + { code: '
save
', errors: [expectedErrorNoRole] }, + { code: '
send
', errors: [expectedErrorNoRole] }, + { code: '
shout
', errors: [expectedErrorNoRole] }, + { code: '
sing
', errors: [expectedErrorNoRole] }, + { code: '
submit
', errors: [expectedErrorNoRole] }, + { code: '
support
', errors: [expectedErrorNoRole] }, + { code: '
talk
', errors: [expectedErrorNoRole] }, + { code: '
trim
', errors: [expectedErrorNoRole] }, + { code: '
visit
', errors: [expectedErrorNoRole] }, + { code: '
volunteer
', errors: [expectedErrorNoRole] }, + { code: '
vote
', errors: [expectedErrorNoRole] }, + { code: '
watch
', errors: [expectedErrorNoRole] }, + { code: '
win
', errors: [expectedErrorNoRole] }, + { code: '
write
', errors: [expectedErrorNoRole] }, + + // tabindex is missing + { code: '
advise
', errors: [expectedErrorNoTabindex] }, + { code: '
amplify
', errors: [expectedErrorNoTabindex] }, + { code: '
apply
', errors: [expectedErrorNoTabindex] }, + { code: '
arrange
', errors: [expectedErrorNoTabindex] }, + { code: '
ask
', errors: [expectedErrorNoTabindex] }, + { code: '
boost
', errors: [expectedErrorNoTabindex] }, + { code: '
build
', errors: [expectedErrorNoTabindex] }, + { code: '
call
', errors: [expectedErrorNoTabindex] }, + { code: '
click
', errors: [expectedErrorNoTabindex] }, + { code: '
close
', errors: [expectedErrorNoTabindex] }, + { code: '
commit
', errors: [expectedErrorNoTabindex] }, + { code: '
consult
', errors: [expectedErrorNoTabindex] }, + { code: '
compile
', errors: [expectedErrorNoTabindex] }, + { code: '
collect
', errors: [expectedErrorNoTabindex] }, + { code: '
contribute
', errors: [expectedErrorNoTabindex] }, + { code: '
create
', errors: [expectedErrorNoTabindex] }, + { code: '
cut
', errors: [expectedErrorNoTabindex] }, + { code: '
decrease
', errors: [expectedErrorNoTabindex] }, + { code: '
delete
', errors: [expectedErrorNoTabindex] }, + { code: '
divide
', errors: [expectedErrorNoTabindex] }, + { code: '
drink
', errors: [expectedErrorNoTabindex] }, + { code: '
eat
', errors: [expectedErrorNoTabindex] }, + { code: '
earn
', errors: [expectedErrorNoTabindex] }, + { code: '
enable
', errors: [expectedErrorNoTabindex] }, + { code: '
enter
', errors: [expectedErrorNoTabindex] }, + { code: '
execute
', errors: [expectedErrorNoTabindex] }, + { code: '
exit
', errors: [expectedErrorNoTabindex] }, + { code: '
expand
', errors: [expectedErrorNoTabindex] }, + { code: '
explain
', errors: [expectedErrorNoTabindex] }, + { code: '
finish
', errors: [expectedErrorNoTabindex] }, + { code: '
forecast
', errors: [expectedErrorNoTabindex] }, + { code: '
fix
', errors: [expectedErrorNoTabindex] }, + { code: '
generate
', errors: [expectedErrorNoTabindex] }, + { code: '
handle
', errors: [expectedErrorNoTabindex] }, + { code: '
help
', errors: [expectedErrorNoTabindex] }, + { code: '
hire
', errors: [expectedErrorNoTabindex] }, + { code: '
hit
', errors: [expectedErrorNoTabindex] }, + { code: '
improve
', errors: [expectedErrorNoTabindex] }, + { code: '
increase
', errors: [expectedErrorNoTabindex] }, + { code: '
join
', errors: [expectedErrorNoTabindex] }, + { code: '
jump
', errors: [expectedErrorNoTabindex] }, + { code: '
leave
', errors: [expectedErrorNoTabindex] }, + { code: '
let\'/s
', errors: [expectedErrorNoTabindex] }, + { code: '
list
', errors: [expectedErrorNoTabindex] }, + { code: '
listen
', errors: [expectedErrorNoTabindex] }, + { code: '
magnify
', errors: [expectedErrorNoTabindex] }, + { code: '
make
', errors: [expectedErrorNoTabindex] }, + { code: '
manage
', errors: [expectedErrorNoTabindex] }, + { code: '
minimize
', errors: [expectedErrorNoTabindex] }, + { code: '
move
', errors: [expectedErrorNoTabindex] }, + { code: '
ok
', errors: [expectedErrorNoTabindex] }, + { code: '
open
', errors: [expectedErrorNoTabindex] }, + { code: '
organise
', errors: [expectedErrorNoTabindex] }, + { code: '
oversee
', errors: [expectedErrorNoTabindex] }, + { code: '
play
', errors: [expectedErrorNoTabindex] }, + { code: '
push
', errors: [expectedErrorNoTabindex] }, + { code: '
read
', errors: [expectedErrorNoTabindex] }, + { code: '
reduce
', errors: [expectedErrorNoTabindex] }, + { code: '
run
', errors: [expectedErrorNoTabindex] }, + { code: '
save
', errors: [expectedErrorNoTabindex] }, + { code: '
send
', errors: [expectedErrorNoTabindex] }, + { code: '
shout
', errors: [expectedErrorNoTabindex] }, + { code: '
sing
', errors: [expectedErrorNoTabindex] }, + { code: '
submit
', errors: [expectedErrorNoTabindex] }, + { code: '
support
', errors: [expectedErrorNoTabindex] }, + { code: '
talk
', errors: [expectedErrorNoTabindex] }, + { code: '
trim
', errors: [expectedErrorNoTabindex] }, + { code: '
visit
', errors: [expectedErrorNoTabindex] }, + { code: '
volunteer
', errors: [expectedErrorNoTabindex] }, + { code: '
vote
', errors: [expectedErrorNoTabindex] }, + { code: '
watch
', errors: [expectedErrorNoTabindex] }, + { code: '
win
', errors: [expectedErrorNoTabindex] }, + { code: '
write
', errors: [expectedErrorNoTabindex] }, + + // tabindex is undefined + { code: '
advise
', errors: [expectedErrorNoTabindex] }, + { code: '
amplify
', errors: [expectedErrorNoTabindex] }, + { code: '
apply
', errors: [expectedErrorNoTabindex] }, + { code: '
arrange
', errors: [expectedErrorNoTabindex] }, + { code: '
ask
', errors: [expectedErrorNoTabindex] }, + { code: '
boost
', errors: [expectedErrorNoTabindex] }, + { code: '
build
', errors: [expectedErrorNoTabindex] }, + { code: '
call
', errors: [expectedErrorNoTabindex] }, + { code: '
click
', errors: [expectedErrorNoTabindex] }, + { code: '
close
', errors: [expectedErrorNoTabindex] }, + { code: '
commit
', errors: [expectedErrorNoTabindex] }, + { code: '
consult
', errors: [expectedErrorNoTabindex] }, + { code: '
compile
', errors: [expectedErrorNoTabindex] }, + { code: '
collect
', errors: [expectedErrorNoTabindex] }, + { code: '
contribute
', errors: [expectedErrorNoTabindex] }, + { code: '
create
', errors: [expectedErrorNoTabindex] }, + { code: '
cut
', errors: [expectedErrorNoTabindex] }, + { code: '
decrease
', errors: [expectedErrorNoTabindex] }, + { code: '
delete
', errors: [expectedErrorNoTabindex] }, + { code: '
divide
', errors: [expectedErrorNoTabindex] }, + { code: '
drink
', errors: [expectedErrorNoTabindex] }, + { code: '
eat
', errors: [expectedErrorNoTabindex] }, + { code: '
earn
', errors: [expectedErrorNoTabindex] }, + { code: '
enable
', errors: [expectedErrorNoTabindex] }, + { code: '
enter
', errors: [expectedErrorNoTabindex] }, + { code: '
execute
', errors: [expectedErrorNoTabindex] }, + { code: '
exit
', errors: [expectedErrorNoTabindex] }, + { code: '
expand
', errors: [expectedErrorNoTabindex] }, + { code: '
explain
', errors: [expectedErrorNoTabindex] }, + { code: '
finish
', errors: [expectedErrorNoTabindex] }, + { code: '
forecast
', errors: [expectedErrorNoTabindex] }, + { code: '
fix
', errors: [expectedErrorNoTabindex] }, + { code: '
generate
', errors: [expectedErrorNoTabindex] }, + { code: '
handle
', errors: [expectedErrorNoTabindex] }, + { code: '
help
', errors: [expectedErrorNoTabindex] }, + { code: '
hire
', errors: [expectedErrorNoTabindex] }, + { code: '
hit
', errors: [expectedErrorNoTabindex] }, + { code: '
improve
', errors: [expectedErrorNoTabindex] }, + { code: '
increase
', errors: [expectedErrorNoTabindex] }, + { code: '
join
', errors: [expectedErrorNoTabindex] }, + { code: '
jump
', errors: [expectedErrorNoTabindex] }, + { code: '
leave
', errors: [expectedErrorNoTabindex] }, + { code: '
let\'/s
', errors: [expectedErrorNoTabindex] }, + { code: '
list
', errors: [expectedErrorNoTabindex] }, + { code: '
listen
', errors: [expectedErrorNoTabindex] }, + { code: '
magnify
', errors: [expectedErrorNoTabindex] }, + { code: '
make
', errors: [expectedErrorNoTabindex] }, + { code: '
manage
', errors: [expectedErrorNoTabindex] }, + { code: '
minimize
', errors: [expectedErrorNoTabindex] }, + { code: '
move
', errors: [expectedErrorNoTabindex] }, + { code: '
ok
', errors: [expectedErrorNoTabindex] }, + { code: '
open
', errors: [expectedErrorNoTabindex] }, + { code: '
organise
', errors: [expectedErrorNoTabindex] }, + { code: '
oversee
', errors: [expectedErrorNoTabindex] }, + { code: '
play
', errors: [expectedErrorNoTabindex] }, + { code: '
push
', errors: [expectedErrorNoTabindex] }, + { code: '
read
', errors: [expectedErrorNoTabindex] }, + { code: '
reduce
', errors: [expectedErrorNoTabindex] }, + { code: '
run
', errors: [expectedErrorNoTabindex] }, + { code: '
save
', errors: [expectedErrorNoTabindex] }, + { code: '
send
', errors: [expectedErrorNoTabindex] }, + { code: '
shout
', errors: [expectedErrorNoTabindex] }, + { code: '
sing
', errors: [expectedErrorNoTabindex] }, + { code: '
submit
', errors: [expectedErrorNoTabindex] }, + { code: '
support
', errors: [expectedErrorNoTabindex] }, + { code: '
talk
', errors: [expectedErrorNoTabindex] }, + { code: '
trim
', errors: [expectedErrorNoTabindex] }, + { code: '
visit
', errors: [expectedErrorNoTabindex] }, + { code: '
volunteer
', errors: [expectedErrorNoTabindex] }, + { code: '
vote
', errors: [expectedErrorNoTabindex] }, + { code: '
watch
', errors: [expectedErrorNoTabindex] }, + { code: '
win
', errors: [expectedErrorNoTabindex] }, + { code: '
write
', errors: [expectedErrorNoTabindex] }, + + // tabindex's value is an empty string + { code: '
advise
', errors: [expectedErrorNoTabindex] }, + { code: '
amplify
', errors: [expectedErrorNoTabindex] }, + { code: '
apply
', errors: [expectedErrorNoTabindex] }, + { code: '
arrange
', errors: [expectedErrorNoTabindex] }, + { code: '
ask
', errors: [expectedErrorNoTabindex] }, + { code: '
boost
', errors: [expectedErrorNoTabindex] }, + { code: '
build
', errors: [expectedErrorNoTabindex] }, + { code: '
call
', errors: [expectedErrorNoTabindex] }, + { code: '
click
', errors: [expectedErrorNoTabindex] }, + { code: '
close
', errors: [expectedErrorNoTabindex] }, + { code: '
commit
', errors: [expectedErrorNoTabindex] }, + { code: '
consult
', errors: [expectedErrorNoTabindex] }, + { code: '
compile
', errors: [expectedErrorNoTabindex] }, + { code: '
collect
', errors: [expectedErrorNoTabindex] }, + { code: '
contribute
', errors: [expectedErrorNoTabindex] }, + { code: '
create
', errors: [expectedErrorNoTabindex] }, + { code: '
cut
', errors: [expectedErrorNoTabindex] }, + { code: '
decrease
', errors: [expectedErrorNoTabindex] }, + { code: '
delete
', errors: [expectedErrorNoTabindex] }, + { code: '
divide
', errors: [expectedErrorNoTabindex] }, + { code: '
drink
', errors: [expectedErrorNoTabindex] }, + { code: '
eat
', errors: [expectedErrorNoTabindex] }, + { code: '
earn
', errors: [expectedErrorNoTabindex] }, + { code: '
enable
', errors: [expectedErrorNoTabindex] }, + { code: '
enter
', errors: [expectedErrorNoTabindex] }, + { code: '
execute
', errors: [expectedErrorNoTabindex] }, + { code: '
exit
', errors: [expectedErrorNoTabindex] }, + { code: '
expand
', errors: [expectedErrorNoTabindex] }, + { code: '
explain
', errors: [expectedErrorNoTabindex] }, + { code: '
finish
', errors: [expectedErrorNoTabindex] }, + { code: '
forecast
', errors: [expectedErrorNoTabindex] }, + { code: '
fix
', errors: [expectedErrorNoTabindex] }, + { code: '
generate
', errors: [expectedErrorNoTabindex] }, + { code: '
handle
', errors: [expectedErrorNoTabindex] }, + { code: '
help
', errors: [expectedErrorNoTabindex] }, + { code: '
hire
', errors: [expectedErrorNoTabindex] }, + { code: '
hit
', errors: [expectedErrorNoTabindex] }, + { code: '
improve
', errors: [expectedErrorNoTabindex] }, + { code: '
increase
', errors: [expectedErrorNoTabindex] }, + { code: '
join
', errors: [expectedErrorNoTabindex] }, + { code: '
jump
', errors: [expectedErrorNoTabindex] }, + { code: '
leave
', errors: [expectedErrorNoTabindex] }, + { code: '
let\'/s
', errors: [expectedErrorNoTabindex] }, + { code: '
list
', errors: [expectedErrorNoTabindex] }, + { code: '
listen
', errors: [expectedErrorNoTabindex] }, + { code: '
magnify
', errors: [expectedErrorNoTabindex] }, + { code: '
make
', errors: [expectedErrorNoTabindex] }, + { code: '
manage
', errors: [expectedErrorNoTabindex] }, + { code: '
minimize
', errors: [expectedErrorNoTabindex] }, + { code: '
move
', errors: [expectedErrorNoTabindex] }, + { code: '
ok
', errors: [expectedErrorNoTabindex] }, + { code: '
open
', errors: [expectedErrorNoTabindex] }, + { code: '
organise
', errors: [expectedErrorNoTabindex] }, + { code: '
oversee
', errors: [expectedErrorNoTabindex] }, + { code: '
play
', errors: [expectedErrorNoTabindex] }, + { code: '
push
', errors: [expectedErrorNoTabindex] }, + { code: '
read
', errors: [expectedErrorNoTabindex] }, + { code: '
reduce
', errors: [expectedErrorNoTabindex] }, + { code: '
run
', errors: [expectedErrorNoTabindex] }, + { code: '
save
', errors: [expectedErrorNoTabindex] }, + { code: '
send
', errors: [expectedErrorNoTabindex] }, + { code: '
shout
', errors: [expectedErrorNoTabindex] }, + { code: '
sing
', errors: [expectedErrorNoTabindex] }, + { code: '
submit
', errors: [expectedErrorNoTabindex] }, + { code: '
support
', errors: [expectedErrorNoTabindex] }, + { code: '
talk
', errors: [expectedErrorNoTabindex] }, + { code: '
trim
', errors: [expectedErrorNoTabindex] }, + { code: '
visit
', errors: [expectedErrorNoTabindex] }, + { code: '
volunteer
', errors: [expectedErrorNoTabindex] }, + { code: '
vote
', errors: [expectedErrorNoTabindex] }, + { code: '
watch
', errors: [expectedErrorNoTabindex] }, + { code: '
win
', errors: [expectedErrorNoTabindex] }, + { code: '
write
', errors: [expectedErrorNoTabindex] }, + + // tabindex's value is -1 + { code: '
advise
', errors: [expectedErrorNoTabindex] }, + { code: '
amplify
', errors: [expectedErrorNoTabindex] }, + { code: '
apply
', errors: [expectedErrorNoTabindex] }, + { code: '
arrange
', errors: [expectedErrorNoTabindex] }, + { code: '
ask
', errors: [expectedErrorNoTabindex] }, + { code: '
boost
', errors: [expectedErrorNoTabindex] }, + { code: '
build
', errors: [expectedErrorNoTabindex] }, + { code: '
call
', errors: [expectedErrorNoTabindex] }, + { code: '
click
', errors: [expectedErrorNoTabindex] }, + { code: '
close
', errors: [expectedErrorNoTabindex] }, + { code: '
commit
', errors: [expectedErrorNoTabindex] }, + { code: '
consult
', errors: [expectedErrorNoTabindex] }, + { code: '
compile
', errors: [expectedErrorNoTabindex] }, + { code: '
collect
', errors: [expectedErrorNoTabindex] }, + { code: '
contribute
', errors: [expectedErrorNoTabindex] }, + { code: '
create
', errors: [expectedErrorNoTabindex] }, + { code: '
cut
', errors: [expectedErrorNoTabindex] }, + { code: '
decrease
', errors: [expectedErrorNoTabindex] }, + { code: '
delete
', errors: [expectedErrorNoTabindex] }, + { code: '
divide
', errors: [expectedErrorNoTabindex] }, + { code: '
drink
', errors: [expectedErrorNoTabindex] }, + { code: '
eat
', errors: [expectedErrorNoTabindex] }, + { code: '
earn
', errors: [expectedErrorNoTabindex] }, + { code: '
enable
', errors: [expectedErrorNoTabindex] }, + { code: '
enter
', errors: [expectedErrorNoTabindex] }, + { code: '
execute
', errors: [expectedErrorNoTabindex] }, + { code: '
exit
', errors: [expectedErrorNoTabindex] }, + { code: '
expand
', errors: [expectedErrorNoTabindex] }, + { code: '
explain
', errors: [expectedErrorNoTabindex] }, + { code: '
finish
', errors: [expectedErrorNoTabindex] }, + { code: '
forecast
', errors: [expectedErrorNoTabindex] }, + { code: '
fix
', errors: [expectedErrorNoTabindex] }, + { code: '
generate
', errors: [expectedErrorNoTabindex] }, + { code: '
handle
', errors: [expectedErrorNoTabindex] }, + { code: '
help
', errors: [expectedErrorNoTabindex] }, + { code: '
hire
', errors: [expectedErrorNoTabindex] }, + { code: '
hit
', errors: [expectedErrorNoTabindex] }, + { code: '
improve
', errors: [expectedErrorNoTabindex] }, + { code: '
increase
', errors: [expectedErrorNoTabindex] }, + { code: '
join
', errors: [expectedErrorNoTabindex] }, + { code: '
jump
', errors: [expectedErrorNoTabindex] }, + { code: '
leave
', errors: [expectedErrorNoTabindex] }, + { code: '
let\'/s
', errors: [expectedErrorNoTabindex] }, + { code: '
list
', errors: [expectedErrorNoTabindex] }, + { code: '
listen
', errors: [expectedErrorNoTabindex] }, + { code: '
magnify
', errors: [expectedErrorNoTabindex] }, + { code: '
make
', errors: [expectedErrorNoTabindex] }, + { code: '
manage
', errors: [expectedErrorNoTabindex] }, + { code: '
minimize
', errors: [expectedErrorNoTabindex] }, + { code: '
move
', errors: [expectedErrorNoTabindex] }, + { code: '
ok
', errors: [expectedErrorNoTabindex] }, + { code: '
open
', errors: [expectedErrorNoTabindex] }, + { code: '
organise
', errors: [expectedErrorNoTabindex] }, + { code: '
oversee
', errors: [expectedErrorNoTabindex] }, + { code: '
play
', errors: [expectedErrorNoTabindex] }, + { code: '
push
', errors: [expectedErrorNoTabindex] }, + { code: '
read
', errors: [expectedErrorNoTabindex] }, + { code: '
reduce
', errors: [expectedErrorNoTabindex] }, + { code: '
run
', errors: [expectedErrorNoTabindex] }, + { code: '
save
', errors: [expectedErrorNoTabindex] }, + { code: '
send
', errors: [expectedErrorNoTabindex] }, + { code: '
shout
', errors: [expectedErrorNoTabindex] }, + { code: '
sing
', errors: [expectedErrorNoTabindex] }, + { code: '
submit
', errors: [expectedErrorNoTabindex] }, + { code: '
support
', errors: [expectedErrorNoTabindex] }, + { code: '
talk
', errors: [expectedErrorNoTabindex] }, + { code: '
trim
', errors: [expectedErrorNoTabindex] }, + { code: '
visit
', errors: [expectedErrorNoTabindex] }, + { code: '
volunteer
', errors: [expectedErrorNoTabindex] }, + { code: '
vote
', errors: [expectedErrorNoTabindex] }, + { code: '
watch
', errors: [expectedErrorNoTabindex] }, + { code: '
win
', errors: [expectedErrorNoTabindex] }, + { code: '
write
', errors: [expectedErrorNoTabindex] }, + + // tabindex's value is 1 + { code: '
advise
', errors: [expectedErrorNoTabindex] }, + { code: '
amplify
', errors: [expectedErrorNoTabindex] }, + { code: '
apply
', errors: [expectedErrorNoTabindex] }, + { code: '
arrange
', errors: [expectedErrorNoTabindex] }, + { code: '
ask
', errors: [expectedErrorNoTabindex] }, + { code: '
boost
', errors: [expectedErrorNoTabindex] }, + { code: '
build
', errors: [expectedErrorNoTabindex] }, + { code: '
call
', errors: [expectedErrorNoTabindex] }, + { code: '
click
', errors: [expectedErrorNoTabindex] }, + { code: '
close
', errors: [expectedErrorNoTabindex] }, + { code: '
commit
', errors: [expectedErrorNoTabindex] }, + { code: '
consult
', errors: [expectedErrorNoTabindex] }, + { code: '
compile
', errors: [expectedErrorNoTabindex] }, + { code: '
collect
', errors: [expectedErrorNoTabindex] }, + { code: '
contribute
', errors: [expectedErrorNoTabindex] }, + { code: '
create
', errors: [expectedErrorNoTabindex] }, + { code: '
cut
', errors: [expectedErrorNoTabindex] }, + { code: '
decrease
', errors: [expectedErrorNoTabindex] }, + { code: '
delete
', errors: [expectedErrorNoTabindex] }, + { code: '
divide
', errors: [expectedErrorNoTabindex] }, + { code: '
drink
', errors: [expectedErrorNoTabindex] }, + { code: '
eat
', errors: [expectedErrorNoTabindex] }, + { code: '
earn
', errors: [expectedErrorNoTabindex] }, + { code: '
enable
', errors: [expectedErrorNoTabindex] }, + { code: '
enter
', errors: [expectedErrorNoTabindex] }, + { code: '
execute
', errors: [expectedErrorNoTabindex] }, + { code: '
exit
', errors: [expectedErrorNoTabindex] }, + { code: '
expand
', errors: [expectedErrorNoTabindex] }, + { code: '
explain
', errors: [expectedErrorNoTabindex] }, + { code: '
finish
', errors: [expectedErrorNoTabindex] }, + { code: '
forecast
', errors: [expectedErrorNoTabindex] }, + { code: '
fix
', errors: [expectedErrorNoTabindex] }, + { code: '
generate
', errors: [expectedErrorNoTabindex] }, + { code: '
handle
', errors: [expectedErrorNoTabindex] }, + { code: '
help
', errors: [expectedErrorNoTabindex] }, + { code: '
hire
', errors: [expectedErrorNoTabindex] }, + { code: '
hit
', errors: [expectedErrorNoTabindex] }, + { code: '
improve
', errors: [expectedErrorNoTabindex] }, + { code: '
increase
', errors: [expectedErrorNoTabindex] }, + { code: '
join
', errors: [expectedErrorNoTabindex] }, + { code: '
jump
', errors: [expectedErrorNoTabindex] }, + { code: '
leave
', errors: [expectedErrorNoTabindex] }, + { code: '
let\'/s
', errors: [expectedErrorNoTabindex] }, + { code: '
list
', errors: [expectedErrorNoTabindex] }, + { code: '
listen
', errors: [expectedErrorNoTabindex] }, + { code: '
magnify
', errors: [expectedErrorNoTabindex] }, + { code: '
make
', errors: [expectedErrorNoTabindex] }, + { code: '
manage
', errors: [expectedErrorNoTabindex] }, + { code: '
minimize
', errors: [expectedErrorNoTabindex] }, + { code: '
move
', errors: [expectedErrorNoTabindex] }, + { code: '
ok
', errors: [expectedErrorNoTabindex] }, + { code: '
open
', errors: [expectedErrorNoTabindex] }, + { code: '
organise
', errors: [expectedErrorNoTabindex] }, + { code: '
oversee
', errors: [expectedErrorNoTabindex] }, + { code: '
play
', errors: [expectedErrorNoTabindex] }, + { code: '
push
', errors: [expectedErrorNoTabindex] }, + { code: '
read
', errors: [expectedErrorNoTabindex] }, + { code: '
reduce
', errors: [expectedErrorNoTabindex] }, + { code: '
run
', errors: [expectedErrorNoTabindex] }, + { code: '
save
', errors: [expectedErrorNoTabindex] }, + { code: '
send
', errors: [expectedErrorNoTabindex] }, + { code: '
shout
', errors: [expectedErrorNoTabindex] }, + { code: '
sing
', errors: [expectedErrorNoTabindex] }, + { code: '
submit
', errors: [expectedErrorNoTabindex] }, + { code: '
support
', errors: [expectedErrorNoTabindex] }, + { code: '
talk
', errors: [expectedErrorNoTabindex] }, + { code: '
trim
', errors: [expectedErrorNoTabindex] }, + { code: '
visit
', errors: [expectedErrorNoTabindex] }, + { code: '
volunteer
', errors: [expectedErrorNoTabindex] }, + { code: '
vote
', errors: [expectedErrorNoTabindex] }, + { code: '
watch
', errors: [expectedErrorNoTabindex] }, + { code: '
win
', errors: [expectedErrorNoTabindex] }, + { code: '
write
', errors: [expectedErrorNoTabindex] }, ].map(parserOptionsMapper), }); diff --git a/package.json b/package.json index 0d9e38e35..5ede43b8c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtytwo", + "name": "eslint-plugin-jsx-a11y-div-fourtythree", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index d5d25a4ba..84749901d 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -16,11 +16,25 @@ import { import { generateObjSchema, arraySchema } from '../util/schemas'; const actionVerbs = [ - 'submit', - 'finish', - 'delete', - 'ok', - 'apply', + 'advise', 'amplify', 'apply', 'arrange', 'ask', + 'boost', 'build', + 'call', 'click', 'close', 'commit', 'consult', 'compile', 'collect', 'contribute', 'create', 'cut', + 'decrease', 'delete', 'divide', 'drink', + 'eat', 'earn', 'enable', 'enter', 'execute', 'exit', 'expand', 'explain', + 'finish', 'forecast', 'fix', + 'generate', + 'handle', 'help', 'hire', 'hit', + 'improve', 'increase', + 'join', 'jump', + 'leave', 'let\'/s', 'list', 'listen', + 'magnify', 'make', 'manage', 'minimize', 'move', + 'ok', 'open', 'organise', 'oversee', + 'play', 'push', + 'read', 'reduce', 'run', + 'save', 'send', 'shout', 'sing', 'submit', 'support', + 'talk', 'trim', + 'visit', 'volunteer', 'vote', + 'watch', 'win', 'write', ]; const schema = generateObjSchema({ components: arraySchema }); @@ -42,33 +56,29 @@ module.exports = { if (typeCheck.indexOf(nodeType) === -1) { return; } + if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { return; } + const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); // Missing tabindex and role prop error. if ((tabindexProp === undefined) || (roleProp === undefined)) { - context.report({ - node, - message: 'Missing or incorrect attributes. Action verbs should have aria attributes or native HTML elements. Please define tabIndex="0" attribute and role="button" aria role in the div. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', - }); return; } const tabindexValue = getPropValue(tabindexProp); if (tabindexValue !== '0') { - context.report({ - node, - message: 'Missing or incorrect tabIndex attribute value. Action verbs should have aria attributes or native HTML elements. Please define tabIndex="0" attribute. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', - }); + return; } const roleValue = getPropValue(roleProp); if (roleValue !== 'button') { - context.report({ - node, - message: 'Missing or incorrect role value. Action verbs should have aria attributes or native HTML elements. Please define role="button" attribute. Refer to the rule defined by W3C: https://w3c.github.io/aria-practices/examples/button/button.html Otherwise, the first rule of aria is to not use aria with div if it can be achieved via native HTML, like button. Refer to the Living HTML standard on MDN website: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', - }); + return; } + context.report({ + node, + message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + }); }, }), }; diff --git a/src/util/hasAccessibleChild.js b/src/util/hasAccessibleChild.js index 4e4110941..f3cc33003 100644 --- a/src/util/hasAccessibleChild.js +++ b/src/util/hasAccessibleChild.js @@ -17,7 +17,7 @@ export default function hasAccessibleChild(node: JSXElement): boolean { ); case 'JSXExpressionContainer': if (child.expression.type === 'Identifier') { - return child.expression.name !== 'undefined'; + return (child.expression.name !== 'undefined') || (child.expression.value !== ''); } return true; default: From d96254e5a118f3d68355da053ec825af45c58ad9 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Sun, 21 Mar 2021 22:44:37 +0000 Subject: [PATCH 25/35] basic tests passed - no variable, or custom elements --- __tests__/src/util/hasApplyText-test.js | 99 ------------------------- src/rules/div-has-apply.js | 29 +++++--- src/util/hasAccessibleChild.js | 3 +- 3 files changed, 21 insertions(+), 110 deletions(-) delete mode 100644 __tests__/src/util/hasApplyText-test.js diff --git a/__tests__/src/util/hasApplyText-test.js b/__tests__/src/util/hasApplyText-test.js deleted file mode 100644 index 053fd046d..000000000 --- a/__tests__/src/util/hasApplyText-test.js +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-env jest */ -import hasApplyText from '../../../src/util/hasApplyText'; -import JSXElementMock from '../../../__mocks__/JSXElementMock'; -import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock'; -import JSXExpressionContainerMock from '../../../__mocks__/JSXExpressionContainerMock'; - -describe('hasApplyText', () => { - describe('has no children and does not set dangerouslySetInnerHTML', () => { - it('returns false', () => { - expect(hasApplyText(JSXElementMock('div', []))).toBe(false); - }); - }); - - describe('has no children and sets dangerouslySetInnerHTML', () => { - it('Returns true', () => { - const prop = JSXAttributeMock('dangerouslySetInnerHTML', true); - const element = JSXElementMock('div', [prop], []); - expect(hasApplyText(element)).toBe(true); - }); - }); - - describe('has apply text as children', () => { - it('Returns true for a Literal child, apply text', () => { - const child = { - type: 'Literal', - value: 'apply', - }; - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(true); // we expect that when
apply
is found then error mesage should be sent to the developer - // we expect that hasApplyText has
apply
and then the rule util hasApplyText and the rule divHasApply should send error message - }); - - it('Returns false for any other Literal child', () => { - const child = { - type: 'Literal', - value: 'orange', - }; - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(false); - }); - - // - it('Returns true for visible child JSXElement', () => { - const child = JSXElementMock('div', []); - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(true); - }); - - it('Returns true for JSXText Element', () => { - const child = { - type: 'JSXText', - value: 'apply', - }; - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(true); - }); - - // it('Returns false for hidden child JSXElement', () => { - // const ariaHiddenAttr = JSXAttributeMock('aria-hidden', true); - // const child = JSXElementMock('button', [ariaHiddenAttr]); - // const element = JSXElementMock('button', [], [child]); - // expect(hasApplyText(element)).toBe(false); - // }); - - it('Returns true for defined JSXExpressionContainer', () => { - const expression = { - type: 'Identifier', - name: 'apply', - }; - const child = JSXExpressionContainerMock(expression); - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(true); - }); - - it('Returns false for undefined JSXExpressionContainer', () => { - const expression = { - type: 'Identifier', - name: 'undefined', - }; - const child = JSXExpressionContainerMock(expression); - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(false); - }); - - it('Returns false for unknown child type', () => { - const child = { - type: 'Unknown', - }; - const element = JSXElementMock('div', [], [child]); - expect(hasApplyText(element)).toBe(false); - }); - - it('Returns true with children passed as a prop', () => { - const children = JSXAttributeMock('children', true); - const element = JSXElementMock('div', [children], []); - expect(hasApplyText(element)).toBe(true); - }); - }); -}); diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 84749901d..3ec7e0146 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -63,22 +63,31 @@ module.exports = { const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); + const tabindexValue = getPropValue(tabindexProp); + const roleValue = getPropValue(roleProp); // Missing tabindex and role prop error. - if ((tabindexProp === undefined) || (roleProp === undefined)) { + if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button'))) { + context.report({ + node, + message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + }); return; } - const tabindexValue = getPropValue(tabindexProp); - if (tabindexValue !== '0') { + + if ((tabindexValue !== '0') || (tabindexProp === undefined)) { + context.report({ + node, + message: 'Missing or incorrect tabIndex attribute value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + }); return; } - const roleValue = getPropValue(roleProp); - if (roleValue !== 'button') { - return; + + if ((roleValue !== 'button') || (roleProp === undefined)) { + context.report({ + node, + message: 'Missing or incorrect role value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + }); } - context.report({ - node, - message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', - }); }, }), }; diff --git a/src/util/hasAccessibleChild.js b/src/util/hasAccessibleChild.js index f3cc33003..822260caa 100644 --- a/src/util/hasAccessibleChild.js +++ b/src/util/hasAccessibleChild.js @@ -17,7 +17,8 @@ export default function hasAccessibleChild(node: JSXElement): boolean { ); case 'JSXExpressionContainer': if (child.expression.type === 'Identifier') { - return (child.expression.name !== 'undefined') || (child.expression.value !== ''); + // return (child.expression.name !== 'undefined') || (child.expression.value !== ''); + return child.expression.name !== 'undefined'; } return true; default: From 3af1c4c6aae269b89a97ef108ad56ec796e325b6 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Sun, 21 Mar 2021 23:49:55 +0000 Subject: [PATCH 26/35] before before changing logic of error messages --- __tests__/src/rules/heading-has-content-test.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/src/rules/heading-has-content-test.js b/__tests__/src/rules/heading-has-content-test.js index 7e762b1ff..fa8821c61 100644 --- a/__tests__/src/rules/heading-has-content-test.js +++ b/__tests__/src/rules/heading-has-content-test.js @@ -56,7 +56,7 @@ ruleTester.run('heading-has-content', rule, { invalid: [ // DEFAULT ELEMENT TESTS { code: '

', errors: [expectedError] }, - { code: '

', errors: [expectedError] }, + { code: '

', errors: [expectedError] }, { code: '

{undefined}

', errors: [expectedError] }, // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION diff --git a/package.json b/package.json index 5ede43b8c..4dd88160c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtythree", + "name": "eslint-plugin-jsx-a11y-div-fourtyfour", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ From ceb121108fa58da0f4482be8453845e84119d91d Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Sun, 21 Mar 2021 23:50:35 +0000 Subject: [PATCH 27/35] added or case --- src/rules/div-has-apply.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 3ec7e0146..115db7458 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -66,7 +66,8 @@ module.exports = { const tabindexValue = getPropValue(tabindexProp); const roleValue = getPropValue(roleProp); // Missing tabindex and role prop error. - if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button'))) { + if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button')) || + ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', From 8abf80481365374aa4d2d898544fb5b836c0dc7d Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Mon, 22 Mar 2021 18:33:18 +0000 Subject: [PATCH 28/35] wrong attribute value and missing attribute --- __tests__/src/rules/div-has-apply-test.js | 152 +++++++++++++++++++++- src/rules/div-has-apply.js | 6 +- 2 files changed, 154 insertions(+), 4 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index d16acf454..421bba855 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -19,7 +19,7 @@ import rule from '../../../src/rules/div-has-apply'; const ruleTester = new RuleTester(); const expectedErrorNoAttributes = { - message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + message: 'Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', type: 'JSXOpeningElement', }; @@ -426,6 +426,156 @@ ruleTester.run('div-has-apply', rule, { { code: '
win
', errors: [expectedErrorNoAttributes] }, { code: '
write
', errors: [expectedErrorNoAttributes] }, + // wrong attribute value: contentinfo and missing attribute: tabindex + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + + // wrong attribute value: tabindex="-1" and missing attribute: role="button" + { code: '
advise
', errors: [expectedErrorNoAttributes] }, + { code: '
amplify
', errors: [expectedErrorNoAttributes] }, + { code: '
apply
', errors: [expectedErrorNoAttributes] }, + { code: '
arrange
', errors: [expectedErrorNoAttributes] }, + { code: '
ask
', errors: [expectedErrorNoAttributes] }, + { code: '
boost
', errors: [expectedErrorNoAttributes] }, + { code: '
build
', errors: [expectedErrorNoAttributes] }, + { code: '
call
', errors: [expectedErrorNoAttributes] }, + { code: '
click
', errors: [expectedErrorNoAttributes] }, + { code: '
close
', errors: [expectedErrorNoAttributes] }, + { code: '
commit
', errors: [expectedErrorNoAttributes] }, + { code: '
consult
', errors: [expectedErrorNoAttributes] }, + { code: '
compile
', errors: [expectedErrorNoAttributes] }, + { code: '
collect
', errors: [expectedErrorNoAttributes] }, + { code: '
contribute
', errors: [expectedErrorNoAttributes] }, + { code: '
create
', errors: [expectedErrorNoAttributes] }, + { code: '
cut
', errors: [expectedErrorNoAttributes] }, + { code: '
decrease
', errors: [expectedErrorNoAttributes] }, + { code: '
delete
', errors: [expectedErrorNoAttributes] }, + { code: '
divide
', errors: [expectedErrorNoAttributes] }, + { code: '
drink
', errors: [expectedErrorNoAttributes] }, + { code: '
eat
', errors: [expectedErrorNoAttributes] }, + { code: '
earn
', errors: [expectedErrorNoAttributes] }, + { code: '
enable
', errors: [expectedErrorNoAttributes] }, + { code: '
enter
', errors: [expectedErrorNoAttributes] }, + { code: '
execute
', errors: [expectedErrorNoAttributes] }, + { code: '
exit
', errors: [expectedErrorNoAttributes] }, + { code: '
expand
', errors: [expectedErrorNoAttributes] }, + { code: '
explain
', errors: [expectedErrorNoAttributes] }, + { code: '
finish
', errors: [expectedErrorNoAttributes] }, + { code: '
forecast
', errors: [expectedErrorNoAttributes] }, + { code: '
fix
', errors: [expectedErrorNoAttributes] }, + { code: '
generate
', errors: [expectedErrorNoAttributes] }, + { code: '
handle
', errors: [expectedErrorNoAttributes] }, + { code: '
help
', errors: [expectedErrorNoAttributes] }, + { code: '
hire
', errors: [expectedErrorNoAttributes] }, + { code: '
hit
', errors: [expectedErrorNoAttributes] }, + { code: '
improve
', errors: [expectedErrorNoAttributes] }, + { code: '
increase
', errors: [expectedErrorNoAttributes] }, + { code: '
join
', errors: [expectedErrorNoAttributes] }, + { code: '
jump
', errors: [expectedErrorNoAttributes] }, + { code: '
leave
', errors: [expectedErrorNoAttributes] }, + { code: '
let\'/s
', errors: [expectedErrorNoAttributes] }, + { code: '
list
', errors: [expectedErrorNoAttributes] }, + { code: '
listen
', errors: [expectedErrorNoAttributes] }, + { code: '
magnify
', errors: [expectedErrorNoAttributes] }, + { code: '
make
', errors: [expectedErrorNoAttributes] }, + { code: '
manage
', errors: [expectedErrorNoAttributes] }, + { code: '
minimize
', errors: [expectedErrorNoAttributes] }, + { code: '
move
', errors: [expectedErrorNoAttributes] }, + { code: '
ok
', errors: [expectedErrorNoAttributes] }, + { code: '
open
', errors: [expectedErrorNoAttributes] }, + { code: '
organise
', errors: [expectedErrorNoAttributes] }, + { code: '
oversee
', errors: [expectedErrorNoAttributes] }, + { code: '
play
', errors: [expectedErrorNoAttributes] }, + { code: '
push
', errors: [expectedErrorNoAttributes] }, + { code: '
read
', errors: [expectedErrorNoAttributes] }, + { code: '
reduce
', errors: [expectedErrorNoAttributes] }, + { code: '
run
', errors: [expectedErrorNoAttributes] }, + { code: '
save
', errors: [expectedErrorNoAttributes] }, + { code: '
send
', errors: [expectedErrorNoAttributes] }, + { code: '
shout
', errors: [expectedErrorNoAttributes] }, + { code: '
sing
', errors: [expectedErrorNoAttributes] }, + { code: '
submit
', errors: [expectedErrorNoAttributes] }, + { code: '
support
', errors: [expectedErrorNoAttributes] }, + { code: '
talk
', errors: [expectedErrorNoAttributes] }, + { code: '
trim
', errors: [expectedErrorNoAttributes] }, + { code: '
visit
', errors: [expectedErrorNoAttributes] }, + { code: '
volunteer
', errors: [expectedErrorNoAttributes] }, + { code: '
vote
', errors: [expectedErrorNoAttributes] }, + { code: '
watch
', errors: [expectedErrorNoAttributes] }, + { code: '
win
', errors: [expectedErrorNoAttributes] }, + { code: '
write
', errors: [expectedErrorNoAttributes] }, + // role is missing { code: '
advise
', errors: [expectedErrorNoRole] }, { code: '
amplify
', errors: [expectedErrorNoRole] }, diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 115db7458..b2a3a393b 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -66,11 +66,11 @@ module.exports = { const tabindexValue = getPropValue(tabindexProp); const roleValue = getPropValue(roleProp); // Missing tabindex and role prop error. - if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button')) || - ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { + if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button')) + || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, - message: 'Missing or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + message: 'Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); return; } From 737801b2214a113ccea4d6451b5d4a5f0f4ecd4d Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Mon, 22 Mar 2021 22:57:49 +0000 Subject: [PATCH 29/35] testing what options, componentOptions, typeCheck and nodeType variables are again --- package.json | 5 +++-- src/rules/div-has-apply.js | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4dd88160c..01de4caed 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtyfour", + "name": "eslint-plugin-jsx-a11y-div-fourtyseven", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ @@ -69,7 +69,8 @@ "has": "^1.0.3", "jsx-ast-utils": "^3.2.0", "language-tags": "^1.0.5", - "minimatch": "^3.0.4" + "minimatch": "^3.0.4", + "proptypes": "^1.1.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7" diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index b2a3a393b..4c796535d 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -47,13 +47,14 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); - const options = context.options[0] || {}; - const componentOptions = options.components || []; - const typeCheck = ['div'].concat(componentOptions); - const nodeType = elementType(node); + const options = context.options[0] || {}; // [object Object] + const componentOptions = options.components || []; // Apply - comming from .eslintrc.js file + const typeCheck = ['div'].concat(componentOptions); // div, Apply + const nodeType = elementType(node); // Apply // Only check 'div*' elements and custom types. - if (typeCheck.indexOf(nodeType) === -1) { + if (typeCheck.indexOf(nodeType) === -1) { // answers the question: is the current node, which is Apply is defined in the componentOptions? + // for example, is the Apply custom component present in div,Apply return; } @@ -70,7 +71,7 @@ module.exports = { || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, - message: 'Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + message: `${options} meh ${componentOptions} meh ${typeCheck} meh ${nodeType} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, }); return; } From c763acb3d6d905c62b6a8fe64bb7b4d163d4ba22 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Tue, 23 Mar 2021 12:39:53 +0000 Subject: [PATCH 30/35] see if it works without TextChildValue, created VariableChildValue in comments --- package.json | 2 +- src/rules/div-has-apply.js | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 01de4caed..7107e7f08 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtyseven", + "name": "eslint-plugin-jsx-a11y-div-fourtyeight", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 4c796535d..70f28cac4 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -46,7 +46,9 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { - const literalChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); + const TextChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); + // TextChildValue.value is the text within the tag elements + // const VariableChildValue = node.parent.children.find((child) => child.type === 'JSXExpressionContainer' || child.type === 'Unknown'); const options = context.options[0] || {}; // [object Object] const componentOptions = options.components || []; // Apply - comming from .eslintrc.js file const typeCheck = ['div'].concat(componentOptions); // div, Apply @@ -58,10 +60,18 @@ module.exports = { return; } - if (actionVerbs.includes(literalChildValue && literalChildValue.value.toLowerCase()) === false) { + // if (actionVerbs.includes(VariableChildValue && VariableChildValue.value.toLowerCase()) === false) { + // return; + // } + + // if (actionVerbs.includes(TextChildValue && TextChildValue.value.toLowerCase()) === false) { + // return; + // } + if (actionVerbs.includes(TextChildValue.value.toLowerCase()) === false) { return; } + const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); const tabindexValue = getPropValue(tabindexProp); @@ -71,7 +81,7 @@ module.exports = { || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, - message: `${options} meh ${componentOptions} meh ${typeCheck} meh ${nodeType} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, + message: `${TextChildValue} meh ${TextChildValue.value} meh ${TextChildValue.type} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, }); return; } From f3f35881b19ee617f305a8c5737609c5b1ccca7b Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Tue, 23 Mar 2021 15:34:21 +0000 Subject: [PATCH 31/35] trial and error for looking at if variable value is equal to text apply - trying to access when expression.type='Identifier' in ast --- package.json | 2 +- src/rules/div-has-apply.js | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 7107e7f08..01d87176a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-fourtyeight", + "name": "eslint-plugin-jsx-a11y-div-sixtysix", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 70f28cac4..219e1790a 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -48,7 +48,10 @@ module.exports = { JSXOpeningElement: (node) => { const TextChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); // TextChildValue.value is the text within the tag elements - // const VariableChildValue = node.parent.children.find((child) => child.type === 'JSXExpressionContainer' || child.type === 'Unknown'); + // const VariableChildValue = node.parent.children.find((child) => child.type === 'JSXExpressionContainer' && child !== 'undefined'); + const expressioncontainer = node.parent.children.find((child) => child.type === 'JSXExpressionContainer'); + // => (expression.type === 'Identifier' && child.value !== 'undefined')); + const options = context.options[0] || {}; // [object Object] const componentOptions = options.components || []; // Apply - comming from .eslintrc.js file const typeCheck = ['div'].concat(componentOptions); // div, Apply @@ -60,18 +63,24 @@ module.exports = { return; } - // if (actionVerbs.includes(VariableChildValue && VariableChildValue.value.toLowerCase()) === false) { + // if (actionVerbs.includes(IdentifierChildValue && IdentifierChildValue.value.toLowerCase()) === false) { // return; // } + if (expressioncontainer === true) { + const IdentifierChildValue = expressioncontainer.expression.type === 'Identifier'; + context.report({ + node, + message: `${IdentifierChildValue.value} is a identifier value`, + }); + return; + } - // if (actionVerbs.includes(TextChildValue && TextChildValue.value.toLowerCase()) === false) { - // return; - // } - if (actionVerbs.includes(TextChildValue.value.toLowerCase()) === false) { + if (actionVerbs.includes(TextChildValue && TextChildValue.value.toLowerCase()) === false) { return; } + const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); const tabindexValue = getPropValue(tabindexProp); @@ -81,7 +90,8 @@ module.exports = { || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, - message: `${TextChildValue} meh ${TextChildValue.value} meh ${TextChildValue.type} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, + // message: `${IdentifierChildValue} meh ${IdentifierChildValue.value} meh ${IdentifierChildValue.type} text: ${TextChildValue} meh ${TextChildValue.value} meh ${TextChildValue.type} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, + message: 'Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); return; } From 08d3aa54e101ee94ba99641bb6d94a2312c04e97 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Tue, 23 Mar 2021 15:49:36 +0000 Subject: [PATCH 32/35]
{apply}
trying to access variable apply inside curly braces AND trying accessing variable value in heading -hasAccessibleChild util --- __tests__/src/rules/div-has-apply-test.js | 16 +++++++++++++--- src/util/hasAccessibleChild.js | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 421bba855..331127985 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -33,9 +33,9 @@ const expectedErrorNoRole = { type: 'JSXOpeningElement', }; -// const components = [{ -// components: ['Button', 'Apply'], -// }]; +const components = [{ + components: ['Button', 'Apply'], +}]; ruleTester.run('div-has-apply', rule, { valid: [ @@ -120,6 +120,16 @@ ruleTester.run('div-has-apply', rule, { { code: '
win
' }, { code: '
write
' }, + // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION + { code: 'Orange', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '{foo}', options: components }, + { code: '{foo.bar}', options: components }, + { code: '', options: components }, + { code: '', options: components }, + { code: '

' }, + ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS diff --git a/src/util/hasAccessibleChild.js b/src/util/hasAccessibleChild.js index 822260caa..d2791ccde 100644 --- a/src/util/hasAccessibleChild.js +++ b/src/util/hasAccessibleChild.js @@ -9,12 +9,12 @@ export default function hasAccessibleChild(node: JSXElement): boolean { switch (child.type) { case 'Literal': case 'JSXText': - return Boolean(child.value); + return Boolean(child.value); //exit if there is text within the tags case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), child.openingElement.attributes, - ); + ); // exit when the JSXElement is visible for screenreader case 'JSXExpressionContainer': if (child.expression.type === 'Identifier') { // return (child.expression.name !== 'undefined') || (child.expression.value !== ''); From c404e78606b035248319f30fe5f04b27584c3193 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Tue, 23 Mar 2021 16:04:45 +0000 Subject: [PATCH 33/35] hasAccessibleChild empty string is not linted neither when expression.name is undefined --- package.json | 2 +- src/util/hasAccessibleChild.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 01d87176a..58ad28fe4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-sixtysix", + "name": "eslint-plugin-jsx-a11y-div-sixtyeigth", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/util/hasAccessibleChild.js b/src/util/hasAccessibleChild.js index d2791ccde..b4080c52d 100644 --- a/src/util/hasAccessibleChild.js +++ b/src/util/hasAccessibleChild.js @@ -17,8 +17,8 @@ export default function hasAccessibleChild(node: JSXElement): boolean { ); // exit when the JSXElement is visible for screenreader case 'JSXExpressionContainer': if (child.expression.type === 'Identifier') { - // return (child.expression.name !== 'undefined') || (child.expression.value !== ''); - return child.expression.name !== 'undefined'; + return (child.expression.name !== 'undefined') || (child.expression.value !== ''); + // return child.expression.name !== 'undefined'; } return true; default: From 9165a02d23edfaf3ae7b8a52a6698785d3cc202d Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Tue, 23 Mar 2021 23:20:21 +0000 Subject: [PATCH 34/35] added custom element tests --- __tests__/src/rules/div-has-apply-test.js | 1224 ++++++++++++++++++++- package.json | 2 +- src/rules/div-has-apply.js | 33 +- src/util/hasAccessibleChild.js | 7 +- 4 files changed, 1229 insertions(+), 37 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index 331127985..cf7186dd6 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -32,6 +32,10 @@ const expectedErrorNoRole = { message: 'Missing or incorrect role value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', type: 'JSXOpeningElement', }; +const expectedErrorCustom = { + message: 'If custom element is an action word then the text within it should also be an action word. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + type: 'JSXOpeningElement', +}; const components = [{ components: ['Button', 'Apply'], @@ -121,22 +125,87 @@ ruleTester.run('div-has-apply', rule, { { code: '
write
' }, // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION - { code: 'Orange', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '{foo}', options: components }, - { code: '{foo.bar}', options: components }, - { code: '', options: components }, - { code: '', options: components }, - { code: '

' }, + + // limitation: I only test for custom element, I do not test when custom element name is any other action word from the list + // any action word //has limitation + { code: 'advise', options: components }, + { code: 'amplify', options: components }, + { code: 'apply', options: components }, + { code: 'arrange', options: components }, + { code: 'ask', options: components }, + { code: 'boost', options: components }, + { code: 'build', options: components }, + { code: 'call', options: components }, + { code: 'click', options: components }, + { code: 'close', options: components }, + { code: 'commit', options: components }, + { code: 'consult', options: components }, + { code: 'compile', options: components }, + { code: 'collect', options: components }, + { code: 'contribute', options: components }, + { code: 'create', options: components }, + { code: 'cut', options: components }, + { code: 'decrease', options: components }, + { code: 'delete', options: components }, + { code: 'divide', options: components }, + { code: 'drink', options: components }, + { code: 'eat', options: components }, + { code: 'earn', options: components }, + { code: 'enable', options: components }, + { code: 'enter', options: components }, + { code: 'execute', options: components }, + { code: 'exit', options: components }, + { code: 'expand', options: components }, + { code: 'explain', options: components }, + { code: 'finish', options: components }, + { code: 'forecast', options: components }, + { code: 'fix', options: components }, + { code: 'generate', options: components }, + { code: 'handle', options: components }, + { code: 'help', options: components }, + { code: 'hire', options: components }, + { code: 'hit', options: components }, + { code: 'improve', options: components }, + { code: 'increase', options: components }, + { code: 'join', options: components }, + { code: 'jump', options: components }, + { code: 'leave', options: components }, + { code: 'let\'/s', options: components }, + { code: 'list', options: components }, + { code: 'listen', options: components }, + { code: 'magnify', options: components }, + { code: 'make', options: components }, + { code: 'manage', options: components }, + { code: 'minimize', options: components }, + { code: 'move', options: components }, + { code: 'ok', options: components }, + { code: 'open', options: components }, + { code: 'organise', options: components }, + { code: 'oversee', options: components }, + { code: 'play', options: components }, + { code: 'push', options: components }, + { code: 'read', options: components }, + { code: 'run', options: components }, + { code: 'save', options: components }, + { code: 'send', options: components }, + { code: 'shout', options: components }, + { code: 'sing', options: components }, + { code: 'submit', options: components }, + { code: 'support', options: components }, + { code: 'talk', options: components }, + { code: 'trim', options: components }, + { code: 'visit', options: components }, + { code: 'volunteer', options: components }, + { code: 'vote', options: components }, + { code: 'watch', options: components }, + { code: 'win', options: components }, + { code: 'write', options: components }, ].map(parserOptionsMapper), invalid: [ // DEFAULT ELEMENT TESTS - // { code: '
orange
', errors: [expectedErrorNoAttributes] }, // any word within div that has the two attributes - - // action words within div that has the two attributes + // action words within div that missing the two attributes { code: '
advise
', errors: [expectedErrorNoAttributes] }, { code: '
amplify
', errors: [expectedErrorNoAttributes] }, { code: '
apply
', errors: [expectedErrorNoAttributes] }, @@ -1260,5 +1329,1138 @@ ruleTester.run('div-has-apply', rule, { { code: '
watch
', errors: [expectedErrorNoTabindex] }, { code: '
win
', errors: [expectedErrorNoTabindex] }, { code: '
write
', errors: [expectedErrorNoTabindex] }, + + // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION + // If custom element is an action word then the text within it should also be an action word: + { code: 'orange', errors: [expectedErrorCustom], options: components }, + + // the below tests are the same as above for invalid + // the only difference is that instead of div I check for custom tag + + // action words within div that missing the two attributes + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // undefined attributes + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // wrong attributes values -1 and navigation + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // wrong attributes values 1 and main + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // wrong attribute value: contentinfo and missing attribute: tabindex + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // wrong attribute value: tabindex="-1" and missing attribute: role="button" + { code: ' advise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' amplify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' apply', errors: [expectedErrorNoAttributes], options: components }, + { code: ' arrange', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ask', errors: [expectedErrorNoAttributes], options: components }, + { code: ' boost', errors: [expectedErrorNoAttributes], options: components }, + { code: ' build', errors: [expectedErrorNoAttributes], options: components }, + { code: ' call', errors: [expectedErrorNoAttributes], options: components }, + { code: ' click', errors: [expectedErrorNoAttributes], options: components }, + { code: ' close', errors: [expectedErrorNoAttributes], options: components }, + { code: ' commit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' consult', errors: [expectedErrorNoAttributes], options: components }, + { code: ' compile', errors: [expectedErrorNoAttributes], options: components }, + { code: ' collect', errors: [expectedErrorNoAttributes], options: components }, + { code: ' contribute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' create', errors: [expectedErrorNoAttributes], options: components }, + { code: ' cut', errors: [expectedErrorNoAttributes], options: components }, + { code: ' decrease', errors: [expectedErrorNoAttributes], options: components }, + { code: ' delete', errors: [expectedErrorNoAttributes], options: components }, + { code: ' divide', errors: [expectedErrorNoAttributes], options: components }, + { code: ' drink', errors: [expectedErrorNoAttributes], options: components }, + { code: ' eat', errors: [expectedErrorNoAttributes], options: components }, + { code: ' earn', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enable', errors: [expectedErrorNoAttributes], options: components }, + { code: ' enter', errors: [expectedErrorNoAttributes], options: components }, + { code: ' execute', errors: [expectedErrorNoAttributes], options: components }, + { code: ' exit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' expand', errors: [expectedErrorNoAttributes], options: components }, + { code: ' explain', errors: [expectedErrorNoAttributes], options: components }, + { code: ' finish', errors: [expectedErrorNoAttributes], options: components }, + { code: ' forecast', errors: [expectedErrorNoAttributes], options: components }, + { code: ' fix', errors: [expectedErrorNoAttributes], options: components }, + { code: ' generate', errors: [expectedErrorNoAttributes], options: components }, + { code: ' handle', errors: [expectedErrorNoAttributes], options: components }, + { code: ' help', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hire', errors: [expectedErrorNoAttributes], options: components }, + { code: ' hit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' improve', errors: [expectedErrorNoAttributes], options: components }, + { code: ' increase', errors: [expectedErrorNoAttributes], options: components }, + { code: ' join', errors: [expectedErrorNoAttributes], options: components }, + { code: ' jump', errors: [expectedErrorNoAttributes], options: components }, + { code: ' leave', errors: [expectedErrorNoAttributes], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoAttributes], options: components }, + { code: ' list', errors: [expectedErrorNoAttributes], options: components }, + { code: ' listen', errors: [expectedErrorNoAttributes], options: components }, + { code: ' magnify', errors: [expectedErrorNoAttributes], options: components }, + { code: ' make', errors: [expectedErrorNoAttributes], options: components }, + { code: ' manage', errors: [expectedErrorNoAttributes], options: components }, + { code: ' minimize', errors: [expectedErrorNoAttributes], options: components }, + { code: ' move', errors: [expectedErrorNoAttributes], options: components }, + { code: ' ok', errors: [expectedErrorNoAttributes], options: components }, + { code: ' open', errors: [expectedErrorNoAttributes], options: components }, + { code: ' organise', errors: [expectedErrorNoAttributes], options: components }, + { code: ' oversee', errors: [expectedErrorNoAttributes], options: components }, + { code: ' play', errors: [expectedErrorNoAttributes], options: components }, + { code: ' push', errors: [expectedErrorNoAttributes], options: components }, + { code: ' read', errors: [expectedErrorNoAttributes], options: components }, + { code: ' reduce', errors: [expectedErrorNoAttributes], options: components }, + { code: ' run', errors: [expectedErrorNoAttributes], options: components }, + { code: ' save', errors: [expectedErrorNoAttributes], options: components }, + { code: ' send', errors: [expectedErrorNoAttributes], options: components }, + { code: ' shout', errors: [expectedErrorNoAttributes], options: components }, + { code: ' sing', errors: [expectedErrorNoAttributes], options: components }, + { code: ' submit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' support', errors: [expectedErrorNoAttributes], options: components }, + { code: ' talk', errors: [expectedErrorNoAttributes], options: components }, + { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, + { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, + { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, + { code: ' win', errors: [expectedErrorNoAttributes], options: components }, + { code: ' write', errors: [expectedErrorNoAttributes], options: components }, + + // role is missing + { code: ' advise', errors: [expectedErrorNoRole], options: components }, + { code: ' amplify', errors: [expectedErrorNoRole], options: components }, + { code: ' apply', errors: [expectedErrorNoRole], options: components }, + { code: ' arrange', errors: [expectedErrorNoRole], options: components }, + { code: ' ask', errors: [expectedErrorNoRole], options: components }, + { code: ' boost', errors: [expectedErrorNoRole], options: components }, + { code: ' build', errors: [expectedErrorNoRole], options: components }, + { code: ' call', errors: [expectedErrorNoRole], options: components }, + { code: ' click', errors: [expectedErrorNoRole], options: components }, + { code: ' close', errors: [expectedErrorNoRole], options: components }, + { code: ' commit', errors: [expectedErrorNoRole], options: components }, + { code: ' consult', errors: [expectedErrorNoRole], options: components }, + { code: ' compile', errors: [expectedErrorNoRole], options: components }, + { code: ' collect', errors: [expectedErrorNoRole], options: components }, + { code: ' contribute', errors: [expectedErrorNoRole], options: components }, + { code: ' create', errors: [expectedErrorNoRole], options: components }, + { code: ' cut', errors: [expectedErrorNoRole], options: components }, + { code: ' decrease', errors: [expectedErrorNoRole], options: components }, + { code: ' delete', errors: [expectedErrorNoRole], options: components }, + { code: ' divide', errors: [expectedErrorNoRole], options: components }, + { code: ' drink', errors: [expectedErrorNoRole], options: components }, + { code: ' eat', errors: [expectedErrorNoRole], options: components }, + { code: ' earn', errors: [expectedErrorNoRole], options: components }, + { code: ' enable', errors: [expectedErrorNoRole], options: components }, + { code: ' enter', errors: [expectedErrorNoRole], options: components }, + { code: ' execute', errors: [expectedErrorNoRole], options: components }, + { code: ' exit', errors: [expectedErrorNoRole], options: components }, + { code: ' expand', errors: [expectedErrorNoRole], options: components }, + { code: ' explain', errors: [expectedErrorNoRole], options: components }, + { code: ' finish', errors: [expectedErrorNoRole], options: components }, + { code: ' forecast', errors: [expectedErrorNoRole], options: components }, + { code: ' fix', errors: [expectedErrorNoRole], options: components }, + { code: ' generate', errors: [expectedErrorNoRole], options: components }, + { code: ' handle', errors: [expectedErrorNoRole], options: components }, + { code: ' help', errors: [expectedErrorNoRole], options: components }, + { code: ' hire', errors: [expectedErrorNoRole], options: components }, + { code: ' hit', errors: [expectedErrorNoRole], options: components }, + { code: ' improve', errors: [expectedErrorNoRole], options: components }, + { code: ' increase', errors: [expectedErrorNoRole], options: components }, + { code: ' join', errors: [expectedErrorNoRole], options: components }, + { code: ' jump', errors: [expectedErrorNoRole], options: components }, + { code: ' leave', errors: [expectedErrorNoRole], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoRole], options: components }, + { code: ' list', errors: [expectedErrorNoRole], options: components }, + { code: ' listen', errors: [expectedErrorNoRole], options: components }, + { code: ' magnify', errors: [expectedErrorNoRole], options: components }, + { code: ' make', errors: [expectedErrorNoRole], options: components }, + { code: ' manage', errors: [expectedErrorNoRole], options: components }, + { code: ' minimize', errors: [expectedErrorNoRole], options: components }, + { code: ' move', errors: [expectedErrorNoRole], options: components }, + { code: ' ok', errors: [expectedErrorNoRole], options: components }, + { code: ' open', errors: [expectedErrorNoRole], options: components }, + { code: ' organise', errors: [expectedErrorNoRole], options: components }, + { code: ' oversee', errors: [expectedErrorNoRole], options: components }, + { code: ' play', errors: [expectedErrorNoRole], options: components }, + { code: ' push', errors: [expectedErrorNoRole], options: components }, + { code: ' read', errors: [expectedErrorNoRole], options: components }, + { code: ' reduce', errors: [expectedErrorNoRole], options: components }, + { code: ' run', errors: [expectedErrorNoRole], options: components }, + { code: ' save', errors: [expectedErrorNoRole], options: components }, + { code: ' send', errors: [expectedErrorNoRole], options: components }, + { code: ' shout', errors: [expectedErrorNoRole], options: components }, + { code: ' sing', errors: [expectedErrorNoRole], options: components }, + { code: ' submit', errors: [expectedErrorNoRole], options: components }, + { code: ' support', errors: [expectedErrorNoRole], options: components }, + { code: ' talk', errors: [expectedErrorNoRole], options: components }, + { code: ' trim', errors: [expectedErrorNoRole], options: components }, + { code: ' visit', errors: [expectedErrorNoRole], options: components }, + { code: ' volunteer', errors: [expectedErrorNoRole], options: components }, + { code: ' vote', errors: [expectedErrorNoRole], options: components }, + { code: ' watch', errors: [expectedErrorNoRole], options: components }, + { code: ' win', errors: [expectedErrorNoRole], options: components }, + { code: ' write', errors: [expectedErrorNoRole], options: components }, + + // role is undefined + { code: ' advise', errors: [expectedErrorNoRole], options: components }, + { code: ' amplify', errors: [expectedErrorNoRole], options: components }, + { code: ' apply', errors: [expectedErrorNoRole], options: components }, + { code: ' arrange', errors: [expectedErrorNoRole], options: components }, + { code: ' ask', errors: [expectedErrorNoRole], options: components }, + { code: ' boost', errors: [expectedErrorNoRole], options: components }, + { code: ' build', errors: [expectedErrorNoRole], options: components }, + { code: ' call', errors: [expectedErrorNoRole], options: components }, + { code: ' click', errors: [expectedErrorNoRole], options: components }, + { code: ' close', errors: [expectedErrorNoRole], options: components }, + { code: ' commit', errors: [expectedErrorNoRole], options: components }, + { code: ' consult', errors: [expectedErrorNoRole], options: components }, + { code: ' compile', errors: [expectedErrorNoRole], options: components }, + { code: ' collect', errors: [expectedErrorNoRole], options: components }, + { code: ' contribute', errors: [expectedErrorNoRole], options: components }, + { code: ' create', errors: [expectedErrorNoRole], options: components }, + { code: ' cut', errors: [expectedErrorNoRole], options: components }, + { code: ' decrease', errors: [expectedErrorNoRole], options: components }, + { code: ' delete', errors: [expectedErrorNoRole], options: components }, + { code: ' divide', errors: [expectedErrorNoRole], options: components }, + { code: ' drink', errors: [expectedErrorNoRole], options: components }, + { code: ' eat', errors: [expectedErrorNoRole], options: components }, + { code: ' earn', errors: [expectedErrorNoRole], options: components }, + { code: ' enable', errors: [expectedErrorNoRole], options: components }, + { code: ' enter', errors: [expectedErrorNoRole], options: components }, + { code: ' execute', errors: [expectedErrorNoRole], options: components }, + { code: ' exit', errors: [expectedErrorNoRole], options: components }, + { code: ' expand', errors: [expectedErrorNoRole], options: components }, + { code: ' explain', errors: [expectedErrorNoRole], options: components }, + { code: ' finish', errors: [expectedErrorNoRole], options: components }, + { code: ' forecast', errors: [expectedErrorNoRole], options: components }, + { code: ' fix', errors: [expectedErrorNoRole], options: components }, + { code: ' generate', errors: [expectedErrorNoRole], options: components }, + { code: ' handle', errors: [expectedErrorNoRole], options: components }, + { code: ' help', errors: [expectedErrorNoRole], options: components }, + { code: ' hire', errors: [expectedErrorNoRole], options: components }, + { code: ' hit', errors: [expectedErrorNoRole], options: components }, + { code: ' improve', errors: [expectedErrorNoRole], options: components }, + { code: ' increase', errors: [expectedErrorNoRole], options: components }, + { code: ' join', errors: [expectedErrorNoRole], options: components }, + { code: ' jump', errors: [expectedErrorNoRole], options: components }, + { code: ' leave', errors: [expectedErrorNoRole], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoRole], options: components }, + { code: ' list', errors: [expectedErrorNoRole], options: components }, + { code: ' listen', errors: [expectedErrorNoRole], options: components }, + { code: ' magnify', errors: [expectedErrorNoRole], options: components }, + { code: ' make', errors: [expectedErrorNoRole], options: components }, + { code: ' manage', errors: [expectedErrorNoRole], options: components }, + { code: ' minimize', errors: [expectedErrorNoRole], options: components }, + { code: ' move', errors: [expectedErrorNoRole], options: components }, + { code: ' ok', errors: [expectedErrorNoRole], options: components }, + { code: ' open', errors: [expectedErrorNoRole], options: components }, + { code: ' organise', errors: [expectedErrorNoRole], options: components }, + { code: ' oversee', errors: [expectedErrorNoRole], options: components }, + { code: ' play', errors: [expectedErrorNoRole], options: components }, + { code: ' push', errors: [expectedErrorNoRole], options: components }, + { code: ' read', errors: [expectedErrorNoRole], options: components }, + { code: ' reduce', errors: [expectedErrorNoRole], options: components }, + { code: ' run', errors: [expectedErrorNoRole], options: components }, + { code: ' save', errors: [expectedErrorNoRole], options: components }, + { code: ' send', errors: [expectedErrorNoRole], options: components }, + { code: ' shout', errors: [expectedErrorNoRole], options: components }, + { code: ' sing', errors: [expectedErrorNoRole], options: components }, + { code: ' submit', errors: [expectedErrorNoRole], options: components }, + { code: ' support', errors: [expectedErrorNoRole], options: components }, + { code: ' talk', errors: [expectedErrorNoRole], options: components }, + { code: ' trim', errors: [expectedErrorNoRole], options: components }, + { code: ' visit', errors: [expectedErrorNoRole], options: components }, + { code: ' volunteer', errors: [expectedErrorNoRole], options: components }, + { code: ' vote', errors: [expectedErrorNoRole], options: components }, + { code: ' watch', errors: [expectedErrorNoRole], options: components }, + { code: ' win', errors: [expectedErrorNoRole], options: components }, + { code: ' write', errors: [expectedErrorNoRole], options: components }, + + // role's value is an empty string + { code: ' advise', errors: [expectedErrorNoRole], options: components }, + { code: ' amplify', errors: [expectedErrorNoRole], options: components }, + { code: ' apply', errors: [expectedErrorNoRole], options: components }, + { code: ' arrange', errors: [expectedErrorNoRole], options: components }, + { code: ' ask', errors: [expectedErrorNoRole], options: components }, + { code: ' boost', errors: [expectedErrorNoRole], options: components }, + { code: ' build', errors: [expectedErrorNoRole], options: components }, + { code: ' call', errors: [expectedErrorNoRole], options: components }, + { code: ' click', errors: [expectedErrorNoRole], options: components }, + { code: ' close', errors: [expectedErrorNoRole], options: components }, + { code: ' commit', errors: [expectedErrorNoRole], options: components }, + { code: ' consult', errors: [expectedErrorNoRole], options: components }, + { code: ' compile', errors: [expectedErrorNoRole], options: components }, + { code: ' collect', errors: [expectedErrorNoRole], options: components }, + { code: ' contribute', errors: [expectedErrorNoRole], options: components }, + { code: ' create', errors: [expectedErrorNoRole], options: components }, + { code: ' cut', errors: [expectedErrorNoRole], options: components }, + { code: ' decrease', errors: [expectedErrorNoRole], options: components }, + { code: ' delete', errors: [expectedErrorNoRole], options: components }, + { code: ' divide', errors: [expectedErrorNoRole], options: components }, + { code: ' drink', errors: [expectedErrorNoRole], options: components }, + { code: ' eat', errors: [expectedErrorNoRole], options: components }, + { code: ' earn', errors: [expectedErrorNoRole], options: components }, + { code: ' enable', errors: [expectedErrorNoRole], options: components }, + { code: ' enter', errors: [expectedErrorNoRole], options: components }, + { code: ' execute', errors: [expectedErrorNoRole], options: components }, + { code: ' exit', errors: [expectedErrorNoRole], options: components }, + { code: ' expand', errors: [expectedErrorNoRole], options: components }, + { code: ' explain', errors: [expectedErrorNoRole], options: components }, + { code: ' finish', errors: [expectedErrorNoRole], options: components }, + { code: ' forecast', errors: [expectedErrorNoRole], options: components }, + { code: ' fix', errors: [expectedErrorNoRole], options: components }, + { code: ' generate', errors: [expectedErrorNoRole], options: components }, + { code: ' handle', errors: [expectedErrorNoRole], options: components }, + { code: ' help', errors: [expectedErrorNoRole], options: components }, + { code: ' hire', errors: [expectedErrorNoRole], options: components }, + { code: ' hit', errors: [expectedErrorNoRole], options: components }, + { code: ' improve', errors: [expectedErrorNoRole], options: components }, + { code: ' increase', errors: [expectedErrorNoRole], options: components }, + { code: ' join', errors: [expectedErrorNoRole], options: components }, + { code: ' jump', errors: [expectedErrorNoRole], options: components }, + { code: ' leave', errors: [expectedErrorNoRole], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoRole], options: components }, + { code: ' list', errors: [expectedErrorNoRole], options: components }, + { code: ' listen', errors: [expectedErrorNoRole], options: components }, + { code: ' magnify', errors: [expectedErrorNoRole], options: components }, + { code: ' make', errors: [expectedErrorNoRole], options: components }, + { code: ' manage', errors: [expectedErrorNoRole], options: components }, + { code: ' minimize', errors: [expectedErrorNoRole], options: components }, + { code: ' move', errors: [expectedErrorNoRole], options: components }, + { code: ' ok', errors: [expectedErrorNoRole], options: components }, + { code: ' open', errors: [expectedErrorNoRole], options: components }, + { code: ' organise', errors: [expectedErrorNoRole], options: components }, + { code: ' oversee', errors: [expectedErrorNoRole], options: components }, + { code: ' play', errors: [expectedErrorNoRole], options: components }, + { code: ' push', errors: [expectedErrorNoRole], options: components }, + { code: ' read', errors: [expectedErrorNoRole], options: components }, + { code: ' reduce', errors: [expectedErrorNoRole], options: components }, + { code: ' run', errors: [expectedErrorNoRole], options: components }, + { code: ' save', errors: [expectedErrorNoRole], options: components }, + { code: ' send', errors: [expectedErrorNoRole], options: components }, + { code: ' shout', errors: [expectedErrorNoRole], options: components }, + { code: ' sing', errors: [expectedErrorNoRole], options: components }, + { code: ' submit', errors: [expectedErrorNoRole], options: components }, + { code: ' support', errors: [expectedErrorNoRole], options: components }, + { code: ' talk', errors: [expectedErrorNoRole], options: components }, + { code: ' trim', errors: [expectedErrorNoRole], options: components }, + { code: ' visit', errors: [expectedErrorNoRole], options: components }, + { code: ' volunteer', errors: [expectedErrorNoRole], options: components }, + { code: ' vote', errors: [expectedErrorNoRole], options: components }, + { code: ' watch', errors: [expectedErrorNoRole], options: components }, + { code: ' win', errors: [expectedErrorNoRole], options: components }, + { code: ' write', errors: [expectedErrorNoRole], options: components }, + + // role's value is not button + { code: ' advise', errors: [expectedErrorNoRole], options: components }, + { code: ' amplify', errors: [expectedErrorNoRole], options: components }, + { code: ' apply', errors: [expectedErrorNoRole], options: components }, + { code: ' arrange', errors: [expectedErrorNoRole], options: components }, + { code: ' ask', errors: [expectedErrorNoRole], options: components }, + { code: ' boost', errors: [expectedErrorNoRole], options: components }, + { code: ' build', errors: [expectedErrorNoRole], options: components }, + { code: ' call', errors: [expectedErrorNoRole], options: components }, + { code: ' click', errors: [expectedErrorNoRole], options: components }, + { code: ' close', errors: [expectedErrorNoRole], options: components }, + { code: ' commit', errors: [expectedErrorNoRole], options: components }, + { code: ' consult', errors: [expectedErrorNoRole], options: components }, + { code: ' compile', errors: [expectedErrorNoRole], options: components }, + { code: ' collect', errors: [expectedErrorNoRole], options: components }, + { code: ' contribute', errors: [expectedErrorNoRole], options: components }, + { code: ' create', errors: [expectedErrorNoRole], options: components }, + { code: ' cut', errors: [expectedErrorNoRole], options: components }, + { code: ' decrease', errors: [expectedErrorNoRole], options: components }, + { code: ' delete', errors: [expectedErrorNoRole], options: components }, + { code: ' divide', errors: [expectedErrorNoRole], options: components }, + { code: ' drink', errors: [expectedErrorNoRole], options: components }, + { code: ' eat', errors: [expectedErrorNoRole], options: components }, + { code: ' earn', errors: [expectedErrorNoRole], options: components }, + { code: ' enable', errors: [expectedErrorNoRole], options: components }, + { code: ' enter', errors: [expectedErrorNoRole], options: components }, + { code: ' execute', errors: [expectedErrorNoRole], options: components }, + { code: ' exit', errors: [expectedErrorNoRole], options: components }, + { code: ' expand', errors: [expectedErrorNoRole], options: components }, + { code: ' explain', errors: [expectedErrorNoRole], options: components }, + { code: ' finish', errors: [expectedErrorNoRole], options: components }, + { code: ' forecast', errors: [expectedErrorNoRole], options: components }, + { code: ' fix', errors: [expectedErrorNoRole], options: components }, + { code: ' generate', errors: [expectedErrorNoRole], options: components }, + { code: ' handle', errors: [expectedErrorNoRole], options: components }, + { code: ' help', errors: [expectedErrorNoRole], options: components }, + { code: ' hire', errors: [expectedErrorNoRole], options: components }, + { code: ' hit', errors: [expectedErrorNoRole], options: components }, + { code: ' improve', errors: [expectedErrorNoRole], options: components }, + { code: ' increase', errors: [expectedErrorNoRole], options: components }, + { code: ' join', errors: [expectedErrorNoRole], options: components }, + { code: ' jump', errors: [expectedErrorNoRole], options: components }, + { code: ' leave', errors: [expectedErrorNoRole], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoRole], options: components }, + { code: ' list', errors: [expectedErrorNoRole], options: components }, + { code: ' listen', errors: [expectedErrorNoRole], options: components }, + { code: ' magnify', errors: [expectedErrorNoRole], options: components }, + { code: ' make', errors: [expectedErrorNoRole], options: components }, + { code: ' manage', errors: [expectedErrorNoRole], options: components }, + { code: ' minimize', errors: [expectedErrorNoRole], options: components }, + { code: ' move', errors: [expectedErrorNoRole], options: components }, + { code: ' ok', errors: [expectedErrorNoRole], options: components }, + { code: ' open', errors: [expectedErrorNoRole], options: components }, + { code: ' organise', errors: [expectedErrorNoRole], options: components }, + { code: ' oversee', errors: [expectedErrorNoRole], options: components }, + { code: ' play', errors: [expectedErrorNoRole], options: components }, + { code: ' push', errors: [expectedErrorNoRole], options: components }, + { code: ' read', errors: [expectedErrorNoRole], options: components }, + { code: ' reduce', errors: [expectedErrorNoRole], options: components }, + { code: ' run', errors: [expectedErrorNoRole], options: components }, + { code: ' save', errors: [expectedErrorNoRole], options: components }, + { code: ' send', errors: [expectedErrorNoRole], options: components }, + { code: ' shout', errors: [expectedErrorNoRole], options: components }, + { code: ' sing', errors: [expectedErrorNoRole], options: components }, + { code: ' submit', errors: [expectedErrorNoRole], options: components }, + { code: ' support', errors: [expectedErrorNoRole], options: components }, + { code: ' talk', errors: [expectedErrorNoRole], options: components }, + { code: ' trim', errors: [expectedErrorNoRole], options: components }, + { code: ' visit', errors: [expectedErrorNoRole], options: components }, + { code: ' volunteer', errors: [expectedErrorNoRole], options: components }, + { code: ' vote', errors: [expectedErrorNoRole], options: components }, + { code: ' watch', errors: [expectedErrorNoRole], options: components }, + { code: ' win', errors: [expectedErrorNoRole], options: components }, + { code: ' write', errors: [expectedErrorNoRole], options: components }, + + // tabindex is missing + { code: ' advise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' amplify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' apply', errors: [expectedErrorNoTabindex], options: components }, + { code: ' arrange', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ask', errors: [expectedErrorNoTabindex], options: components }, + { code: ' boost', errors: [expectedErrorNoTabindex], options: components }, + { code: ' build', errors: [expectedErrorNoTabindex], options: components }, + { code: ' call', errors: [expectedErrorNoTabindex], options: components }, + { code: ' click', errors: [expectedErrorNoTabindex], options: components }, + { code: ' close', errors: [expectedErrorNoTabindex], options: components }, + { code: ' commit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' consult', errors: [expectedErrorNoTabindex], options: components }, + { code: ' compile', errors: [expectedErrorNoTabindex], options: components }, + { code: ' collect', errors: [expectedErrorNoTabindex], options: components }, + { code: ' contribute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' create', errors: [expectedErrorNoTabindex], options: components }, + { code: ' cut', errors: [expectedErrorNoTabindex], options: components }, + { code: ' decrease', errors: [expectedErrorNoTabindex], options: components }, + { code: ' delete', errors: [expectedErrorNoTabindex], options: components }, + { code: ' divide', errors: [expectedErrorNoTabindex], options: components }, + { code: ' drink', errors: [expectedErrorNoTabindex], options: components }, + { code: ' eat', errors: [expectedErrorNoTabindex], options: components }, + { code: ' earn', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enable', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enter', errors: [expectedErrorNoTabindex], options: components }, + { code: ' execute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' exit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' expand', errors: [expectedErrorNoTabindex], options: components }, + { code: ' explain', errors: [expectedErrorNoTabindex], options: components }, + { code: ' finish', errors: [expectedErrorNoTabindex], options: components }, + { code: ' forecast', errors: [expectedErrorNoTabindex], options: components }, + { code: ' fix', errors: [expectedErrorNoTabindex], options: components }, + { code: ' generate', errors: [expectedErrorNoTabindex], options: components }, + { code: ' handle', errors: [expectedErrorNoTabindex], options: components }, + { code: ' help', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hire', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' improve', errors: [expectedErrorNoTabindex], options: components }, + { code: ' increase', errors: [expectedErrorNoTabindex], options: components }, + { code: ' join', errors: [expectedErrorNoTabindex], options: components }, + { code: ' jump', errors: [expectedErrorNoTabindex], options: components }, + { code: ' leave', errors: [expectedErrorNoTabindex], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoTabindex], options: components }, + { code: ' list', errors: [expectedErrorNoTabindex], options: components }, + { code: ' listen', errors: [expectedErrorNoTabindex], options: components }, + { code: ' magnify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' make', errors: [expectedErrorNoTabindex], options: components }, + { code: ' manage', errors: [expectedErrorNoTabindex], options: components }, + { code: ' minimize', errors: [expectedErrorNoTabindex], options: components }, + { code: ' move', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ok', errors: [expectedErrorNoTabindex], options: components }, + { code: ' open', errors: [expectedErrorNoTabindex], options: components }, + { code: ' organise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' oversee', errors: [expectedErrorNoTabindex], options: components }, + { code: ' play', errors: [expectedErrorNoTabindex], options: components }, + { code: ' push', errors: [expectedErrorNoTabindex], options: components }, + { code: ' read', errors: [expectedErrorNoTabindex], options: components }, + { code: ' reduce', errors: [expectedErrorNoTabindex], options: components }, + { code: ' run', errors: [expectedErrorNoTabindex], options: components }, + { code: ' save', errors: [expectedErrorNoTabindex], options: components }, + { code: ' send', errors: [expectedErrorNoTabindex], options: components }, + { code: ' shout', errors: [expectedErrorNoTabindex], options: components }, + { code: ' sing', errors: [expectedErrorNoTabindex], options: components }, + { code: ' submit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' support', errors: [expectedErrorNoTabindex], options: components }, + { code: ' talk', errors: [expectedErrorNoTabindex], options: components }, + { code: ' trim', errors: [expectedErrorNoTabindex], options: components }, + { code: ' visit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' volunteer', errors: [expectedErrorNoTabindex], options: components }, + { code: ' vote', errors: [expectedErrorNoTabindex], options: components }, + { code: ' watch', errors: [expectedErrorNoTabindex], options: components }, + { code: ' win', errors: [expectedErrorNoTabindex], options: components }, + { code: ' write', errors: [expectedErrorNoTabindex], options: components }, + + // tabindex is undefined + { code: ' advise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' amplify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' apply', errors: [expectedErrorNoTabindex], options: components }, + { code: ' arrange', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ask', errors: [expectedErrorNoTabindex], options: components }, + { code: ' boost', errors: [expectedErrorNoTabindex], options: components }, + { code: ' build', errors: [expectedErrorNoTabindex], options: components }, + { code: ' call', errors: [expectedErrorNoTabindex], options: components }, + { code: ' click', errors: [expectedErrorNoTabindex], options: components }, + { code: ' close', errors: [expectedErrorNoTabindex], options: components }, + { code: ' commit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' consult', errors: [expectedErrorNoTabindex], options: components }, + { code: ' compile', errors: [expectedErrorNoTabindex], options: components }, + { code: ' collect', errors: [expectedErrorNoTabindex], options: components }, + { code: ' contribute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' create', errors: [expectedErrorNoTabindex], options: components }, + { code: ' cut', errors: [expectedErrorNoTabindex], options: components }, + { code: ' decrease', errors: [expectedErrorNoTabindex], options: components }, + { code: ' delete', errors: [expectedErrorNoTabindex], options: components }, + { code: ' divide', errors: [expectedErrorNoTabindex], options: components }, + { code: ' drink', errors: [expectedErrorNoTabindex], options: components }, + { code: ' eat', errors: [expectedErrorNoTabindex], options: components }, + { code: ' earn', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enable', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enter', errors: [expectedErrorNoTabindex], options: components }, + { code: ' execute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' exit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' expand', errors: [expectedErrorNoTabindex], options: components }, + { code: ' explain', errors: [expectedErrorNoTabindex], options: components }, + { code: ' finish', errors: [expectedErrorNoTabindex], options: components }, + { code: ' forecast', errors: [expectedErrorNoTabindex], options: components }, + { code: ' fix', errors: [expectedErrorNoTabindex], options: components }, + { code: ' generate', errors: [expectedErrorNoTabindex], options: components }, + { code: ' handle', errors: [expectedErrorNoTabindex], options: components }, + { code: ' help', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hire', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' improve', errors: [expectedErrorNoTabindex], options: components }, + { code: ' increase', errors: [expectedErrorNoTabindex], options: components }, + { code: ' join', errors: [expectedErrorNoTabindex], options: components }, + { code: ' jump', errors: [expectedErrorNoTabindex], options: components }, + { code: ' leave', errors: [expectedErrorNoTabindex], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoTabindex], options: components }, + { code: ' list', errors: [expectedErrorNoTabindex], options: components }, + { code: ' listen', errors: [expectedErrorNoTabindex], options: components }, + { code: ' magnify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' make', errors: [expectedErrorNoTabindex], options: components }, + { code: ' manage', errors: [expectedErrorNoTabindex], options: components }, + { code: ' minimize', errors: [expectedErrorNoTabindex], options: components }, + { code: ' move', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ok', errors: [expectedErrorNoTabindex], options: components }, + { code: ' open', errors: [expectedErrorNoTabindex], options: components }, + { code: ' organise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' oversee', errors: [expectedErrorNoTabindex], options: components }, + { code: ' play', errors: [expectedErrorNoTabindex], options: components }, + { code: ' push', errors: [expectedErrorNoTabindex], options: components }, + { code: ' read', errors: [expectedErrorNoTabindex], options: components }, + { code: ' reduce', errors: [expectedErrorNoTabindex], options: components }, + { code: ' run', errors: [expectedErrorNoTabindex], options: components }, + { code: ' save', errors: [expectedErrorNoTabindex], options: components }, + { code: ' send', errors: [expectedErrorNoTabindex], options: components }, + { code: ' shout', errors: [expectedErrorNoTabindex], options: components }, + { code: ' sing', errors: [expectedErrorNoTabindex], options: components }, + { code: ' submit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' support', errors: [expectedErrorNoTabindex], options: components }, + { code: ' talk', errors: [expectedErrorNoTabindex], options: components }, + { code: ' trim', errors: [expectedErrorNoTabindex], options: components }, + { code: ' visit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' volunteer', errors: [expectedErrorNoTabindex], options: components }, + { code: ' vote', errors: [expectedErrorNoTabindex], options: components }, + { code: ' watch', errors: [expectedErrorNoTabindex], options: components }, + { code: ' win', errors: [expectedErrorNoTabindex], options: components }, + { code: ' write', errors: [expectedErrorNoTabindex], options: components }, + + // tabindex's value is an empty string + { code: ' advise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' amplify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' apply', errors: [expectedErrorNoTabindex], options: components }, + { code: ' arrange', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ask', errors: [expectedErrorNoTabindex], options: components }, + { code: ' boost', errors: [expectedErrorNoTabindex], options: components }, + { code: ' build', errors: [expectedErrorNoTabindex], options: components }, + { code: ' call', errors: [expectedErrorNoTabindex], options: components }, + { code: ' click', errors: [expectedErrorNoTabindex], options: components }, + { code: ' close', errors: [expectedErrorNoTabindex], options: components }, + { code: ' commit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' consult', errors: [expectedErrorNoTabindex], options: components }, + { code: ' compile', errors: [expectedErrorNoTabindex], options: components }, + { code: ' collect', errors: [expectedErrorNoTabindex], options: components }, + { code: ' contribute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' create', errors: [expectedErrorNoTabindex], options: components }, + { code: ' cut', errors: [expectedErrorNoTabindex], options: components }, + { code: ' decrease', errors: [expectedErrorNoTabindex], options: components }, + { code: ' delete', errors: [expectedErrorNoTabindex], options: components }, + { code: ' divide', errors: [expectedErrorNoTabindex], options: components }, + { code: ' drink', errors: [expectedErrorNoTabindex], options: components }, + { code: ' eat', errors: [expectedErrorNoTabindex], options: components }, + { code: ' earn', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enable', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enter', errors: [expectedErrorNoTabindex], options: components }, + { code: ' execute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' exit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' expand', errors: [expectedErrorNoTabindex], options: components }, + { code: ' explain', errors: [expectedErrorNoTabindex], options: components }, + { code: ' finish', errors: [expectedErrorNoTabindex], options: components }, + { code: ' forecast', errors: [expectedErrorNoTabindex], options: components }, + { code: ' fix', errors: [expectedErrorNoTabindex], options: components }, + { code: ' generate', errors: [expectedErrorNoTabindex], options: components }, + { code: ' handle', errors: [expectedErrorNoTabindex], options: components }, + { code: ' help', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hire', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' improve', errors: [expectedErrorNoTabindex], options: components }, + { code: ' increase', errors: [expectedErrorNoTabindex], options: components }, + { code: ' join', errors: [expectedErrorNoTabindex], options: components }, + { code: ' jump', errors: [expectedErrorNoTabindex], options: components }, + { code: ' leave', errors: [expectedErrorNoTabindex], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoTabindex], options: components }, + { code: ' list', errors: [expectedErrorNoTabindex], options: components }, + { code: ' listen', errors: [expectedErrorNoTabindex], options: components }, + { code: ' magnify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' make', errors: [expectedErrorNoTabindex], options: components }, + { code: ' manage', errors: [expectedErrorNoTabindex], options: components }, + { code: ' minimize', errors: [expectedErrorNoTabindex], options: components }, + { code: ' move', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ok', errors: [expectedErrorNoTabindex], options: components }, + { code: ' open', errors: [expectedErrorNoTabindex], options: components }, + { code: ' organise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' oversee', errors: [expectedErrorNoTabindex], options: components }, + { code: ' play', errors: [expectedErrorNoTabindex], options: components }, + { code: ' push', errors: [expectedErrorNoTabindex], options: components }, + { code: ' read', errors: [expectedErrorNoTabindex], options: components }, + { code: ' reduce', errors: [expectedErrorNoTabindex], options: components }, + { code: ' run', errors: [expectedErrorNoTabindex], options: components }, + { code: ' save', errors: [expectedErrorNoTabindex], options: components }, + { code: ' send', errors: [expectedErrorNoTabindex], options: components }, + { code: ' shout', errors: [expectedErrorNoTabindex], options: components }, + { code: ' sing', errors: [expectedErrorNoTabindex], options: components }, + { code: ' submit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' support', errors: [expectedErrorNoTabindex], options: components }, + { code: ' talk', errors: [expectedErrorNoTabindex], options: components }, + { code: ' trim', errors: [expectedErrorNoTabindex], options: components }, + { code: ' visit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' volunteer', errors: [expectedErrorNoTabindex], options: components }, + { code: ' vote', errors: [expectedErrorNoTabindex], options: components }, + { code: ' watch', errors: [expectedErrorNoTabindex], options: components }, + { code: ' win', errors: [expectedErrorNoTabindex], options: components }, + { code: ' write', errors: [expectedErrorNoTabindex], options: components }, + + // tabindex's value is -1 + { code: ' advise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' amplify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' apply', errors: [expectedErrorNoTabindex], options: components }, + { code: ' arrange', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ask', errors: [expectedErrorNoTabindex], options: components }, + { code: ' boost', errors: [expectedErrorNoTabindex], options: components }, + { code: ' build', errors: [expectedErrorNoTabindex], options: components }, + { code: ' call', errors: [expectedErrorNoTabindex], options: components }, + { code: ' click', errors: [expectedErrorNoTabindex], options: components }, + { code: ' close', errors: [expectedErrorNoTabindex], options: components }, + { code: ' commit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' consult', errors: [expectedErrorNoTabindex], options: components }, + { code: ' compile', errors: [expectedErrorNoTabindex], options: components }, + { code: ' collect', errors: [expectedErrorNoTabindex], options: components }, + { code: ' contribute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' create', errors: [expectedErrorNoTabindex], options: components }, + { code: ' cut', errors: [expectedErrorNoTabindex], options: components }, + { code: ' decrease', errors: [expectedErrorNoTabindex], options: components }, + { code: ' delete', errors: [expectedErrorNoTabindex], options: components }, + { code: ' divide', errors: [expectedErrorNoTabindex], options: components }, + { code: ' drink', errors: [expectedErrorNoTabindex], options: components }, + { code: ' eat', errors: [expectedErrorNoTabindex], options: components }, + { code: ' earn', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enable', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enter', errors: [expectedErrorNoTabindex], options: components }, + { code: ' execute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' exit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' expand', errors: [expectedErrorNoTabindex], options: components }, + { code: ' explain', errors: [expectedErrorNoTabindex], options: components }, + { code: ' finish', errors: [expectedErrorNoTabindex], options: components }, + { code: ' forecast', errors: [expectedErrorNoTabindex], options: components }, + { code: ' fix', errors: [expectedErrorNoTabindex], options: components }, + { code: ' generate', errors: [expectedErrorNoTabindex], options: components }, + { code: ' handle', errors: [expectedErrorNoTabindex], options: components }, + { code: ' help', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hire', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' improve', errors: [expectedErrorNoTabindex], options: components }, + { code: ' increase', errors: [expectedErrorNoTabindex], options: components }, + { code: ' join', errors: [expectedErrorNoTabindex], options: components }, + { code: ' jump', errors: [expectedErrorNoTabindex], options: components }, + { code: ' leave', errors: [expectedErrorNoTabindex], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoTabindex], options: components }, + { code: ' list', errors: [expectedErrorNoTabindex], options: components }, + { code: ' listen', errors: [expectedErrorNoTabindex], options: components }, + { code: ' magnify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' make', errors: [expectedErrorNoTabindex], options: components }, + { code: ' manage', errors: [expectedErrorNoTabindex], options: components }, + { code: ' minimize', errors: [expectedErrorNoTabindex], options: components }, + { code: ' move', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ok', errors: [expectedErrorNoTabindex], options: components }, + { code: ' open', errors: [expectedErrorNoTabindex], options: components }, + { code: ' organise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' oversee', errors: [expectedErrorNoTabindex], options: components }, + { code: ' play', errors: [expectedErrorNoTabindex], options: components }, + { code: ' push', errors: [expectedErrorNoTabindex], options: components }, + { code: ' read', errors: [expectedErrorNoTabindex], options: components }, + { code: ' reduce', errors: [expectedErrorNoTabindex], options: components }, + { code: ' run', errors: [expectedErrorNoTabindex], options: components }, + { code: ' save', errors: [expectedErrorNoTabindex], options: components }, + { code: ' send', errors: [expectedErrorNoTabindex], options: components }, + { code: ' shout', errors: [expectedErrorNoTabindex], options: components }, + { code: ' sing', errors: [expectedErrorNoTabindex], options: components }, + { code: ' submit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' support', errors: [expectedErrorNoTabindex], options: components }, + { code: ' talk', errors: [expectedErrorNoTabindex], options: components }, + { code: ' trim', errors: [expectedErrorNoTabindex], options: components }, + { code: ' visit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' volunteer', errors: [expectedErrorNoTabindex], options: components }, + { code: ' vote', errors: [expectedErrorNoTabindex], options: components }, + { code: ' watch', errors: [expectedErrorNoTabindex], options: components }, + { code: ' win', errors: [expectedErrorNoTabindex], options: components }, + { code: ' write', errors: [expectedErrorNoTabindex], options: components }, + + // tabindex's value is 1 + { code: ' advise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' amplify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' apply', errors: [expectedErrorNoTabindex], options: components }, + { code: ' arrange', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ask', errors: [expectedErrorNoTabindex], options: components }, + { code: ' boost', errors: [expectedErrorNoTabindex], options: components }, + { code: ' build', errors: [expectedErrorNoTabindex], options: components }, + { code: ' call', errors: [expectedErrorNoTabindex], options: components }, + { code: ' click', errors: [expectedErrorNoTabindex], options: components }, + { code: ' close', errors: [expectedErrorNoTabindex], options: components }, + { code: ' commit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' consult', errors: [expectedErrorNoTabindex], options: components }, + { code: ' compile', errors: [expectedErrorNoTabindex], options: components }, + { code: ' collect', errors: [expectedErrorNoTabindex], options: components }, + { code: ' contribute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' create', errors: [expectedErrorNoTabindex], options: components }, + { code: ' cut', errors: [expectedErrorNoTabindex], options: components }, + { code: ' decrease', errors: [expectedErrorNoTabindex], options: components }, + { code: ' delete', errors: [expectedErrorNoTabindex], options: components }, + { code: ' divide', errors: [expectedErrorNoTabindex], options: components }, + { code: ' drink', errors: [expectedErrorNoTabindex], options: components }, + { code: ' eat', errors: [expectedErrorNoTabindex], options: components }, + { code: ' earn', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enable', errors: [expectedErrorNoTabindex], options: components }, + { code: ' enter', errors: [expectedErrorNoTabindex], options: components }, + { code: ' execute', errors: [expectedErrorNoTabindex], options: components }, + { code: ' exit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' expand', errors: [expectedErrorNoTabindex], options: components }, + { code: ' explain', errors: [expectedErrorNoTabindex], options: components }, + { code: ' finish', errors: [expectedErrorNoTabindex], options: components }, + { code: ' forecast', errors: [expectedErrorNoTabindex], options: components }, + { code: ' fix', errors: [expectedErrorNoTabindex], options: components }, + { code: ' generate', errors: [expectedErrorNoTabindex], options: components }, + { code: ' handle', errors: [expectedErrorNoTabindex], options: components }, + { code: ' help', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hire', errors: [expectedErrorNoTabindex], options: components }, + { code: ' hit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' improve', errors: [expectedErrorNoTabindex], options: components }, + { code: ' increase', errors: [expectedErrorNoTabindex], options: components }, + { code: ' join', errors: [expectedErrorNoTabindex], options: components }, + { code: ' jump', errors: [expectedErrorNoTabindex], options: components }, + { code: ' leave', errors: [expectedErrorNoTabindex], options: components }, + { code: ' let\'/s', errors: [expectedErrorNoTabindex], options: components }, + { code: ' list', errors: [expectedErrorNoTabindex], options: components }, + { code: ' listen', errors: [expectedErrorNoTabindex], options: components }, + { code: ' magnify', errors: [expectedErrorNoTabindex], options: components }, + { code: ' make', errors: [expectedErrorNoTabindex], options: components }, + { code: ' manage', errors: [expectedErrorNoTabindex], options: components }, + { code: ' minimize', errors: [expectedErrorNoTabindex], options: components }, + { code: ' move', errors: [expectedErrorNoTabindex], options: components }, + { code: ' ok', errors: [expectedErrorNoTabindex], options: components }, + { code: ' open', errors: [expectedErrorNoTabindex], options: components }, + { code: ' organise', errors: [expectedErrorNoTabindex], options: components }, + { code: ' oversee', errors: [expectedErrorNoTabindex], options: components }, + { code: ' play', errors: [expectedErrorNoTabindex], options: components }, + { code: ' push', errors: [expectedErrorNoTabindex], options: components }, + { code: ' read', errors: [expectedErrorNoTabindex], options: components }, + { code: ' reduce', errors: [expectedErrorNoTabindex], options: components }, + { code: ' run', errors: [expectedErrorNoTabindex], options: components }, + { code: ' save', errors: [expectedErrorNoTabindex], options: components }, + { code: ' send', errors: [expectedErrorNoTabindex], options: components }, + { code: ' shout', errors: [expectedErrorNoTabindex], options: components }, + { code: ' sing', errors: [expectedErrorNoTabindex], options: components }, + { code: ' submit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' support', errors: [expectedErrorNoTabindex], options: components }, + { code: ' talk', errors: [expectedErrorNoTabindex], options: components }, + { code: ' trim', errors: [expectedErrorNoTabindex], options: components }, + { code: ' visit', errors: [expectedErrorNoTabindex], options: components }, + { code: ' volunteer', errors: [expectedErrorNoTabindex], options: components }, + { code: ' vote', errors: [expectedErrorNoTabindex], options: components }, + { code: ' watch', errors: [expectedErrorNoTabindex], options: components }, + { code: ' win', errors: [expectedErrorNoTabindex], options: components }, + { code: ' write', errors: [expectedErrorNoTabindex], options: components }, + ].map(parserOptionsMapper), }); diff --git a/package.json b/package.json index 58ad28fe4..256a60376 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-sixtyeigth", + "name": "eslint-plugin-jsx-a11y-div-sixtynine", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index 219e1790a..aeed369ea 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -47,50 +47,41 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { const TextChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); - // TextChildValue.value is the text within the tag elements - // const VariableChildValue = node.parent.children.find((child) => child.type === 'JSXExpressionContainer' && child !== 'undefined'); - const expressioncontainer = node.parent.children.find((child) => child.type === 'JSXExpressionContainer'); - // => (expression.type === 'Identifier' && child.value !== 'undefined')); - const options = context.options[0] || {}; // [object Object] const componentOptions = options.components || []; // Apply - comming from .eslintrc.js file const typeCheck = ['div'].concat(componentOptions); // div, Apply const nodeType = elementType(node); // Apply // Only check 'div*' elements and custom types. - if (typeCheck.indexOf(nodeType) === -1) { // answers the question: is the current node, which is Apply is defined in the componentOptions? - // for example, is the Apply custom component present in div,Apply + // for example, is the Apply custom component present in div,Apply + // answers the question: is the current node, which is Apply is defined in the componentOptions in the eslintrc.json file? + if (typeCheck.indexOf(nodeType) === -1) { return; } - // if (actionVerbs.includes(IdentifierChildValue && IdentifierChildValue.value.toLowerCase()) === false) { - // return; - // } - if (expressioncontainer === true) { - const IdentifierChildValue = expressioncontainer.expression.type === 'Identifier'; - context.report({ - node, - message: `${IdentifierChildValue.value} is a identifier value`, - }); - return; + if ((actionVerbs.includes(nodeType.toLowerCase()) || nodeType.toLowerCase() === 'button') === true) { + if (actionVerbs.includes(TextChildValue && TextChildValue.value.toLowerCase()) === false) { + context.report({ + node, + message: 'If custom element is an action word then the text within it should also be an action word. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + }); + return; + } } if (actionVerbs.includes(TextChildValue && TextChildValue.value.toLowerCase()) === false) { return; } - - const tabindexProp = getProp(node.attributes, 'tabIndex'); const roleProp = getProp(node.attributes, 'role'); const tabindexValue = getPropValue(tabindexProp); const roleValue = getPropValue(roleProp); // Missing tabindex and role prop error. if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button')) - || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { + || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ node, - // message: `${IdentifierChildValue} meh ${IdentifierChildValue.value} meh ${IdentifierChildValue.type} text: ${TextChildValue} meh ${TextChildValue.value} meh ${TextChildValue.type} Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns `, message: 'Missing and/or incorrect attributes. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); return; diff --git a/src/util/hasAccessibleChild.js b/src/util/hasAccessibleChild.js index b4080c52d..4e4110941 100644 --- a/src/util/hasAccessibleChild.js +++ b/src/util/hasAccessibleChild.js @@ -9,16 +9,15 @@ export default function hasAccessibleChild(node: JSXElement): boolean { switch (child.type) { case 'Literal': case 'JSXText': - return Boolean(child.value); //exit if there is text within the tags + return Boolean(child.value); case 'JSXElement': return !isHiddenFromScreenReader( elementType(child.openingElement), child.openingElement.attributes, - ); // exit when the JSXElement is visible for screenreader + ); case 'JSXExpressionContainer': if (child.expression.type === 'Identifier') { - return (child.expression.name !== 'undefined') || (child.expression.value !== ''); - // return child.expression.name !== 'undefined'; + return child.expression.name !== 'undefined'; } return true; default: From 6ba1ebe4fb2c2fff11252c484414e4294b3f92b6 Mon Sep 17 00:00:00 2001 From: Felicia5 Date: Fri, 23 Apr 2021 09:57:40 +0100 Subject: [PATCH 35/35] markdown file finished --- __tests__/src/rules/div-has-apply-test.js | 7 +- docs/rules/div-has-apply.md | 94 ++++++++++++++++++++++- package.json | 2 +- src/rules/div-has-apply.js | 22 +++--- 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/__tests__/src/rules/div-has-apply-test.js b/__tests__/src/rules/div-has-apply-test.js index cf7186dd6..7e1fd6e53 100644 --- a/__tests__/src/rules/div-has-apply-test.js +++ b/__tests__/src/rules/div-has-apply-test.js @@ -1,6 +1,6 @@ /* eslint-env jest */ /** - * @fileoverview Discourage use of div when text is apply + * @fileoverview Discourage use of div when text is an action word * @author Felicia Kovacs */ @@ -126,7 +126,8 @@ ruleTester.run('div-has-apply', rule, { // CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION - // limitation: I only test for custom element, I do not test when custom element name is any other action word from the list + // limitation: I only test for custom element, + // I do not test when custom element name is any other action word from the list // any action word //has limitation { code: 'advise', options: components }, { code: 'amplify', options: components }, @@ -1707,7 +1708,7 @@ ruleTester.run('div-has-apply', rule, { { code: ' trim', errors: [expectedErrorNoAttributes], options: components }, { code: ' visit', errors: [expectedErrorNoAttributes], options: components }, { code: ' volunteer', errors: [expectedErrorNoAttributes], options: components }, - { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, + { code: ' vote', errors: [expectedErrorNoAttributes], options: components }, { code: ' watch', errors: [expectedErrorNoAttributes], options: components }, { code: ' win', errors: [expectedErrorNoAttributes], options: components }, { code: ' write', errors: [expectedErrorNoAttributes], options: components }, diff --git a/docs/rules/div-has-apply.md b/docs/rules/div-has-apply.md index 49c9961ab..677699c91 100644 --- a/docs/rules/div-has-apply.md +++ b/docs/rules/div-has-apply.md @@ -1,21 +1,109 @@ # div-has-apply -Write a useful explanation here! +Enforcing the use of tabindex="0" role="button" attributes when a call to action verb is used in a `div` element. Furthermore, recommendation is made to use button native HTML element over the ARIA attributes following the first rule of ARIA. ### References - 1. + 1. eslint-plugin-jsx-a11y/docs/rule/heading-has-content ## Rule details -This rule takes no arguments. +Here are the list of call to action(CTA) verbs that the rule will recognise and +indicate the line of code incorrect if one of them is in between `
` `` elements: + 'advise', 'amplify', 'apply', 'arrange', 'ask', + 'boost', 'build', + 'call', 'click', 'close', 'commit', 'consult', 'compile', 'collect', 'contribute', 'create', 'cut', + 'decrease', 'delete', 'divide', 'drink', + 'eat', 'earn', 'enable', 'enter', 'execute', 'exit', 'expand', 'explain', + 'finish', 'forecast', 'fix', + 'generate', + 'handle', 'help', 'hire', 'hit', + 'improve', 'increase', + 'join', 'jump', + 'leave', 'let\'/s', 'list', 'listen', + 'magnify', 'make', 'manage', 'minimize', 'move', + 'ok', 'open', 'organise', 'oversee', + 'play', 'push', + 'read', 'reduce', 'run', + 'save', 'send', 'shout', 'sing', 'submit', 'support', + 'talk', 'trim', + 'visit', 'volunteer', 'vote', + 'watch', 'win', 'write', + +This rule takes one optional object argument of type object: + +```json +{ + "rules": { + "jsx-a11y/heading-has-content": [ 2, { + "components": [ "Apply" ], + }], + } +} +``` +For the `components` option, these strings determine which JSX elements (**always including** `
`) should be checked for having call to action verbs content. This is a good use case when you have a wrapper component that simply renders a `button` element (like in React): + + +```js +// Apply.js +const Apply = props => { + return ( +
{ props.children }
+ ); +} + +... + +// CreateForm.js (for example) +... +return ( + Apply +); +``` ### Succeed ```jsx
+
// empty div is allowed +
orange
// no action word within div element +
orange
// any word within div that has the two attributes +
advise
// any of the action words e.g.: advise within div that has the two attributes +advise // If custom element is an action word then the text within it should also be an action word. ``` ### Fail ```jsx +// when a call to action verb is between div elements: + +//both attributes are missing and/or wrong +
submit
// both attributes are missing +
apply
// both attributes are undefined +
apply
// both attributes values are wrong +
apply
// both attributes values are wrong +
apply
// wrong attribute value and missing attribute +
apply
// wrong attribute value and missing attribute + +// tabindex is missing or wrong +
apply
+
apply
+
apply
+
apply
+
apply
+ +// role is missing or wrong +
apply
+
apply
+
apply
+
apply
+
apply
+// custom element name is an action verb and the text in between too +apply ``` + + +## Accessibility guidelines +- [WCAG 2.4.7](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-visible.html) + +### Resources +- [axe-core, focus-order-semantics](https://dequeuniversity.com/rules/axe/3.2/focus-order-semantics) diff --git a/package.json b/package.json index 256a60376..9ff5bcd94 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-jsx-a11y-div-sixtynine", + "name": "eslint-plugin-jsx-a11y-div-seventyone", "version": "6.4.1", "description": "Static AST checker for accessibility rules on JSX elements.", "keywords": [ diff --git a/src/rules/div-has-apply.js b/src/rules/div-has-apply.js index aeed369ea..7ff955b32 100644 --- a/src/rules/div-has-apply.js +++ b/src/rules/div-has-apply.js @@ -1,7 +1,6 @@ /** - * @fileoverview check if div has apply text - * @author Felicia - * @flow + * @fileoverview Discourage use of div when text is an action word + * @author Felicia Kovacs */ // ---------------------------------------------------------------------------- @@ -15,6 +14,7 @@ import { } from 'jsx-ast-utils'; import { generateObjSchema, arraySchema } from '../util/schemas'; +// random list of action verbs in alphabetical order const actionVerbs = [ 'advise', 'amplify', 'apply', 'arrange', 'ask', 'boost', 'build', @@ -46,11 +46,11 @@ module.exports = { create: (context) => ({ JSXOpeningElement: (node) => { - const TextChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); - const options = context.options[0] || {}; // [object Object] - const componentOptions = options.components || []; // Apply - comming from .eslintrc.js file - const typeCheck = ['div'].concat(componentOptions); // div, Apply - const nodeType = elementType(node); // Apply + const TextChildValue = node.parent.children.find((child) => child.type === 'Literal' || child.type === 'JSXText' || child.type === 'Unknown'); // text within div elements + const options = context.options[0] || {}; // returns e.g.: [object Object] + const componentOptions = options.components || []; // returns e.g.: Apply - comming from .eslintrc.js file + const typeCheck = ['div'].concat(componentOptions); // returns e.g.: the string div, Apply + const nodeType = elementType(node); // returns e.g.: Apply // Only check 'div*' elements and custom types. // for example, is the Apply custom component present in div,Apply @@ -77,7 +77,7 @@ module.exports = { const roleProp = getProp(node.attributes, 'role'); const tabindexValue = getPropValue(tabindexProp); const roleValue = getPropValue(roleProp); - // Missing tabindex and role prop error. + // Missing and/ or incorrect tabindex and role attributes if (((tabindexProp === undefined) && (roleProp === undefined)) || ((tabindexValue !== '0') && (roleValue !== 'button')) || ((tabindexProp === undefined) && (roleValue !== 'button')) || ((tabindexValue !== '0') && (roleProp === undefined))) { context.report({ @@ -87,14 +87,16 @@ module.exports = { return; } + // Missing and/or incorrect tabindex attribute if ((tabindexValue !== '0') || (tabindexProp === undefined)) { context.report({ node, - message: 'Missing or incorrect tabIndex attribute value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', + message: 'Missing or incorrect role attribute value. Action verbs should be contained preferably within a native HTML button element(see first rule of ARIA) or within a div element that has tabIndex="0" attribute and role="button" aria role. Refer to https://w3c.github.io/aria-practices/examples/button/button.html and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#accessibility_concerns', }); return; } + // Missing and/or incorrect role attribute if ((roleValue !== 'button') || (roleProp === undefined)) { context.report({ node,