Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/checks/aria/aria-valid-attr-value-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,12 @@ export default function ariaValidAttrValueEvaluate(node, options, virtualNode) {
}

if (needsReview) {
this.data({ messageKey, needsReview });
this.data({
Comment thread
rajathmr2000 marked this conversation as resolved.
messageKey,
needsReview,
// a11y-rule-aria-valid-attr-value: emit reviewPayload
reviewPayload: { visualHelperData: { ariaAttribute: needsReview } }
});
return undefined;
}

Expand Down
20 changes: 19 additions & 1 deletion lib/checks/keyboard/focusable-disabled-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function focusableDisabledEvaluate(node, options, virtualNode) {
return true;
}

return relatedNodes.every(vNode => {
const verdict = relatedNodes.every(vNode => {
const pointerEvents = vNode.getComputedStylePropertyValue('pointer-events');
const width = parseInt(vNode.getComputedStylePropertyValue('width'));
const height = parseInt(vNode.getComputedStylePropertyValue('height'));
Expand All @@ -37,6 +37,24 @@ function focusableDisabledEvaluate(node, options, virtualNode) {
})
? undefined
: false;

// a11y-rule-aria-hidden-focus: emit focusable descendants for bulk review; additive, guarded.
try {
this.data({
reviewPayload: {
visualHelperData: {
focusableChildren: relatedNodes
.map(vNode => vNode.actualNode)
.filter(Boolean)
.map(actualNode => new axe.utils.DqElement(actualNode).selector)
}
}
});
} catch {
// best-effort emit — preserve the scan result
}

return verdict;
}

export default focusableDisabledEvaluate;
20 changes: 19 additions & 1 deletion lib/checks/keyboard/focusable-not-tabbable-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function focusableNotTabbableEvaluate(node, options, virtualNode) {
return true;
}

return relatedNodes.every(vNode => {
const verdict = relatedNodes.every(vNode => {
const pointerEvents = vNode.getComputedStylePropertyValue('pointer-events');
const width = parseInt(vNode.getComputedStylePropertyValue('width'));
const height = parseInt(vNode.getComputedStylePropertyValue('height'));
Expand All @@ -37,6 +37,24 @@ function focusableNotTabbableEvaluate(node, options, virtualNode) {
})
? undefined
: false;

// a11y-rule-aria-hidden-focus: emit focusable descendants for bulk review; additive, guarded.
try {
this.data({
reviewPayload: {
visualHelperData: {
focusableChildren: relatedNodes
.map(vNode => vNode.actualNode)
.filter(Boolean)
.map(actualNode => new axe.utils.DqElement(actualNode).selector)
}
}
});
} catch {
// best-effort emit — preserve the scan result
}

return verdict;
}

export default focusableNotTabbableEvaluate;
21 changes: 21 additions & 0 deletions lib/checks/navigation/identical-links-same-purpose-after.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ function identicalLinksSamePurposeAfter(results) {
currentResult.result = undefined;
}

// primary link's DqElement is at relatedNodes[0]; capture before rebuild.
const currentNode = currentResult.relatedNodes[0];

/**
* -> deduplicate results (for both `pass` or `incomplete`) and add `relatedNodes` if any
*/
Expand All @@ -92,6 +95,24 @@ function identicalLinksSamePurposeAfter(results) {
...sameNameResults.map(node => node.relatedNodes[0])
);

if (sameNameResults.length) {
try {
const identicalLinks = [currentNode, ...currentResult.relatedNodes]
.filter(Boolean)
.map(dqElm => (dqElm ? dqElm.selector : undefined))
.filter(Boolean);
const linkText =
(currentResult.data && currentResult.data.accessibleText) || name;
if (currentResult.data) {
currentResult.data.reviewPayload = {
visualHelperData: { linkText, identicalLinks }
};
}
} catch {
// best-effort emit — preserve the reconciled results
}
}

/**
* Update `nodeMap` with `sameNameResults`
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function identicalLinksSamePurposeEvaluate(node, options, virtualNode) {
*/
const afterData = {
name,
accessibleText: text.sanitize(accText),
urlProps: dom.urlPropsFromAttribute(node, 'href')
};
this.data(afterData);
Expand Down
5 changes: 3 additions & 2 deletions lib/checks/parsing/duplicate-id-after.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
function duplicateIdAfter(results) {
const uniqueIds = [];
return results.filter(r => {
if (uniqueIds.indexOf(r.data) === -1) {
uniqueIds.push(r.data);
const id = r.data && typeof r.data === 'object' ? r.data.id : r.data;
if (uniqueIds.indexOf(id) === -1) {
uniqueIds.push(id);
return true;
}
return false;
Expand Down
3 changes: 2 additions & 1 deletion lib/checks/parsing/duplicate-id-aria.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
"id": "duplicate-id-aria",
"evaluate": "duplicate-id-evaluate",
"after": "duplicate-id-after",
"options": { "reviewPayload": true },
"metadata": {
"impact": "critical",
"messages": {
"pass": "Document has no elements referenced with ARIA or labels that share the same id attribute",
"fail": "Document has multiple elements referenced with ARIA with the same id attribute: ${data}"
"fail": "Document has multiple elements referenced with ARIA with the same id attribute: ${data.id}"
}
}
}
25 changes: 24 additions & 1 deletion lib/checks/parsing/duplicate-id-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getRootNode } from '../../commons/dom';
import { escapeSelector } from '../../core/utils';

function duplicateIdEvaluate(node) {
function duplicateIdEvaluate(node, options = {}) {
const id = node.getAttribute('id').trim();
// Since empty ID's are not meaningful and are ignored by Edge, we'll
// let those pass.
Expand All @@ -16,6 +16,29 @@ function duplicateIdEvaluate(node) {
if (matchingNodes.length) {
this.relatedNodes(matchingNodes);
}

// a11y-rule-duplicate-id-aria: emit duplicate id + sharing elements for bulk review when enabled.
if (options.reviewPayload) {
const verdict = matchingNodes.length === 0;
const data = { id };
try {
if (matchingNodes.length) {
data.reviewPayload = {
visualHelperData: {
duplicateId: id,
elements: matchingNodes
.filter(Boolean)
.map(foundNode => new axe.utils.DqElement(foundNode).selector)
}
};
}
} catch {
// best-effort emit — preserve the scan result
}
this.data(data);
return verdict;
}

this.data(id);

return matchingNodes.length === 0;
Expand Down
1 change: 1 addition & 0 deletions lib/rules/aria-hidden-focus.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
],
"actIds": ["6cfa84"],
"metadata": {
"defaultCategory": "aria-hidden",
"description": "Ensure aria-hidden elements are not focusable nor contain focusable elements",
"help": "ARIA hidden element must not be focusable or contain focusable elements",
"needsReviewConfidence": 49.99
Expand Down
1 change: 1 addition & 0 deletions lib/rules/aria-valid-attr-value.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
],
"actIds": ["6a7281"],
"metadata": {
"defaultCategory": "aria-attributes",
"description": "Ensure all ARIA attributes have valid values",
"help": "ARIA attributes must conform to valid values",
"needsReviewConfidence": 49.99
Expand Down
1 change: 1 addition & 0 deletions lib/rules/bypass.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
],
"actIds": ["cf77f2", "047fe0", "b40fd1", "3e12e1", "ye5d6e"],
"metadata": {
"defaultCategory": "bypass-blocks",
"description": "Ensure each page has at least one mechanism for a user to bypass navigation and jump straight to the content",
"help": "Page must have means to bypass repeated blocks",
"needsReviewConfidence": 49.99
Expand Down
1 change: 1 addition & 0 deletions lib/rules/css-orientation-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
],
"actIds": ["b33eff"],
"metadata": {
"defaultCategory": "orientation",
"description": "Ensure content is not locked to any specific display orientation, and the content is operable in all display orientations",
"help": "CSS Media queries must not lock display orientation",
"violationConfidence": 90,
Expand Down
1 change: 1 addition & 0 deletions lib/rules/duplicate-id-aria.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"reviewOnFail": true,
"actIds": ["3ea0c8"],
"metadata": {
"defaultCategory": "duplicate-ids",
"description": "Ensure every id attribute value used in ARIA and in labels is unique",
"help": "IDs used in ARIA and labels must be unique",
"needsReviewConfidence": 49.99
Expand Down
1 change: 1 addition & 0 deletions lib/rules/identical-links-same-purpose.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"tags": ["cat.semantics", "wcag2aaa", "wcag249"],
"actIds": ["b20e66"],
"metadata": {
"defaultCategory": "link-consistency",
"description": "Ensure that links with the same accessible name serve a similar purpose",
"help": "Links with the same name must have a similar purpose",
"needsReviewConfidence": 49.99
Expand Down
1 change: 1 addition & 0 deletions lib/rules/video-caption.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
],
"actIds": ["eac66b"],
"metadata": {
"defaultCategory": "media-captions",
"description": "Ensure <video> elements have captions",
"help": "<video> elements must have captions",
"needsReviewConfidence": 49.99
Expand Down
46 changes: 46 additions & 0 deletions test/checks/aria/aria-valid-attr-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
describe('aria-valid-attr-value', () => {
'use strict';

const checkContext = axe.testUtils.MockCheckContext();
const checkSetup = axe.testUtils.checkSetup;
const checkEvaluate = axe.testUtils.getCheckEvaluate('aria-valid-attr-value');

afterEach(() => {
checkContext.reset();
});

it('emits the flagged aria-current attribute in reviewPayload.visualHelperData', () => {
const params = checkSetup(
'<div id="target" aria-current="active">Contents</div>'
);
assert.isUndefined(checkEvaluate.apply(checkContext, params));
assert.equal(checkContext._data.messageKey, 'ariaCurrent');
assert.equal(checkContext._data.needsReview, 'aria-current="active"');
assert.deepEqual(checkContext._data.reviewPayload, {
visualHelperData: { ariaAttribute: 'aria-current="active"' }
});
});

it('emits the flagged aria-labelledby attribute in reviewPayload.visualHelperData', () => {
const params = checkSetup(
'<div id="target" aria-labelledby="missing-id">Contents</div>'
);
assert.isUndefined(checkEvaluate.apply(checkContext, params));
assert.equal(
checkContext._data.needsReview,
'aria-labelledby="missing-id"'
);
assert.deepEqual(checkContext._data.reviewPayload, {
visualHelperData: { ariaAttribute: 'aria-labelledby="missing-id"' }
});
});

it('does not add a reviewPayload on the invalid-value (violation) path', () => {
const params = checkSetup(
'<div id="target" role="checkbox" aria-checked="foo">Contents</div>'
);
assert.isFalse(checkEvaluate.apply(checkContext, params));
assert.isArray(checkContext._data);
assert.notProperty(checkContext._data, 'reviewPayload');
});
});
36 changes: 28 additions & 8 deletions test/checks/aria/valid-attr-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'controlsWithinPopup',
needsReview: 'aria-controls="test"'
needsReview: 'aria-controls="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-controls="test"' }
}
});
});

Expand Down Expand Up @@ -160,7 +163,10 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'noId',
needsReview: 'aria-describedby="test"'
needsReview: 'aria-describedby="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-describedby="test"' }
}
});
});

Expand All @@ -174,7 +180,10 @@ describe('aria-valid-attr-value', function () {
assert.isUndefined(validAttrValueCheck.apply(checkContext, params));
assert.deepEqual(checkContext._data, {
messageKey: 'noIdShadow',
needsReview: 'aria-describedby="test"'
needsReview: 'aria-describedby="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-describedby="test"' }
}
});
}
);
Expand All @@ -188,7 +197,10 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'noId',
needsReview: 'aria-labelledby="test"'
needsReview: 'aria-labelledby="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-labelledby="test"' }
}
});
});

Expand All @@ -202,7 +214,10 @@ describe('aria-valid-attr-value', function () {
assert.isUndefined(validAttrValueCheck.apply(checkContext, params));
assert.deepEqual(checkContext._data, {
messageKey: 'noIdShadow',
needsReview: 'aria-labelledby="test"'
needsReview: 'aria-labelledby="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-labelledby="test"' }
}
});
}
);
Expand Down Expand Up @@ -248,7 +263,8 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'empty',
needsReview: 'aria-checked'
needsReview: 'aria-checked',
reviewPayload: { visualHelperData: { ariaAttribute: 'aria-checked' } }
});
});

Expand All @@ -261,7 +277,8 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'empty',
needsReview: 'aria-checked'
needsReview: 'aria-checked',
reviewPayload: { visualHelperData: { ariaAttribute: 'aria-checked' } }
});
});

Expand Down Expand Up @@ -320,7 +337,10 @@ describe('aria-valid-attr-value', function () {
);
assert.deepEqual(checkContext._data, {
messageKey: 'idrefs',
needsReview: 'aria-owns="test"'
needsReview: 'aria-owns="test"',
reviewPayload: {
visualHelperData: { ariaAttribute: 'aria-owns="test"' }
}
});
});

Expand Down
Loading
Loading