Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Div actionverbs withattributes rule #787

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ab6b704
div-has-content
Felicia5 Feb 19, 2021
5f550a7
added div-has-apply
Felicia5 Feb 26, 2021
e82f72d
forgot to add rule under src/index.jsx
Felicia5 Feb 26, 2021
0ebad68
jest test for hasAccessibleChild template, the hasApplyText that is c…
Felicia5 Feb 26, 2021
34c2e3b
hasApplyText-test looks for divs though not buttons or headings, in …
Felicia5 Feb 26, 2021
21f9563
published as npm package no. seven
Felicia5 Feb 26, 2021
3ddb61b
replaced div for button jsx elements
Felicia5 Feb 26, 2021
080cf36
published as an npm package version eight
Felicia5 Feb 26, 2021
05fd998
typo in hasApplyText function name
Felicia5 Feb 27, 2021
26f6117
when has child return false for apply literal when jsxelement is div …
Felicia5 Feb 27, 2021
8a1b816
emoj rule logic: accessing children of div
Felicia5 Mar 17, 2021
803cdf7
deleted tolowercase, added hasAccessibleChild===false
Felicia5 Mar 18, 2021
2e7a7e9
div is underlined when text is apply
Felicia5 Mar 18, 2021
1799cb9
defined case for unknown to run test
Felicia5 Mar 19, 2021
87c84f4
actionVerbs variable is created
Felicia5 Mar 19, 2021
8c93887
or in variable doesn't apply, but neither does changing apply to subm…
Felicia5 Mar 19, 2021
aaa71c4
deleted looking for just apply keyword and we look for other words as…
Felicia5 Mar 19, 2021
7ef8737
checking attributes, logic from alt-text rule
Felicia5 Mar 19, 2021
7c4985f
return commands after each check are added
Felicia5 Mar 19, 2021
56926b9
added more meaningful error messages
Felicia5 Mar 19, 2021
4ac6140
when tabindex AND role values are missing the error message should hi…
Felicia5 Mar 19, 2021
c179d25
corrected error messages, deleted unnecessary commented code parts
Felicia5 Mar 19, 2021
eb4b893
got rid off unnecessary comment and hasAccessibleChild util
Felicia5 Mar 20, 2021
71f135a
before three messages
Felicia5 Mar 21, 2021
d96254e
basic tests passed - no variable, or custom elements
Felicia5 Mar 21, 2021
3af1c4c
before before changing logic of error messages
Felicia5 Mar 21, 2021
ceb1211
added or case
Felicia5 Mar 21, 2021
8abf804
wrong attribute value and missing attribute
Felicia5 Mar 22, 2021
737801b
testing what options, componentOptions, typeCheck and nodeType variab…
Felicia5 Mar 22, 2021
c763acb
see if it works without TextChildValue, created VariableChildValue in…
Felicia5 Mar 23, 2021
f3f3588
trial and error for looking at if variable value is equal to text app…
Felicia5 Mar 23, 2021
08d3aa5
<div>{apply}</div> trying to access variable apply inside curly brace…
Felicia5 Mar 23, 2021
c404e78
hasAccessibleChild empty string is not linted neither when expression…
Felicia5 Mar 23, 2021
9165a02
added custom element tests
Felicia5 Mar 23, 2021
6ba1ebe
markdown file finished
Felicia5 Apr 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2,467 changes: 2,467 additions & 0 deletions __tests__/src/rules/div-has-apply-test.js

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions __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: '<div>content</div>;' },
].map(parserOptionsMapper),
invalid: [
// DEFAULT ELEMENT TESTS
{ code: '<div />', errors: [expectedError] },
].map(parserOptionsMapper),
});
2 changes: 1 addition & 1 deletion __tests__/src/rules/heading-has-content-test.js
Expand Up @@ -56,7 +56,7 @@ ruleTester.run('heading-has-content', rule, {
invalid: [
// DEFAULT ELEMENT TESTS
{ code: '<h1 />', errors: [expectedError] },
{ code: '<h1><Bar aria-hidden /></h1>', errors: [expectedError] },
{ code: '<h1><Bar aria-hidden="true" /></h1>', errors: [expectedError] },
{ code: '<h1>{undefined}</h1>', errors: [expectedError] },

// CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION
Expand Down
109 changes: 109 additions & 0 deletions docs/rules/div-has-apply.md
@@ -0,0 +1,109 @@
# div-has-apply

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. eslint-plugin-jsx-a11y/docs/rule/heading-has-content

## Rule details

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 `<div>` `</ div>` 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** `<div>`) 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 (
<div tabindex="0" role="button" {...props}>{ props.children }</div>
);
}

...

// CreateForm.js (for example)
...
return (
<Apply>Apply</Apply>
);
```

### Succeed
```jsx
<div />
<div></div> // empty div is allowed
<div>orange</div> // no action word within div element
<div tabIndex="0" role="button">orange</div> // any word within div that has the two attributes
<div tabIndex="0" role="button">advise</div> // any of the action words e.g.: advise within div that has the two attributes
<Apply tabIndex="0" role="button">advise</Apply> // 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
<div>submit</div> // both attributes are missing
<div tabIndex role>apply</div> // both attributes are undefined
<div tabIndex="-1" role="navigation">apply</div> // both attributes values are wrong
<div tabIndex="1" role="main">apply</div> // both attributes values are wrong
<div role="contentinfo">apply</div> // wrong attribute value and missing attribute
<div tabindex="-1">apply</div> // wrong attribute value and missing attribute

// tabindex is missing or wrong
<div role="button">apply</div>
<div role="button" tabindex>apply</div>
<div role="button" tabindex="">apply</div>
<div role="button" tabindex="-1">apply</div>
<div role="button" tabindex="1">apply</div>

// role is missing or wrong
<div tabIndex="0">apply</div>
<div tabIndex="0" role>apply</div>
<div tabIndex="0" role="">apply</div>
<div tabIndex="0" role="main">apply</div>
<div tabIndex="0" role="main">apply</div>

// custom element name is an action verb and the text in between too
<Appy>apply</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)
21 changes: 21 additions & 0 deletions 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
<div />
```

### Fail
```jsx

```
7 changes: 4 additions & 3 deletions package.json
@@ -1,5 +1,5 @@
{
"name": "eslint-plugin-jsx-a11y",
"name": "eslint-plugin-jsx-a11y-div-seventyone",
"version": "6.4.1",
"description": "Static AST checker for accessibility rules on JSX elements.",
"keywords": [
Expand All @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Expand Up @@ -15,6 +15,8 @@ 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'),
'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'),
Expand Down Expand Up @@ -91,6 +93,8 @@ 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',
Expand Down Expand Up @@ -245,6 +249,8 @@ 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',
Expand Down
108 changes: 108 additions & 0 deletions src/rules/div-has-apply.js
@@ -0,0 +1,108 @@
/**
* @fileoverview Discourage use of div when text is an action word
* @author Felicia Kovacs
*/

// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------

import {
elementType,
getProp,
getPropValue,
} 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',
'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 });

module.exports = {
meta: {
docs: {},
schema: [schema],
},

create: (context) => ({
JSXOpeningElement: (node) => {
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
// 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(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 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({
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',
});
return;
}

// Missing and/or incorrect tabindex attribute
if ((tabindexValue !== '0') || (tabindexProp === undefined)) {
context.report({
node,
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,
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',
});
}
},
}),
};
54 changes: 54 additions & 0 deletions 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,
});
},
}),
};