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): use VirtualNode and deprecate HTMLElement #1988

Merged
merged 8 commits into from
Jan 29, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 12 additions & 8 deletions lib/commons/matches/attributes.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
/* global matches */
/**
* Check if a node matches some attribute(s)
* Check if a virtual node matches some attribute(s)
*
* Note: matches.attributes(node, matcher) can be indirectly used through
* matches(node, { attributes: matcher })
* Note: matches.attributes(vNode, matcher) can be indirectly used through
* matches(vNode, { attributes: matcher })
*
* Example:
* ```js
* matches.attributes(node, {
* matches.attributes(vNode, {
* 'aria-live': 'assertive', // Simple string match
* 'aria-expanded': /true|false/i, // either boolean, case insensitive
* })
* ```
*
* @param {HTMLElement|VirtualNode} node
* @deprecated HTMLElement is deprecated, use VirtualNode instead
*
* @param {HTMLElement|VirtualNode} vNode
* @param {Object} Attribute matcher
* @returns {Boolean}
*/
matches.attributes = function matchesAttributes(node, matcher) {
node = node.actualNode || node;
return matches.fromFunction(attrName => node.getAttribute(attrName), matcher);
matches.attributes = function matchesAttributes(vNode, matcher) {
if (!(vNode instanceof axe.AbstractVirtualNode)) {
vNode = axe.utils.getNodeFromTree(vNode);
}
return matches.fromFunction(attrName => vNode.attr(attrName), matcher);
};
25 changes: 15 additions & 10 deletions lib/commons/matches/from-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,38 @@
const matchers = ['nodeName', 'attributes', 'properties', 'condition'];

/**
* Check if a node matches some definition
* Check if a virtual node matches some definition
*
* Note: matches.fromDefinition(node, definition) can be indirectly used through
* matches(node, definition)
* Note: matches.fromDefinition(vNode, definition) can be indirectly used through
* matches(vNode, definition)
*
* Example:
* ```js
* matches.fromDefinition(node, {
* matches.fromDefinition(vNode, {
* nodeName: ['div', 'span']
* attributes: {
* 'aria-live': 'assertive'
* }
* })
* ```
*
* @deprecated HTMLElement is deprecated, use VirtualNode instead
*
* @private
* @param {HTMLElement|VirtualNode} node
* @param {HTMLElement|VirtualNode} vNode
* @param {Object|Array<Object>} definition
* @returns {Boolean}
*/
matches.fromDefinition = function matchFromDefinition(node, definition) {
node = node.actualNode || node;
matches.fromDefinition = function matchFromDefinition(vNode, definition) {
if (!(vNode instanceof axe.AbstractVirtualNode)) {
vNode = axe.utils.getNodeFromTree(vNode);
}

if (Array.isArray(definition)) {
return definition.some(definitionItem => matches(node, definitionItem));
return definition.some(definitionItem => matches(vNode, definitionItem));
}
if (typeof definition === 'string') {
return axe.utils.matchesSelector(node, definition);
return axe.utils.matches(vNode, definition);
}

return Object.keys(definition).every(matcherName => {
Expand All @@ -42,6 +47,6 @@ matches.fromDefinition = function matchFromDefinition(node, definition) {
// Find the matcher that goes into the matches method.
// 'div', /^div$/, (str) => str === 'div', etc.
const matcher = definition[matcherName];
return matchMethod(node, matcher);
return matchMethod(vNode, matcher);
});
};
20 changes: 11 additions & 9 deletions lib/commons/matches/index.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
/* exported matches */

/**
* Check if a DOM element matches a definition
* Check if a virtual node matches a definition
*
* Example:
* ```js
* // Match a single nodeName:
* axe.commons.matches(elm, 'div')
* axe.commons.matches(vNode, 'div')
*
* // Match one of multiple nodeNames:
* axe.commons.matches(elm, ['ul', 'ol'])
* axe.commons.matches(vNode, ['ul', 'ol'])
*
* // Match a node with nodeName 'button' and with aria-hidden: true:
* axe.commons.matches(elm, {
* axe.commons.matches(vNode, {
* nodeName: 'button',
* attributes: { 'aria-hidden': 'true' }
* })
*
* // Mixed input. Match button nodeName, input[type=button] and input[type=reset]
* axe.commons.matches(elm, ['button', {
* axe.commons.matches(vNode, ['button', {
* nodeName: 'input', // nodeName match isn't case sensitive
* properties: { type: ['button', 'reset'] }
* }])
* ```
*
* @deprecated HTMLElement is deprecated, use VirtualNode instead
*
* @namespace matches
* @memberof axe.commons
* @param {HTMLElement|VirtualNode} node node to verify attributes against constraints
* @param {HTMLElement|VirtualNode} vNode virtual node to verify attributes against constraints
* @param {Array<ElementDefinition>|String|Object|Function|Regex} definition
* @return {Boolean} true/ false based on if node passes the constraints expected
* @return {Boolean} true/ false based on if vNode passes the constraints expected
*/
function matches(node, definition) {
return matches.fromDefinition(node, definition);
function matches(vNode, definition) {
return matches.fromDefinition(vNode, definition);
}

commons.matches = matches;
32 changes: 11 additions & 21 deletions lib/commons/matches/node-name.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
/* global matches */
let isXHTMLGlobal;
/**
* Check if the nodeName of a node matches some value
* Check if the nodeName of a virtual node matches some value.
*
* Note: matches.nodeName(node, matcher) can be indirectly used through
* matches(node, { nodeName: matcher })
* Note: matches.nodeName(vNode, matcher) can be indirectly used through
* matches(vNode, { nodeName: matcher })
*
* Example:
* ```js
* matches.nodeName(node, ['div', 'span'])
* matches.nodeName(vNode, ['div', 'span'])
* ```
*
* @param {HTMLElement|VirtualNode} node
* @deprecated HTMLElement is deprecated, use VirtualNode instead
*
* @param {HTMLElement|VirtualNode} vNode
* @param {Object} Attribute matcher
* @returns {Boolean}
*/
matches.nodeName = function matchNodeName(node, matcher, { isXHTML } = {}) {
node = node.actualNode || node;
if (typeof isXHTML === 'undefined') {
// When the matcher is a string, use native .matches() function:
if (typeof matcher === 'string') {
return axe.utils.matchesSelector(node, matcher);
}

if (typeof isXHTMLGlobal === 'undefined') {
isXHTMLGlobal = axe.utils.isXHTML(node.ownerDocument);
}
isXHTML = isXHTMLGlobal;
matches.nodeName = function matchNodeName(vNode, matcher) {
if (!(vNode instanceof axe.AbstractVirtualNode)) {
vNode = axe.utils.getNodeFromTree(vNode);
}

const nodeName = isXHTML ? node.nodeName : node.nodeName.toLowerCase();
return matches.fromPrimative(nodeName, matcher);
return matches.fromPrimative(vNode.props.nodeName, matcher);
};
21 changes: 12 additions & 9 deletions lib/commons/matches/properties.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
/* global matches */

/**
* Check if a node matches some attribute(s)
* Check if a virtual node matches some attribute(s)
*
* Note: matches.properties(node, matcher) can be indirectly used through
* matches(node, { properties: matcher })
* Note: matches.properties(vNode, matcher) can be indirectly used through
* matches(vNode, { properties: matcher })
*
* Example:
* ```js
* matches.properties(node, {
* matches.properties(vNode, {
* type: 'text', // Simple string match
* value: value => value.trim() !== '', // None-empty value, using a function matcher
* })
* ```
*
* @param {HTMLElement|VirtualNode} node
* @deprecated HTMLElement is deprecated, use VirtualNode instead
*
* @param {HTMLElement|VirtualNode} vNode
* @param {Object} matcher
* @returns {Boolean}
*/
matches.properties = function matchesProperties(node, matcher) {
node = node.actualNode || node;
const out = matches.fromFunction(propName => node[propName], matcher);
return out;
matches.properties = function matchesProperties(vNode, matcher) {
if (!(vNode instanceof axe.AbstractVirtualNode)) {
vNode = axe.utils.getNodeFromTree(vNode);
}
return matches.fromFunction(propName => vNode.props[propName], matcher);
};
14 changes: 11 additions & 3 deletions lib/core/base/virtual-node/virtual-node.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
let isXHTMLGlobal;

// class is unused in the file...
// eslint-disable-next-line no-unused-vars
class VirtualNode extends axe.AbstractVirtualNode {
Expand All @@ -17,6 +19,11 @@ class VirtualNode extends axe.AbstractVirtualNode {
this._isHidden = null; // will be populated by axe.utils.isHidden
this._cache = {};

if (typeof isXHTMLGlobal === 'undefined') {
isXHTMLGlobal = axe.utils.isXHTML(node.ownerDocument);
}
this._isXHTML = isXHTMLGlobal;

if (axe._cache.get('nodeMap')) {
axe._cache.get('nodeMap').set(node, this);
}
Expand All @@ -25,13 +32,14 @@ class VirtualNode extends axe.AbstractVirtualNode {
// abstract Node properties so we can run axe in DOM-less environments.
// add to the prototype so memory is shared across all virtual nodes
get props() {
const { nodeType, nodeName, id, type } = this.actualNode;
const { nodeType, nodeName, id, type, multiple } = this.actualNode;

return {
nodeType,
nodeName: nodeName.toLowerCase(),
nodeName: this._isXHTML ? nodeName : nodeName.toLowerCase(),
id,
type
type,
multiple
};
}

Expand Down