Skip to content

Commit

Permalink
feat(matches): use VirtualNode and deprecate HTMLElement (#1988)
Browse files Browse the repository at this point in the history
* fix(matchers): use VirtualNode functions

* [WIP] feat(matches, convert-selector, matchers): use virtual node for commons.matchers

* add tests for matches

* refactor

* fixes

* test with serialNode

* match multiple expressions
  • Loading branch information
straker committed Jan 29, 2020
1 parent 414dfb1 commit 2600a06
Show file tree
Hide file tree
Showing 13 changed files with 832 additions and 429 deletions.
20 changes: 12 additions & 8 deletions lib/commons/matches/attributes.js
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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

0 comments on commit 2600a06

Please sign in to comment.