Skip to content

Commit

Permalink
fix(aria-toggle-field-name): work with virtual nodes (#2353)
Browse files Browse the repository at this point in the history
* fix(aria-toggle-field-name): work with virtual nodes

* matches

* dont throw if no id
  • Loading branch information
straker authored Jul 10, 2020
1 parent a40b89c commit e5fb01e
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 43 deletions.
30 changes: 17 additions & 13 deletions lib/checks/aria/no-implicit-explicit-label-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { getRole } from '../../commons/aria';
import { sanitize, labelText, accessibleText } from '../../commons/text';
import { sanitize, labelText, accessibleTextVirtual } from '../../commons/text';

function noImplicitExplicitLabelEvaluate(node, options, virtualNode) {
const role = getRole(node, { noImplicit: true });
const role = getRole(virtualNode, { noImplicit: true });
this.data(role);

const label = sanitize(labelText(virtualNode)).toLowerCase();
const accText = sanitize(accessibleText(node)).toLowerCase();
try {
const label = sanitize(labelText(virtualNode)).toLowerCase();
const accText = sanitize(accessibleTextVirtual(virtualNode)).toLowerCase();

if (!accText && !label) {
return false;
}
if (!accText && !label) {
return false;
}

if (!accText && label) {
return undefined;
}
if (!accText && label) {
return undefined;
}

if (!accText.includes(label)) {
if (!accText.includes(label)) {
return undefined;
}

return false;
} catch (e) {
return undefined;
}

return false;
}

export default noImplicitExplicitLabelEvaluate;
4 changes: 2 additions & 2 deletions lib/commons/dom/find-elms-in-context.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getRootNode from './get-root-node';
import { escapeSelector } from '../../core/utils';

/**
* Find elements referenced from a given context
Expand All @@ -14,8 +15,7 @@ import getRootNode from './get-root-node';
*/
function findElmsInContext({ context, value, attr, elm = '' }) {
let root;
// TODO: es-module-utils.escapeSelector
const escapedValue = axe.utils.escapeSelector(value);
const escapedValue = escapeSelector(value);

if (context.nodeType === 9 || context.nodeType === 11) {
// It's already root
Expand Down
24 changes: 15 additions & 9 deletions lib/commons/text/label-text.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import accessibleTextVirtual from './accessible-text-virtual';
import accessibleText from './accessible-text';
import findUpVirtual from '../dom/find-up-virtual';
import findElmsInContext from '../dom/find-elms-in-context';
import { closest, nodeSorter } from '../../core/utils';

/**
* Return accessible text for an implicit and/or explicit HTML label element
Expand All @@ -26,13 +26,12 @@ function labelText(virtualNode, context = {}) {

const labelContext = { inControlContext: true, ...context };
const explicitLabels = getExplicitLabels(virtualNode);
const implicitLabel = findUpVirtual(virtualNode, 'label');
const implicitLabel = closest(virtualNode, 'label');

let labels;
if (implicitLabel) {
labels = [...explicitLabels, implicitLabel];
// TODO: es-module-utils.nodeSorter
labels.sort(axe.utils.nodeSorter);
labels = [...explicitLabels, implicitLabel.actualNode];
labels.sort(nodeSorter);
} else {
labels = explicitLabels;
}
Expand All @@ -49,15 +48,22 @@ function labelText(virtualNode, context = {}) {
* @param {VirtualNode} element The VirtualNode instance whose label we are seeking
* @return {HTMLElement} The label element, or null if none is found
*/
function getExplicitLabels({ actualNode }) {
if (!actualNode.id) {
function getExplicitLabels(virtualNode) {
if (!virtualNode.attr('id')) {
return [];
}

if (!virtualNode.actualNode) {
throw new TypeError(
'Cannot resolve explicit label reference for non-DOM nodes'
);
}

return findElmsInContext({
elm: 'label',
attr: 'for',
value: actualNode.id,
context: actualNode
value: virtualNode.attr('id'),
context: virtualNode.actualNode
});
}

Expand Down
12 changes: 6 additions & 6 deletions lib/rules/aria-form-field-name-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,34 @@ function ariaFormFieldNameMatches(node, virtualNode) {
* This rule filters elements with 'role=*' attribute via 'selector'
* see relevant rule spec for details of 'role(s)' being filtered.
*/
const nodeName = node.nodeName.toUpperCase();
const role = getExplicitRole(node);
const nodeName = virtualNode.props.nodeName;
const role = getExplicitRole(virtualNode);

/**
* Ignore elements from rule -> 'area-alt'
*/
if (nodeName === 'AREA' && !!node.getAttribute('href')) {
if (nodeName === 'area' && !!virtualNode.attr('href')) {
return false;
}

/**
* Ignore elements from rule -> 'label'
*/
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(nodeName)) {
if (['input', 'select', 'textarea'].includes(nodeName)) {
return false;
}

/**
* Ignore elements from rule -> 'image-alt'
*/
if (nodeName === 'IMG' || (role === 'img' && nodeName !== 'SVG')) {
if (nodeName === 'img' || (role === 'img' && nodeName !== 'svg')) {
return false;
}

/**
* Ignore elements from rule -> 'button-name'
*/
if (nodeName === 'BUTTON' || role === 'button') {
if (nodeName === 'button' || role === 'button') {
return false;
}

Expand Down
23 changes: 19 additions & 4 deletions test/checks/aria/no-implicit-explicit-label.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,46 @@ describe('no-implicit-explicit-label', function() {
var vNode = queryFixture(
'<div id="target" role="searchbox" contenteditable="true"></div>'
);
var actual = check.evaluate.call(checkContext, vNode.actualNode, {}, vNode);
var actual = check.evaluate.call(checkContext, null, {}, vNode);
assert.isFalse(actual);
});

it('returns undefined when there is no accessible text', function() {
var vNode = queryFixture(
'<label for="target">Choose currency:</label><div id="target" role="searchbox" contenteditable="true"></div>'
);
var actual = check.evaluate.call(checkContext, vNode.actualNode, {}, vNode);
var actual = check.evaluate.call(checkContext, null, {}, vNode);
assert.isUndefined(actual);
});

it('returns undefined when accessible text does not contain label text', function() {
var vNode = queryFixture(
'<label for="target">Choose country:</label><div id="target" aria-label="country" role="combobox">England</div>'
);
var actual = check.evaluate.call(checkContext, vNode.actualNode, {}, vNode);
var actual = check.evaluate.call(checkContext, null, {}, vNode);
assert.isUndefined(actual);
});

it('returns false when accessible text contains label text', function() {
var vNode = queryFixture(
'<label for="target">Country</label><div id="target" aria-label="Choose country" role="combobox">England</div>'
);
var actual = check.evaluate.call(checkContext, vNode.actualNode, {}, vNode);
var actual = check.evaluate.call(checkContext, null, {}, vNode);
assert.isFalse(actual);
});

describe('SerialVirtualNode', function() {
it('should return undefined', function() {
var serialNode = new axe.SerialVirtualNode({
nodeName: 'div',
attributes: {
role: 'combobox',
'aria-label': 'woohoo'
}
});

var actual = check.evaluate.call(checkContext, null, {}, serialNode);
assert.isUndefined(actual);
});
});
});
18 changes: 9 additions & 9 deletions test/rule-matches/aria-form-field-name-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,41 @@ describe('aria-form-field-name-matches', function() {
var vNode = queryFixture(
'<map><area id="target" href="#" role="checkbox"></map>'
);
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});

it('returns false when node is either INPUT, SELECT or TEXTAREA', function() {
['INPUT', 'SELECT', 'TEXTAREA'].forEach(function(node) {
['input', 'select', 'textarea'].forEach(function(node) {
var vNode = queryFixture(
'<' + node + ' role="menuitemcheckbox" id="target"><' + node + '>'
);
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});
});

it('returns false when node is IMG', function() {
var vNode = queryFixture('<img id="target" role="menuitemradio">');
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});

it('returns false when node is not SVG with role=`img`', function() {
var vNode = queryFixture('<div id="target" role="img">');
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});

it('returns false when node is BUTTON', function() {
var vNode = queryFixture('<button id="target" role="button"></button>');
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});

it('returns false when role=`button`', function() {
var vNode = queryFixture('<div id="target" role="button"></div>');
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});

Expand All @@ -61,7 +61,7 @@ describe('aria-form-field-name-matches', function() {
var vNode = queryFixture(
'<input id="target" role="radio" type="' + type + '">'
);
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});
});
Expand All @@ -70,7 +70,7 @@ describe('aria-form-field-name-matches', function() {
var vNode = queryFixture(
'<div id="target" role="combobox"><input type="text"/></div>'
);
var actual = rule.matches(vNode.actualNode, vNode);
var actual = rule.matches(null, vNode);
assert.isFalse(actual);
});
});

0 comments on commit e5fb01e

Please sign in to comment.