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

feat(matches): add explicitRole, implicitRole, and semanticRole matches functions #2286

Merged
merged 1 commit into from
Jun 12, 2020
Merged
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
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