Skip to content

Commit 4d8ef0a

Browse files
Release 5.14.0 (#175)
* Phase 1: Adding New Rules * Initial merge * Phase1.25 * minor changes * Minor changes * Minor Changes * Should change reverted * Reverting old changes * Making heading-order & heading-order-bp stable * Added accuracy bucket * "Ensure" compliance with changes * reverting labelContentNameMismatchEvaluate changes it should be in Phase 2 * Resolving PR review comments * reverting minor change * validate.js * json update * json update * updating _template.json * minor fix for eslint issue * 🤖 Automated formatting fixes --------- Co-authored-by: Gagan Meena <gagan.m@browserstack.com> Co-authored-by: gaganmBrowserStack <gaganmBrowserStack@users.noreply.github.com>
1 parent ac9b3dc commit 4d8ef0a

407 files changed

Lines changed: 17928 additions & 3368 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 108 additions & 0 deletions
Large diffs are not rendered by default.

build/cherry-pick.js

100644100755
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#!/usr/bin/env node
2+
13
const { execSync } = require('child_process');
24
const conventionalCommitsParser = require('conventional-commits-parser');
35
const chalk = require('chalk');
@@ -129,7 +131,7 @@ commitsToCherryPick.forEach(({ hash, type, scope, subject }) => {
129131

130132
try {
131133
execSync(`git cherry-pick ${hash} -X theirs`);
132-
} catch (e) {
134+
} catch {
133135
console.error(
134136
chalk.red.bold('\nAborting cherry-pick and reseting to master')
135137
);

build/tasks/metadata-function-map.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = function (grunt) {
2222
'// This file is automatically generated using build/tasks/metadata-function-map.js\n';
2323

2424
src.forEach(globPath => {
25-
glob.sync(globPath).forEach(filePath => {
25+
glob.sync(globPath, { posix: true }).forEach(filePath => {
2626
const relativePath = path.relative(
2727
path.dirname(file.dest),
2828
filePath

build/tasks/validate.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function fileExists(v, o) {
1414
var exists;
1515
try {
1616
exists = fs.existsSync(file);
17-
} catch (e) {
17+
} catch {
1818
return false;
1919
}
2020
return exists;
@@ -316,7 +316,9 @@ const miscTags = [
316316
'a11y-engine-experimental',
317317
'advanced',
318318
'ai',
319-
'new'
319+
'new',
320+
'medium-accuracy',
321+
'high-accuracy'
320322
];
321323

322324
const categories = [
@@ -361,6 +363,12 @@ const standardsTags = [
361363
standardRegex: /^EN-301-549$/,
362364
criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/,
363365
wcagLevelRegex: /^wcag21?aa?$/
366+
},
367+
{
368+
name: 'RGAA',
369+
standardRegex: /^RGAAv4$/,
370+
criterionRegex: /^RGAA-\d{1,2}\.\d{1,2}\.\d{1,2}$/,
371+
wcagLevelRegex: /^wcag21?aa?$/
364372
}
365373
];
366374

@@ -411,7 +419,7 @@ function findTagIssues(tags) {
411419
standardTag: standardTags[0] ?? null,
412420
criterionTags
413421
};
414-
if (bestPracticeTags.length !== 0) {
422+
if (name !== 'RGAA' && bestPracticeTags.length !== 0) {
415423
issues.push(`${name} tags cannot be used along side best-practice tag`);
416424
}
417425
if (standardTags.length === 0) {
@@ -423,7 +431,7 @@ function findTagIssues(tags) {
423431
issues.push(`Expected at least one ${name} criterion tag, got 0`);
424432
}
425433

426-
if (wcagLevelRegex) {
434+
if (wcagLevelRegex && standards.WCAG) {
427435
const wcagLevel = standards.WCAG.standardTag;
428436
if (!wcagLevel.match(wcagLevelRegex)) {
429437
issues.push(`${name} rules not allowed on ${wcagLevel}`);

doc/examples/qunit/test/test.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
<!-- Load local QUnit. -->
77
<link
88
rel="stylesheet"
9-
href="../node_modules/qunitjs/qunit/qunit.css"
9+
href="../node_modules/qunit/qunit/qunit.css"
1010
media="screen"
1111
/>
12-
<script src="../node_modules/qunitjs/qunit/qunit.js"></script>
12+
<script src="../node_modules/qunit/qunit/qunit.js"></script>
1313
<!-- Load local lib and tests. -->
1414
<script src="../node_modules/axe-core/axe.min.js"></script>
1515
<script src="a11y.js"></script>

doc/rule-descriptions.md

Lines changed: 116 additions & 114 deletions
Large diffs are not rendered by default.

lib/checks/aria/aria-allowed-attr-evaluate.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) {
3939

4040
// Unknown ARIA attributes are tested in aria-valid-attr
4141
for (const attrName of virtualNode.attrNames) {
42-
if (validateAttr(attrName) && !allowed.includes(attrName)) {
42+
if (
43+
validateAttr(attrName) &&
44+
!allowed.includes(attrName) &&
45+
!ignoredAttrs(attrName, virtualNode.attr(attrName), virtualNode)
46+
) {
4347
invalid.push(attrName);
4448
}
4549
}
@@ -57,3 +61,23 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) {
5761
}
5862
return false;
5963
}
64+
65+
function ignoredAttrs(attrName, attrValue, vNode) {
66+
// allow aria-required=false as screen readers consistently ignore it
67+
// @see https://github.com/dequelabs/axe-core/issues/3756
68+
if (attrName === 'aria-required' && attrValue === 'false') {
69+
return true;
70+
}
71+
72+
// allow aria-multiline=false when contenteditable is set
73+
// @see https://github.com/dequelabs/axe-core/issues/4463
74+
if (
75+
attrName === 'aria-multiline' &&
76+
attrValue === 'false' &&
77+
vNode.hasAttr('contenteditable')
78+
) {
79+
return true;
80+
}
81+
82+
return false;
83+
}

lib/checks/aria/aria-errormessage-evaluate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import standards from '../../standards';
2-
import { idrefs } from '../../commons/dom';
2+
import { idrefs, isVisibleToScreenReaders } from '../../commons/dom';
33
import { tokenList } from '../../core/utils';
4-
import { isVisibleToScreenReaders } from '../../commons/dom';
4+
import { getExplicitRole } from '../../commons/aria';
55
/**
66
* Check if `aria-errormessage` references an element that also uses a technique to announce the message (aria-live, aria-describedby, etc.).
77
*
@@ -46,7 +46,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) {
4646

4747
try {
4848
idref = attr && idrefs(virtualNode, 'aria-errormessage')[0];
49-
} catch (e) {
49+
} catch {
5050
this.data({
5151
messageKey: 'idrefs',
5252
values: tokenList(attr)
@@ -63,7 +63,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) {
6363
return false;
6464
}
6565
return (
66-
idref.getAttribute('role') === 'alert' ||
66+
getExplicitRole(idref) === 'alert' ||
6767
idref.getAttribute('aria-live') === 'assertive' ||
6868
idref.getAttribute('aria-live') === 'polite' ||
6969
tokenList(virtualNode.attr('aria-describedby')).indexOf(attr) > -1

lib/checks/aria/aria-errormessage.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
"hidden": "aria-errormessage value `${data.values}` cannot reference a hidden element"
1212
},
1313
"incomplete": {
14-
"singular": "ensure aria-errormessage value `${data.values}` references an existing element",
15-
"plural": "ensure aria-errormessage values `${data.values}` reference existing elements",
16-
"idrefs": "unable to determine if aria-errormessage element exists on the page: ${data.values}"
14+
"singular": "Ensure aria-errormessage value `${data.values}` references an existing element",
15+
"plural": "Ensure aria-errormessage values `${data.values}` reference existing elements",
16+
"idrefs": "Unable to determine if aria-errormessage element exists on the page: ${data.values}"
1717
}
1818
}
1919
}

lib/checks/aria/aria-prohibited-attr-evaluate.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { getRole } from '../../commons/aria';
1+
import { getRole, getRoleType } from '../../commons/aria';
22
import { sanitize, subtreeText } from '../../commons/text';
33
import standards from '../../standards';
4+
import memoize from '../../core/utils/memoize';
45

56
/**
67
* Check that an element does not use any prohibited ARIA attributes.
@@ -33,9 +34,14 @@ export default function ariaProhibitedAttrEvaluate(
3334
) {
3435
const elementsAllowedAriaLabel = options?.elementsAllowedAriaLabel || [];
3536
const { nodeName } = virtualNode.props;
36-
const role = getRole(virtualNode, { chromium: true });
37+
const role = getRole(virtualNode, {
38+
chromium: true,
39+
// this check allows fallback roles. For example, `<div role="foo img" aria-label="...">` is legal.
40+
fallback: true
41+
});
3742

3843
const prohibitedList = listProhibitedAttrs(
44+
virtualNode,
3945
role,
4046
nodeName,
4147
elementsAllowedAriaLabel
@@ -51,7 +57,7 @@ export default function ariaProhibitedAttrEvaluate(
5157
return false;
5258
}
5359

54-
let messageKey = virtualNode.hasAttr('role') ? 'hasRole' : 'noRole';
60+
let messageKey = role !== null ? 'hasRole' : 'noRole';
5561
messageKey += prohibited.length > 1 ? 'Plural' : 'Singular';
5662
this.data({ role, nodeName, messageKey, prohibited });
5763

@@ -64,13 +70,32 @@ export default function ariaProhibitedAttrEvaluate(
6470
return true;
6571
}
6672

67-
function listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel) {
73+
function listProhibitedAttrs(vNode, role, nodeName, elementsAllowedAriaLabel) {
6874
const roleSpec = standards.ariaRoles[role];
6975
if (roleSpec) {
7076
return roleSpec.prohibitedAttrs || [];
7177
}
72-
if (!!role || elementsAllowedAriaLabel.includes(nodeName)) {
78+
if (
79+
!!role ||
80+
elementsAllowedAriaLabel.includes(nodeName) ||
81+
getClosestAncestorRoleType(vNode) === 'widget'
82+
) {
7383
return [];
7484
}
7585
return ['aria-label', 'aria-labelledby'];
7686
}
87+
88+
const getClosestAncestorRoleType = memoize(
89+
function getClosestAncestorRoleTypeMemoized(vNode) {
90+
if (!vNode) {
91+
return;
92+
}
93+
94+
const role = getRole(vNode, { noPresentational: true, chromium: true });
95+
if (role) {
96+
return getRoleType(role);
97+
}
98+
99+
return getClosestAncestorRoleType(vNode.parent);
100+
}
101+
);

0 commit comments

Comments
 (0)