Skip to content

Commit

Permalink
feat(matches): add explicitRole, implicitRole, and semanticRole match…
Browse files Browse the repository at this point in the history
…es functions (#2286)
  • Loading branch information
straker committed Jun 12, 2020
1 parent c764713 commit 30efbff
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 25 deletions.
10 changes: 4 additions & 6 deletions lib/commons/matches/attributes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import fromFunction from './from-function';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';

/**
* Check if a virtual node matches some attribute(s)
Expand All @@ -21,12 +23,8 @@ import fromFunction from './from-function';
* @returns {Boolean}
*/
function attributes(vNode, matcher) {
// TODO: this is a ridiculous hack since webpack is making these two
// separate functions
// TODO: es-module-AbstractVirtualNode
if (!axe._isAbstractNode(vNode)) {
// TODO: es-module-utils.getNodeFromTree
vNode = axe.utils.getNodeFromTree(vNode);
if (!(vNode instanceof AbstractVirtualNode)) {
vNode = getNodeFromTree(vNode);
}
return fromFunction(attrName => vNode.attr(attrName), matcher);
}
Expand Down
24 changes: 24 additions & 0 deletions lib/commons/matches/explicit-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fromPrimative from './from-primative';
import getRole from '../aria/get-role';

/**
* Check if a virtual node matches an explicit role(s)
*``
* Note: matches.explicitRole(vNode, matcher) can be indirectly used through
* matches(vNode, { explicitRole: matcher })
*
* Example:
* ```js
* matches.explicitRole(vNode, ['combobox', 'textbox']);
* matches.explicitRole(vNode, 'combobox');
* ```
*
* @param {VirtualNode} vNode
* @param {Object} matcher
* @returns {Boolean}
*/
function explicitRole(vNode, matcher) {
return fromPrimative(getRole(vNode, { noImplicit: true }), matcher);
}

export default explicitRole;
18 changes: 11 additions & 7 deletions lib/commons/matches/from-definition.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import attributes from './attributes';
import condition from './condition';
import explicitRole from './explicit-role';
import implicitRole from './implicit-role';
import nodeName from './node-name';
import properties from './properties';
import semanticRole from './semantic-role';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';

const matchers = {
attributes,
condition,
explicitRole,
implicitRole,
nodeName,
properties
properties,
semanticRole
};

/**
Expand All @@ -34,12 +42,8 @@ const matchers = {
* @returns {Boolean}
*/
function fromDefinition(vNode, definition) {
// TODO: this is a ridiculous hack since webpack is making these two
// separate functions
// TODO: es-module-AbstractVirtualNode
if (!axe._isAbstractNode(vNode)) {
// TODO: es-module-utils.getNodeFromTree
vNode = axe.utils.getNodeFromTree(vNode);
if (!(vNode instanceof AbstractVirtualNode)) {
vNode = getNodeFromTree(vNode);
}

if (Array.isArray(definition)) {
Expand Down
24 changes: 24 additions & 0 deletions lib/commons/matches/implicit-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fromPrimative from './from-primative';
import getImplicitRole from '../aria/implicit-role';

/**
* Check if a virtual node matches an implicit role(s)
*``
* Note: matches.implicitRole(vNode, matcher) can be indirectly used through
* matches(vNode, { implicitRole: matcher })
*
* Example:
* ```js
* matches.implicitRole(vNode, ['combobox', 'textbox']);
* matches.implicitRole(vNode, 'combobox');
* ```
*
* @param {VirtualNode} vNode
* @param {Object} matcher
* @returns {Boolean}
*/
function implicitRole(vNode, matcher) {
return fromPrimative(getImplicitRole(vNode.actualNode), matcher);
}

export default implicitRole;
6 changes: 6 additions & 0 deletions lib/commons/matches/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@
*/
import attributes from './attributes';
import condition from './condition';
import explicitRole from './explicit-role';
import fromDefinition from './from-definition';
import fromFunction from './from-function';
import fromPrimative from './from-primative';
import implicitRole from './implicit-role';
import matches from './matches';
import nodeName from './node-name';
import properties from './properties';
import semanticRole from './semantic-role';

matches.attributes = attributes;
matches.condition = condition;
matches.explicitRole = explicitRole;
matches.fromDefinition = fromDefinition;
matches.fromFunction = fromFunction;
matches.fromPrimative = fromPrimative;
matches.implicitRole = implicitRole;
matches.nodeName = nodeName;
matches.properties = properties;
matches.semanticRole = semanticRole;

export default matches;
10 changes: 4 additions & 6 deletions lib/commons/matches/node-name.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import fromPrimative from './from-primative';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';

/**
* Check if the nodeName of a virtual node matches some value.
Expand All @@ -18,12 +20,8 @@ import fromPrimative from './from-primative';
* @returns {Boolean}
*/
function nodeName(vNode, matcher) {
// TODO: this is a ridiculous hack since webpack is making these two
// separate functions
// TODO: es-module-AbstractVirtualNode
if (!axe._isAbstractNode(vNode)) {
// TODO: es-module-utils.getNodeFromTree
vNode = axe.utils.getNodeFromTree(vNode);
if (!(vNode instanceof AbstractVirtualNode)) {
vNode = getNodeFromTree(vNode);
}
return fromPrimative(vNode.props.nodeName, matcher);
}
Expand Down
10 changes: 4 additions & 6 deletions lib/commons/matches/properties.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import fromFunction from './from-function';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';

/**
* Check if a virtual node matches some attribute(s)
Expand All @@ -21,12 +23,8 @@ import fromFunction from './from-function';
* @returns {Boolean}
*/
function properties(vNode, matcher) {
// TODO: this is a ridiculous hack since webpack is making these two
// separate functions
// TODO: es-module-AbstractVirtualNode
if (!axe._isAbstractNode(vNode)) {
// TODO: es-module-utils.getNodeFromTree
vNode = axe.utils.getNodeFromTree(vNode);
if (!(vNode instanceof AbstractVirtualNode)) {
vNode = getNodeFromTree(vNode);
}
return fromFunction(propName => vNode.props[propName], matcher);
}
Expand Down
24 changes: 24 additions & 0 deletions lib/commons/matches/semantic-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fromPrimative from './from-primative';
import getRole from '../aria/get-role';

/**
* Check if a virtual node matches an semantic role(s)
*``
* Note: matches.semanticRole(vNode, matcher) can be indirectly used through
* matches(vNode, { semanticRole: matcher })
*
* Example:
* ```js
* matches.semanticRole(vNode, ['combobox', 'textbox']);
* matches.semanticRole(vNode, 'combobox');
* ```
*
* @param {VirtualNode} vNode
* @param {Object} matcher
* @returns {Boolean}
*/
function semanticRole(vNode, matcher) {
return fromPrimative(getRole(vNode), matcher);
}

export default semanticRole;
41 changes: 41 additions & 0 deletions test/commons/matches/explicit-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
describe('matches.explicitRole', function() {
var explicitRole = axe.commons.matches.explicitRole;
var fixture = document.querySelector('#fixture');
var queryFixture = axe.testUtils.queryFixture;

beforeEach(function() {
fixture.innerHTML = '';
});

it('should return true if explicit role matches', function() {
var virtualNode = queryFixture('<span id="target" role="textbox"></span>');
assert.isTrue(explicitRole(virtualNode, 'textbox'));
});

it('should return true if explicit role matches array', function() {
var virtualNode = queryFixture('<span id="target" role="textbox"></span>');
assert.isTrue(explicitRole(virtualNode, ['combobox', 'textbox']));
});

it('should return false if explicit role does not match', function() {
var virtualNode = queryFixture('<span id="target" role="main"></span>');
assert.isFalse(explicitRole(virtualNode, 'textbox'));
});

it('should return false if matching implicit role', function() {
var virtualNode = queryFixture('<ul><li id="target"></li></ul>');
assert.isFalse(explicitRole(virtualNode, 'listitem'));
});

// TODO: will only work when get-role works exclusively with virtual
// nodes
it.skip('works with SerialVirtualNode', function() {
var serialNode = new axe.SerialVirtualNode({
nodeName: 'span',
attributes: {
role: 'textbox'
}
});
assert.isTrue(explicitRole(serialNode, 'textbox'));
});
});
72 changes: 72 additions & 0 deletions test/commons/matches/from-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,78 @@ describe('matches.fromDefinition', function() {
);
});

it('matches a definition with an `explicitRole` property', function() {
var virtualNode = queryFixture('<span id="target" role="textbox"></span>');
var matchers = [
'textbox',
['textbox', 'combobox'],
/textbox/,
function(attributeName) {
return attributeName === 'textbox';
}
];
matchers.forEach(function(matcher) {
assert.isTrue(
fromDefinition(virtualNode, {
explicitRole: matcher
})
);
});
assert.isFalse(
fromDefinition(virtualNode, {
explicitRole: 'main'
})
);
});

it('matches a definition with an `implicitRole` property', function() {
var virtualNode = queryFixture('<input id="target">');
var matchers = [
'textbox',
['textbox', 'combobox'],
/textbox/,
function(attributeName) {
return attributeName === 'textbox';
}
];
matchers.forEach(function(matcher) {
assert.isTrue(
fromDefinition(virtualNode, {
implicitRole: matcher
})
);
});
assert.isFalse(
fromDefinition(virtualNode, {
implicitRole: 'main'
})
);
});

it('matches a definition with an `semanticRole` property', function() {
var virtualNode = queryFixture('<input id="target">');
var matchers = [
'textbox',
['textbox', 'combobox'],
/textbox/,
function(attributeName) {
return attributeName === 'textbox';
}
];
matchers.forEach(function(matcher) {
assert.isTrue(
fromDefinition(virtualNode, {
semanticRole: matcher
})
);
});
assert.isFalse(
fromDefinition(virtualNode, {
semanticRole: 'main'
})
);
});

it('returns true when all matching properties return true', function() {
var virtualNode = queryFixture(
'<input id="target" value="bar" aria-disabled="true" />'
Expand Down
43 changes: 43 additions & 0 deletions test/commons/matches/implicit-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
describe('matches.implicitRole', function() {
var implicitRole = axe.commons.matches.implicitRole;
var fixture = document.querySelector('#fixture');
var queryFixture = axe.testUtils.queryFixture;

beforeEach(function() {
fixture.innerHTML = '';
});

it('should return true if implicit role matches', function() {
var virtualNode = queryFixture('<ul><li id="target"></li></ul>');
assert.isTrue(implicitRole(virtualNode, 'listitem'));
});

it('should return true if implicit role matches array', function() {
var virtualNode = queryFixture('<ul><li id="target"></li></ul>');
assert.isTrue(implicitRole(virtualNode, ['textbox', 'listitem']));
});

it('should return false if implicit role does not match', function() {
var virtualNode = queryFixture('<ul><li id="target"></li></ul>');
assert.isFalse(implicitRole(virtualNode, 'textbox'));
});

it('should return false if matching explicit role', function() {
var virtualNode = queryFixture(
'<ul role="menu"><li id="target" role="menuitem"></li></ul>'
);
assert.isFalse(implicitRole(virtualNode, 'menuitem'));
});

// TODO: will only work when get-role works exclusively with virtual
// nodes
it.skip('works with SerialVirtualNode', function() {
var serialNode = new axe.SerialVirtualNode({
nodeName: 'span',
attributes: {
role: 'textbox'
}
});
assert.isTrue(implicitRole(serialNode, 'textbox'));
});
});
Loading

0 comments on commit 30efbff

Please sign in to comment.