diff --git a/lib/checks/aria/aria-allowed-role-evaluate.js b/lib/checks/aria/aria-allowed-role-evaluate.js
index 5ca99e9fc9..40e0df04b6 100644
--- a/lib/checks/aria/aria-allowed-role-evaluate.js
+++ b/lib/checks/aria/aria-allowed-role-evaluate.js
@@ -1,4 +1,4 @@
-import { isVisible } from '../../commons/dom';
+import { isVisibleForScreenreader } from '../../commons/dom';
import { getElementUnallowedRoles } from '../../commons/aria';
/**
@@ -29,7 +29,7 @@ function ariaAllowedRoleEvaluate(node, options = {}, virtualNode) {
const unallowedRoles = getElementUnallowedRoles(virtualNode, allowImplicit);
if (unallowedRoles.length) {
this.data(unallowedRoles);
- if (!isVisible(virtualNode, true)) {
+ if (!isVisibleForScreenreader(virtualNode)) {
// flag hidden elements for review
return undefined;
}
diff --git a/lib/checks/aria/aria-errormessage-evaluate.js b/lib/checks/aria/aria-errormessage-evaluate.js
index bacb1c9c7c..482a7d6491 100644
--- a/lib/checks/aria/aria-errormessage-evaluate.js
+++ b/lib/checks/aria/aria-errormessage-evaluate.js
@@ -1,7 +1,7 @@
import standards from '../../standards';
import { idrefs } from '../../commons/dom';
import { tokenList } from '../../core/utils';
-import { isVisible } from '../../commons/dom';
+import { isVisibleForScreenreader } from '../../commons/dom';
/**
* Check if `aria-errormessage` references an element that also uses a technique to announce the message (aria-live, aria-describedby, etc.).
*
@@ -55,7 +55,7 @@ function ariaErrormessageEvaluate(node, options, virtualNode) {
}
if (idref) {
- if (!isVisible(idref, true)) {
+ if (!isVisibleForScreenreader(idref)) {
this.data({
messageKey: 'hidden',
values: tokenList(attr)
diff --git a/lib/checks/color/color-contrast-evaluate.js b/lib/checks/color/color-contrast-evaluate.js
index e10b0bc6bf..5895a6250e 100644
--- a/lib/checks/color/color-contrast-evaluate.js
+++ b/lib/checks/color/color-contrast-evaluate.js
@@ -1,4 +1,4 @@
-import { isVisible } from '../../commons/dom';
+import { isVisibleOnScreen } from '../../commons/dom';
import {
visibleVirtual,
hasUnicode,
@@ -29,7 +29,7 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
pseudoSizeThreshold
} = options;
- if (!isVisible(node, false)) {
+ if (!isVisibleOnScreen(node)) {
this.data({ messageKey: 'hidden' });
return true;
}
diff --git a/lib/checks/generic/has-descendant-evaluate.js b/lib/checks/generic/has-descendant-evaluate.js
index b4e50a549a..62824a54f1 100644
--- a/lib/checks/generic/has-descendant-evaluate.js
+++ b/lib/checks/generic/has-descendant-evaluate.js
@@ -1,5 +1,5 @@
import { querySelectorAllFilter } from '../../core/utils';
-import { isVisible, isModalOpen } from '../../commons/dom';
+import { isVisibleForScreenreader, isModalOpen } from '../../commons/dom';
function hasDescendant(node, options, virtualNode) {
if (!options || !options.selector || typeof options.selector !== 'string') {
@@ -15,7 +15,7 @@ function hasDescendant(node, options, virtualNode) {
const matchingElms = querySelectorAllFilter(
virtualNode,
options.selector,
- vNode => isVisible(vNode.actualNode, true)
+ vNode => isVisibleForScreenreader(vNode)
);
this.relatedNodes(matchingElms.map(vNode => vNode.actualNode));
return matchingElms.length > 0;
diff --git a/lib/checks/generic/page-no-duplicate-evaluate.js b/lib/checks/generic/page-no-duplicate-evaluate.js
index 5d87ec0277..93cfac680d 100644
--- a/lib/checks/generic/page-no-duplicate-evaluate.js
+++ b/lib/checks/generic/page-no-duplicate-evaluate.js
@@ -1,6 +1,6 @@
import cache from '../../core/base/cache';
import { querySelectorAllFilter } from '../../core/utils';
-import { isVisible, findUpVirtual } from '../../commons/dom';
+import { isVisibleForScreenreader, findUpVirtual } from '../../commons/dom';
function pageNoDuplicateEvaluate(node, options, virtualNode) {
if (!options || !options.selector || typeof options.selector !== 'string') {
@@ -18,7 +18,7 @@ function pageNoDuplicateEvaluate(node, options, virtualNode) {
cache.set(key, true);
let elms = querySelectorAllFilter(axe._tree[0], options.selector, elm =>
- isVisible(elm.actualNode, true)
+ isVisibleForScreenreader(elm)
);
// Filter elements that, within certain contexts, don't map their role.
diff --git a/lib/checks/keyboard/accesskeys-evaluate.js b/lib/checks/keyboard/accesskeys-evaluate.js
index 36dcef1b9a..97eb36169d 100644
--- a/lib/checks/keyboard/accesskeys-evaluate.js
+++ b/lib/checks/keyboard/accesskeys-evaluate.js
@@ -1,7 +1,7 @@
-import { isVisible } from '../../commons/dom';
+import { isVisibleOnScreen } from '../../commons/dom';
function accesskeysEvaluate(node) {
- if (isVisible(node, false)) {
+ if (isVisibleOnScreen(node)) {
this.data(node.getAttribute('accesskey'));
this.relatedNodes([node]);
}
diff --git a/lib/checks/label/explicit-evaluate.js b/lib/checks/label/explicit-evaluate.js
index e118360e49..b056b8e41d 100644
--- a/lib/checks/label/explicit-evaluate.js
+++ b/lib/checks/label/explicit-evaluate.js
@@ -1,4 +1,4 @@
-import { getRootNode, isVisible } from '../../commons/dom';
+import { getRootNode, isVisibleOnScreen } from '../../commons/dom';
import { accessibleText } from '../../commons/text';
import { escapeSelector } from '../../core/utils';
@@ -16,7 +16,7 @@ function explicitEvaluate(node, options, virtualNode) {
try {
return labels.some(label => {
// defer to hidden-explicit-label check for better messaging
- if (!isVisible(label)) {
+ if (!isVisibleOnScreen(label)) {
return true;
} else {
return !!accessibleText(label);
diff --git a/lib/checks/label/hidden-explicit-label-evaluate.js b/lib/checks/label/hidden-explicit-label-evaluate.js
index 06c2678acf..e69fb09a99 100644
--- a/lib/checks/label/hidden-explicit-label-evaluate.js
+++ b/lib/checks/label/hidden-explicit-label-evaluate.js
@@ -1,4 +1,4 @@
-import { getRootNode, isVisible } from '../../commons/dom';
+import { getRootNode, isVisibleForScreenreader } from '../../commons/dom';
import { accessibleTextVirtual } from '../../commons/text';
import { escapeSelector } from '../../core/utils';
@@ -12,7 +12,7 @@ function hiddenExplicitLabelEvaluate(node, options, virtualNode) {
const id = escapeSelector(node.getAttribute('id'));
const label = root.querySelector(`label[for="${id}"]`);
- if (label && !isVisible(label, true)) {
+ if (label && !isVisibleForScreenreader(label)) {
let name;
try {
name = accessibleTextVirtual(virtualNode).trim();
diff --git a/lib/checks/label/multiple-label-evaluate.js b/lib/checks/label/multiple-label-evaluate.js
index baae68963b..a2f4964d5c 100644
--- a/lib/checks/label/multiple-label-evaluate.js
+++ b/lib/checks/label/multiple-label-evaluate.js
@@ -1,4 +1,4 @@
-import { getRootNode, isVisible, idrefs } from '../../commons/dom';
+import { getRootNode, isVisibleOnScreen, isVisibleForScreenreader, idrefs } from '../../commons/dom';
import { escapeSelector } from '../../core/utils';
function multipleLabelEvaluate(node) {
@@ -10,7 +10,7 @@ function multipleLabelEvaluate(node) {
if (labels.length) {
// filter out CSS hidden labels because they're fine
- labels = labels.filter(label => isVisible(label));
+ labels = labels.filter(label => isVisibleOnScreen(label));
}
while (parent) {
@@ -27,7 +27,7 @@ function multipleLabelEvaluate(node) {
// more than 1 CSS visible label
if (labels.length > 1) {
- const ATVisibleLabels = labels.filter(label => isVisible(label, true));
+ const ATVisibleLabels = labels.filter(label => isVisibleForScreenreader(label));
// more than 1 AT visible label will fail IOS/Safari/VO even with aria-labelledby
if (ATVisibleLabels.length > 1) {
return undefined;
diff --git a/lib/checks/lists/only-dlitems-evaluate.js b/lib/checks/lists/only-dlitems-evaluate.js
index 98663e3f72..985d1a7841 100644
--- a/lib/checks/lists/only-dlitems-evaluate.js
+++ b/lib/checks/lists/only-dlitems-evaluate.js
@@ -1,4 +1,4 @@
-import { isVisible } from '../../commons/dom';
+import { isVisibleForScreenreader } from '../../commons/dom';
import { getRole, getExplicitRole } from '../../commons/aria';
function onlyDlitemsEvaluate(node, options, virtualNode) {
@@ -23,7 +23,7 @@ function onlyDlitemsEvaluate(node, options, virtualNode) {
const { actualNode } = childNode;
const tagName = actualNode.nodeName.toUpperCase();
- if (actualNode.nodeType === 1 && isVisible(actualNode, true, false)) {
+ if (actualNode.nodeType === 1 && isVisibleForScreenreader(actualNode)) {
const explicitRole = getExplicitRole(actualNode);
if ((tagName !== 'DT' && tagName !== 'DD') || explicitRole) {
diff --git a/lib/checks/lists/only-listitems-evaluate.js b/lib/checks/lists/only-listitems-evaluate.js
index 6b0d926a50..8e1b7788e3 100644
--- a/lib/checks/lists/only-listitems-evaluate.js
+++ b/lib/checks/lists/only-listitems-evaluate.js
@@ -1,4 +1,4 @@
-import { isVisible } from '../../commons/dom';
+import { isVisibleForScreenreader } from '../../commons/dom';
import { getRole } from '../../commons/aria';
function onlyListitemsEvaluate(node, options, virtualNode) {
@@ -17,7 +17,7 @@ function onlyListitemsEvaluate(node, options, virtualNode) {
return;
}
- if (actualNode.nodeType !== 1 || !isVisible(actualNode, true, false)) {
+ if (actualNode.nodeType !== 1 || !isVisibleForScreenreader(actualNode)) {
return;
}
diff --git a/lib/checks/navigation/heading-order-evaluate.js b/lib/checks/navigation/heading-order-evaluate.js
index 4585e92ad1..c3600cedb9 100644
--- a/lib/checks/navigation/heading-order-evaluate.js
+++ b/lib/checks/navigation/heading-order-evaluate.js
@@ -1,6 +1,6 @@
import cache from '../../core/base/cache';
import { querySelectorAllFilter, getAncestry } from '../../core/utils';
-import { isVisible } from '../../commons/dom';
+import { isVisibleForScreenreader } from '../../commons/dom';
import { getRole } from '../../commons/aria';
function getLevel(vNode) {
@@ -54,8 +54,10 @@ function headingOrderEvaluate() {
// @see https://github.com/dequelabs/axe-core/issues/728
const selector = 'h1, h2, h3, h4, h5, h6, [role=heading], iframe, frame';
// TODO: es-modules_tree
- const vNodes = querySelectorAllFilter(axe._tree[0], selector, vNode =>
- isVisible(vNode.actualNode, true)
+ const vNodes = querySelectorAllFilter(
+ axe._tree[0],
+ selector,
+ isVisibleForScreenreader
);
headingOrder = vNodes.map(vNode => {
diff --git a/lib/checks/navigation/region-evaluate.js b/lib/checks/navigation/region-evaluate.js
index 48b86e92f1..a4b34387ae 100644
--- a/lib/checks/navigation/region-evaluate.js
+++ b/lib/checks/navigation/region-evaluate.js
@@ -46,7 +46,7 @@ function findRegionlessElms(virtualNode, options) {
['iframe', 'frame'].includes(virtualNode.props.nodeName) ||
(dom.isSkipLink(virtualNode.actualNode) &&
dom.getElementByReference(virtualNode.actualNode, 'href')) ||
- !dom.isVisible(node, true)
+ !dom.isVisibleForScreenreader(node)
) {
// Mark each parent node as having region descendant
let vNode = virtualNode;
diff --git a/lib/checks/navigation/skip-link-evaluate.js b/lib/checks/navigation/skip-link-evaluate.js
index 292b4e3651..b691810ce7 100644
--- a/lib/checks/navigation/skip-link-evaluate.js
+++ b/lib/checks/navigation/skip-link-evaluate.js
@@ -1,9 +1,9 @@
-import { getElementByReference, isVisible } from '../../commons/dom';
+import { getElementByReference, isVisibleForScreenreader } from '../../commons/dom';
function skipLinkEvaluate(node) {
const target = getElementByReference(node, 'href');
if (target) {
- return isVisible(target, true) || undefined;
+ return isVisibleForScreenreader(target) || undefined;
}
return false;
}
diff --git a/lib/checks/shared/is-on-screen-evaluate.js b/lib/checks/shared/is-on-screen-evaluate.js
index e289511436..1c525f0532 100644
--- a/lib/checks/shared/is-on-screen-evaluate.js
+++ b/lib/checks/shared/is-on-screen-evaluate.js
@@ -1,8 +1,8 @@
-import { isVisible, isOffscreen } from '../../commons/dom';
+import { isVisibleOnScreen, isOffscreen } from '../../commons/dom';
function isOnScreenEvaluate(node) {
// From a visual perspective
- return isVisible(node, false) && !isOffscreen(node);
+ return isVisibleOnScreen(node) && !isOffscreen(node);
}
export default isOnScreenEvaluate;
diff --git a/lib/commons/dom/focus-disabled.js b/lib/commons/dom/focus-disabled.js
index 108ec7b301..34e75bc0ad 100644
--- a/lib/commons/dom/focus-disabled.js
+++ b/lib/commons/dom/focus-disabled.js
@@ -1,6 +1,6 @@
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';
-import isHiddenWithCSS from './is-hidden-with-css';
+import isHiddenForEveryone from './is-hidden-for-everyone';
// Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
const allowedDisabledNodeNames = [
'button',
@@ -74,7 +74,7 @@ function focusDisabled(el) {
if (!vNode.actualNode) {
return false;
}
- return isHiddenWithCSS(vNode.actualNode);
+ return isHiddenForEveryone(vNode);
}
return false;
diff --git a/lib/commons/dom/get-rect-stack.js b/lib/commons/dom/get-rect-stack.js
index 6febf1a404..e2872886d2 100644
--- a/lib/commons/dom/get-rect-stack.js
+++ b/lib/commons/dom/get-rect-stack.js
@@ -1,4 +1,4 @@
-import isVisible from './is-visible';
+import isVisibleOnScreen from './is-visible-on-screen';
import VirtualNode from '../../core/base/virtual-node/virtual-node';
import { getNodeFromTree, getScroll, isShadowRoot } from '../../core/utils';
@@ -80,7 +80,7 @@ export function createGrid(
// (we don't do this before so we can calculate stacking context
// of parents with 0 width/height)
const rect = vNode.boundingClientRect;
- if (rect.width !== 0 && rect.height !== 0 && isVisible(node)) {
+ if (rect.width !== 0 && rect.height !== 0 && isVisibleOnScreen(node)) {
addNodeToGrid(grid, vNode);
}
diff --git a/lib/commons/dom/has-lang-text.js b/lib/commons/dom/has-lang-text.js
index 6dceb6f440..11c37d020d 100644
--- a/lib/commons/dom/has-lang-text.js
+++ b/lib/commons/dom/has-lang-text.js
@@ -1,6 +1,6 @@
import { hasChildTextNodes } from './has-content-virtual';
import isVisualContent from './is-visual-content';
-import isHiddenWithCSS from './is-hidden-with-css';
+import isHiddenForEveryone from './is-hidden-for-everyone';
/**
* Check that a node has text, or an accessible name which language is defined by the
@@ -23,6 +23,6 @@ export default function hasLangText(virtualNode) {
child =>
!child.attr('lang') && // non-empty lang
hasLangText(child) && // has text
- !isHiddenWithCSS(child) // Not hidden for anyone
+ !isHiddenForEveryone(child) // Not hidden for anyone
);
}
diff --git a/lib/commons/dom/index.js b/lib/commons/dom/index.js
index 0e23bfa913..b9be84acbe 100644
--- a/lib/commons/dom/index.js
+++ b/lib/commons/dom/index.js
@@ -23,6 +23,7 @@ export { default as insertedIntoFocusOrder } from './inserted-into-focus-order';
export { default as isCurrentPageLink } from './is-current-page-link';
export { default as isFocusable } from './is-focusable';
export { default as isHiddenWithCSS } from './is-hidden-with-css';
+export { default as isHiddenForEveryone } from './is-hidden-for-everyone';
export { default as isHTML5 } from './is-html5';
export { default as isInTabOrder } from './is-in-tab-order';
export { default as isInTextBlock } from './is-in-text-block';
@@ -33,6 +34,8 @@ export { default as isNode } from './is-node';
export { default as isOffscreen } from './is-offscreen';
export { default as isOpaque } from './is-opaque';
export { default as isSkipLink } from './is-skip-link';
+export { default as isVisibleForScreenreader } from './is-visible-for-screenreader';
+export { default as isVisibleOnScreen } from './is-visible-on-screen';
export { default as isVisible } from './is-visible';
export { default as isVisualContent } from './is-visual-content';
export { default as reduceToElementsBelowFloating } from './reduce-to-elements-below-floating';
diff --git a/lib/commons/dom/is-hidden-for-everyone.js b/lib/commons/dom/is-hidden-for-everyone.js
new file mode 100644
index 0000000000..a2f03c4947
--- /dev/null
+++ b/lib/commons/dom/is-hidden-for-everyone.js
@@ -0,0 +1,75 @@
+import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
+import { getNodeFromTree } from '../../core/utils';
+import memoize from '../../core/utils/memoize';
+import {
+ nativelyHidden,
+ displayHidden,
+ visibilityHidden
+} from './visibility-methods';
+
+const hiddenMethods = [displayHidden, visibilityHidden];
+
+/**
+ * Determine if an element is hidden from screenreaders and visual users
+ * @method isHiddenForEveryone
+ * @memberof axe.commons.dom
+ * @param {VirtualNode} vNode The Virtual Node
+ * @param {Object} [options]
+ * @param {Boolean} [options.skipAncestors] If the ancestor tree should be not be used
+ * @param {Boolean} [options.isAncestor] If this function is being called on an ancestor for the target node
+ * @return {Boolean} The element's visibility state
+ */
+export default function isHiddenForEveryone(
+ vNode,
+ { skipAncestors, isAncestor = false } = {}
+) {
+ vNode = vNode instanceof AbstractVirtualNode ? vNode : getNodeFromTree(vNode);
+
+ if (skipAncestors) {
+ return isHiddenSelf(vNode, isAncestor);
+ }
+
+ return isHiddenAncestors(vNode, isAncestor);
+}
+
+/**
+ * Check the element for visibility state
+ */
+const isHiddenSelf = memoize(function isHiddenSelfMemoized(vNode, isAncestor) {
+ if (nativelyHidden(vNode)) {
+ return true;
+ }
+
+ if (!vNode.actualNode) {
+ return false;
+ }
+
+ if (hiddenMethods.some(method => method(vNode, { isAncestor }))) {
+ return true;
+ }
+
+ // detached node
+ if (!vNode.actualNode.isConnected) {
+ return true;
+ }
+
+ return false;
+});
+
+/**
+ * Check the element and ancestors for visibility state
+ */
+const isHiddenAncestors = memoize(function isHiddenAncestorsMemoized(
+ vNode,
+ isAncestor
+) {
+ if (isHiddenSelf(vNode, isAncestor)) {
+ return true;
+ }
+
+ if (!vNode.parent) {
+ return false;
+ }
+
+ return isHiddenAncestors(vNode.parent, true);
+});
diff --git a/lib/commons/dom/is-hidden-with-css.js b/lib/commons/dom/is-hidden-with-css.js
index fc5a7af241..847c961ce9 100644
--- a/lib/commons/dom/is-hidden-with-css.js
+++ b/lib/commons/dom/is-hidden-with-css.js
@@ -10,6 +10,7 @@ import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-n
* @param {HTMLElement} el The HTML Element
* @param {Boolean} descendentVisibilityValue (Optional) immediate descendant visibility value used for recursive computation
* @return {Boolean} the element's hidden status
+ * @deprecated use isHiddenForEveryone
*/
function isHiddenWithCSS(node, descendentVisibilityValue) {
const vNode =
diff --git a/lib/commons/dom/is-modal-open.js b/lib/commons/dom/is-modal-open.js
index 7c71b4d5e6..c9feb673fb 100644
--- a/lib/commons/dom/is-modal-open.js
+++ b/lib/commons/dom/is-modal-open.js
@@ -1,4 +1,4 @@
-import isVisible from './is-visible';
+import isVisibleOnScreen from './is-visible-on-screen';
import getViewportSize from './get-viewport-size';
import cache from '../../core/base/cache';
import { querySelectorAllFilter } from '../../core/utils';
@@ -42,7 +42,7 @@ function isModalOpen(options) {
// TODO: es-module-_tree
axe._tree[0],
'dialog, [role=dialog], [aria-modal=true]',
- vNode => isVisible(vNode.actualNode)
+ isVisibleOnScreen
);
if (definiteModals.length) {
diff --git a/lib/commons/dom/is-offscreen.js b/lib/commons/dom/is-offscreen.js
index ed25204f19..ffad2c0470 100644
--- a/lib/commons/dom/is-offscreen.js
+++ b/lib/commons/dom/is-offscreen.js
@@ -1,6 +1,7 @@
import getComposedParent from './get-composed-parent';
import getElementCoordinates from './get-element-coordinates';
import getViewportSize from './get-viewport-size';
+import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
function noParentScrolled(element, offset) {
element = getComposedParent(element);
@@ -22,9 +23,22 @@ function noParentScrolled(element, offset) {
* @memberof axe.commons.dom
* @instance
* @param {Element} element
- * @return {Boolean}
+ * @param {Object} [options]
+ * @param {Boolean} [options.isAncestor] If this function is being called on an ancestor of the target node
+ * @return {Boolean|undefined}
*/
-function isOffscreen(element) {
+function isOffscreen(element, { isAncestor } = {}) {
+ if (isAncestor) {
+ return false;
+ }
+
+ element =
+ element instanceof AbstractVirtualNode ? element.actualNode : element;
+
+ if (!element) {
+ return undefined;
+ }
+
let leftBoundary;
const docElement = document.documentElement;
const styl = window.getComputedStyle(element);
diff --git a/lib/commons/dom/is-visible-for-screenreader.js b/lib/commons/dom/is-visible-for-screenreader.js
new file mode 100644
index 0000000000..7d1d732001
--- /dev/null
+++ b/lib/commons/dom/is-visible-for-screenreader.js
@@ -0,0 +1,42 @@
+import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
+import { getNodeFromTree } from '../../core/utils';
+import memoize from '../../core/utils/memoize';
+import isHiddenForEveryone from './is-hidden-for-everyone';
+import { ariaHidden, areaHidden } from './visibility-methods';
+
+/**
+ * Determine if an element is visible to a screen reader
+ * @method isVisibleForScreenreader
+ * @memberof axe.commons.dom
+ * @param {VirtualNode} vNode The Virtual Node
+ * @return {Boolean} True if the element is visible to a screen reader
+ */
+export default function isVisibleForScreenreader(vNode) {
+ vNode = vNode instanceof AbstractVirtualNode ? vNode : getNodeFromTree(vNode);
+ return isVisibleForScreenreaderVirtual(vNode);
+}
+
+/**
+ * Check the element and ancestors
+ */
+const isVisibleForScreenreaderVirtual = memoize(
+ function isVisibleForScreenreaderMemoized(vNode, isAncestor) {
+ if (ariaHidden(vNode)) {
+ return false;
+ }
+
+ if (vNode.actualNode && vNode.props.nodeName === 'area') {
+ return !areaHidden(vNode, isVisibleForScreenreaderVirtual);
+ }
+
+ if (isHiddenForEveryone(vNode, { skipAncestors: true, isAncestor })) {
+ return false;
+ }
+
+ if (!vNode.parent) {
+ return true;
+ }
+
+ return isVisibleForScreenreaderVirtual(vNode.parent, true);
+ }
+);
diff --git a/lib/commons/dom/is-visible-on-screen.js b/lib/commons/dom/is-visible-on-screen.js
new file mode 100644
index 0000000000..3411533c40
--- /dev/null
+++ b/lib/commons/dom/is-visible-on-screen.js
@@ -0,0 +1,58 @@
+import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
+import { getNodeFromTree } from '../../core/utils';
+import memoize from '../../core/utils/memoize';
+import isHiddenForEveryone from './is-hidden-for-everyone';
+import {
+ opacityHidden,
+ scrollHidden,
+ overflowHidden,
+ clipHidden,
+ areaHidden
+} from './visibility-methods';
+import isOffscreen from './is-offscreen';
+
+const hiddenMethods = [
+ opacityHidden,
+ scrollHidden,
+ overflowHidden,
+ clipHidden,
+ isOffscreen
+];
+
+/**
+ * Determine if an element is visible on screen
+ * @method isVisibleOnScreen
+ * @memberof axe.commons.dom
+ * @param {VirtualNode} vNode The Virtual Node
+ * @return {Boolean} True if the element is visible on screen
+ */
+export default function isVisibleOnScreen(vNode) {
+ vNode = vNode instanceof AbstractVirtualNode ? vNode : getNodeFromTree(vNode);
+ return isVisibleOnScreenVirtual(vNode);
+}
+
+const isVisibleOnScreenVirtual = memoize(function isVisibleOnScreenMemoized(
+ vNode,
+ isAncestor
+) {
+ if (vNode.actualNode && vNode.props.nodeName === 'area') {
+ return !areaHidden(vNode, isVisibleOnScreenVirtual);
+ }
+
+ if (isHiddenForEveryone(vNode, { skipAncestors: true, isAncestor })) {
+ return false;
+ }
+
+ if (
+ vNode.actualNode &&
+ hiddenMethods.some(method => method(vNode, { isAncestor }))
+ ) {
+ return false;
+ }
+
+ if (!vNode.parent) {
+ return true;
+ }
+
+ return isVisibleOnScreenVirtual(vNode.parent, true);
+});
diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js
index 8cb847719d..94b6d9f2e7 100644
--- a/lib/commons/dom/is-visible.js
+++ b/lib/commons/dom/is-visible.js
@@ -105,6 +105,7 @@ function isAreaVisible(el, screenReader, recursed) {
* @param {Boolean} screenReader When provided, will evaluate visibility from the perspective of a screen reader
* @param {Boolean} recursed
* @return {Boolean} The element's visibilty status
+ * @deprecated use isVisibleOnScreen or isVisibleForScreenreader if `screenReader: true` was passed
*/
function isVisible(el, screenReader, recursed) {
if (!el) {
diff --git a/lib/commons/dom/visibility-methods.js b/lib/commons/dom/visibility-methods.js
new file mode 100644
index 0000000000..41b35293f9
--- /dev/null
+++ b/lib/commons/dom/visibility-methods.js
@@ -0,0 +1,186 @@
+import {
+ getScroll,
+ closest,
+ getRootNode,
+ querySelectorAll,
+ escapeSelector
+} from '../../core/utils';
+
+const clipRegex =
+ /rect\s*\(([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px\s*\)/;
+const clipPathRegex = /(\w+)\((\d+)/;
+
+/**
+ * Determine if an element is natively hidden
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function nativelyHidden(vNode) {
+ return ['style', 'script', 'noscript', 'template'].includes(
+ vNode.props.nodeName
+ );
+}
+
+/**
+ * Determine if an element is hidden using the display property
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function displayHidden(vNode) {
+ // Firefox's user-agent always sets `area` element
+ // to `display:none` so we can't rely on it to
+ // check for hidden
+ if (vNode.props.nodeName === 'area') {
+ return false;
+ }
+
+ return vNode.getComputedStylePropertyValue('display') === 'none';
+}
+
+/**
+ * Determine if an element is hidden using the visibility property. Visibility is only applicable for the node itself (and not any ancestors)
+ * @param {VirtualNode} vNode
+ * @param {Object} [options]
+ * @param {Boolean} [options.isAncestor] If this function is being called on an ancestor for the target node
+ * @return {Boolean}
+ */
+export function visibilityHidden(vNode, { isAncestor } = {}) {
+ // because the parent can be hidden, and the child visible we
+ // have to ignore visibility on ancestors. we don't need to look
+ // at the parent either, because visibility inherits
+ return (
+ !isAncestor &&
+ ['hidden', 'collapse'].includes(
+ vNode.getComputedStylePropertyValue('visibility')
+ )
+ );
+}
+
+/**
+ * Determine if an element is hidden using the aria-hidden attribute
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function ariaHidden(vNode) {
+ return vNode.attr('aria-hidden') === 'true';
+}
+
+/**
+ * Determine if an element is hidden by making the opacity 0
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function opacityHidden(vNode) {
+ return vNode.getComputedStylePropertyValue('opacity') === '0';
+}
+
+/**
+ * Determine if an element is hidden by using scroll and dimensions
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function scrollHidden(vNode) {
+ const scroll = getScroll(vNode.actualNode);
+ const elHeight = parseInt(vNode.getComputedStylePropertyValue('height'));
+ const elWidth = parseInt(vNode.getComputedStylePropertyValue('width'));
+
+ return !!scroll && (elHeight === 0 || elWidth === 0);
+}
+
+/**
+ * Determine if an element is hidden by using overflow: hidden and dimensions
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function overflowHidden(vNode) {
+ const elHeight = parseInt(vNode.getComputedStylePropertyValue('height'));
+ const elWidth = parseInt(vNode.getComputedStylePropertyValue('width'));
+
+ return (
+ vNode.getComputedStylePropertyValue('position') === 'absolute' &&
+ (elHeight < 2 || elWidth < 2) &&
+ vNode.getComputedStylePropertyValue('overflow') === 'hidden'
+ );
+}
+
+/**
+ * Determines if an element is hidden with a clip or clip-path technique
+ * @param {VirtualNode} vNode
+ * @return {Boolean}
+ */
+export function clipHidden(vNode) {
+ const matchesClip = vNode
+ .getComputedStylePropertyValue('clip')
+ .match(clipRegex);
+ const matchesClipPath = vNode
+ .getComputedStylePropertyValue('clip-path')
+ .match(clipPathRegex);
+ if (matchesClip && matchesClip.length === 5) {
+ const position = vNode.getComputedStylePropertyValue('position');
+ // clip is only applied to absolutely positioned elements
+ if (['fixed', 'absolute'].includes(position)) {
+ return (
+ matchesClip[3] - matchesClip[1] <= 0 &&
+ matchesClip[2] - matchesClip[4] <= 0
+ );
+ }
+ }
+ if (matchesClipPath) {
+ const type = matchesClipPath[1];
+ const value = parseInt(matchesClipPath[2], 10);
+
+ switch (type) {
+ case 'inset':
+ return value >= 50;
+ case 'circle':
+ return value === 0;
+ default:
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Check `AREA` element is hidden
+ * - validate if it is a child of `map`
+ * - ensure `map` is referred by `img` using the `usemap` attribute
+ * @param {VirtualNode} vNode
+ * @param {Function} visibleFunction Function used to check if the image element is visible
+ * @retruns {Boolean}
+ */
+export function areaHidden(vNode, visibleFunction) {
+ /**
+ * Note:
+ * - Verified that `map` element cannot refer to `area` elements across different document trees
+ * - Verified that `map` element does not get affected by altering `display` property
+ */
+ const mapEl = closest(vNode, 'map');
+ if (!mapEl) {
+ return true;
+ }
+
+ const mapElName = mapEl.attr('name');
+ if (!mapElName) {
+ return true;
+ }
+
+ /**
+ * `map` element has to be in light DOM
+ */
+ const mapElRootNode = getRootNode(vNode.actualNode);
+ if (!mapElRootNode || mapElRootNode.nodeType !== 9) {
+ return true;
+ }
+
+ const refs = querySelectorAll(
+ // TODO: es-module-_tree
+ axe._tree,
+ `img[usemap="#${escapeSelector(mapElName)}"]`
+ );
+ if (!refs || !refs.length) {
+ return true;
+ }
+
+ return refs.some(ref => !visibleFunction(ref));
+}
diff --git a/lib/commons/text/accessible-text-virtual.js b/lib/commons/text/accessible-text-virtual.js
index cd423a2f7a..49ea9a621c 100644
--- a/lib/commons/text/accessible-text-virtual.js
+++ b/lib/commons/text/accessible-text-virtual.js
@@ -5,7 +5,7 @@ import formControlValue from './form-control-value';
import subtreeText from './subtree-text';
import titleText from './title-text';
import sanitize from './sanitize';
-import isVisible from '../dom/is-visible';
+import isVisibleForScreenreader from '../dom/is-visible-for-screenreader';
import isIconLigature from '../text/is-icon-ligature';
/**
@@ -93,7 +93,7 @@ function shouldIgnoreHidden(virtualNode, context) {
return false;
}
- return !isVisible(virtualNode, true);
+ return !isVisibleForScreenreader(virtualNode);
}
/**
@@ -137,7 +137,7 @@ function prepareContext(virtualNode, context) {
context.includeHidden === undefined
) {
context = {
- includeHidden: !isVisible(virtualNode, true),
+ includeHidden: !isVisibleForScreenreader(virtualNode),
...context
};
}
diff --git a/lib/commons/text/form-control-value.js b/lib/commons/text/form-control-value.js
index 2a4b672649..eac24b770a 100644
--- a/lib/commons/text/form-control-value.js
+++ b/lib/commons/text/form-control-value.js
@@ -9,7 +9,7 @@ import isAriaListbox from '../forms/is-aria-listbox';
import isAriaCombobox from '../forms/is-aria-combobox';
import isAriaRange from '../forms/is-aria-range';
import getOwnedVirtual from '../aria/get-owned-virtual';
-import isHiddenWithCSS from '../dom/is-hidden-with-css';
+import isHiddenForEveryone from '../dom/is-hidden-for-everyone';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree, querySelectorAll } from '../../core/utils';
import log from '../../core/log';
@@ -130,7 +130,7 @@ function ariaTextboxValue(node) {
if (!isAriaTextbox(vNode)) {
return '';
}
- if (!actualNode || (actualNode && !isHiddenWithCSS(actualNode))) {
+ if (!actualNode || (actualNode && !isHiddenForEveryone(actualNode))) {
return visibleVirtual(vNode, true);
} else {
return actualNode.textContent;
diff --git a/lib/commons/text/visible-text-nodes.js b/lib/commons/text/visible-text-nodes.js
index b82b2b0d3e..19a1067ab8 100644
--- a/lib/commons/text/visible-text-nodes.js
+++ b/lib/commons/text/visible-text-nodes.js
@@ -1,4 +1,4 @@
-import isVisible from '../dom/is-visible';
+import isVisibleOnScreen from '../dom/is-visible-on-screen';
/**
* Returns an array of visible text virtual nodes
@@ -11,7 +11,7 @@ import isVisible from '../dom/is-visible';
* @deprecated
*/
function visibleTextNodes(vNode) {
- const parentVisible = isVisible(vNode.actualNode);
+ const parentVisible = isVisibleOnScreen(vNode);
let nodes = [];
vNode.children.forEach(child => {
if (child.actualNode.nodeType === 3) {
diff --git a/lib/commons/text/visible-virtual.js b/lib/commons/text/visible-virtual.js
index 73a578b38a..3febc49743 100644
--- a/lib/commons/text/visible-virtual.js
+++ b/lib/commons/text/visible-virtual.js
@@ -1,5 +1,6 @@
import sanitize from './sanitize';
-import isVisible from '../dom/is-visible';
+import isVisibleOnScreen from '../dom/is-visible-on-screen';
+import isVisibleForScreenreader from '../dom/is-visible-for-screenreader';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';
@@ -20,12 +21,13 @@ import { getNodeFromTree } from '../../core/utils';
function visibleVirtual(element, screenReader, noRecursing) {
const vNode =
element instanceof AbstractVirtualNode ? element : getNodeFromTree(element);
+ const visibleMethod = screenReader ? isVisibleForScreenreader : isVisibleOnScreen
// if the element does not have an actual node treat it as if
// it is visible
const visible =
!element.actualNode ||
- (element.actualNode && isVisible(element.actualNode, screenReader));
+ (element.actualNode && visibleMethod(element));
const result = vNode.children
.map(child => {
diff --git a/lib/core/base/context.js b/lib/core/base/context.js
index 0b366ce431..4e593175fd 100644
--- a/lib/core/base/context.js
+++ b/lib/core/base/context.js
@@ -1,6 +1,5 @@
import createFrameContext from './create-frame-context';
import {
- isHidden,
findBy,
getNodeFromTree,
getFlattenedTree,
@@ -10,6 +9,7 @@ import {
respondable,
clone
} from '../utils';
+import { isVisibleForScreenreader } from '../../commons/dom'
/**
* Pushes a unique frame onto `frames` array, filtering any hidden iframes
@@ -18,7 +18,7 @@ import {
* @param {HTMLElement} frame The frame to push onto Context
*/
function pushUniqueFrame(context, frame) {
- if (isHidden(frame) || findBy(context.frames, 'node', frame)) {
+ if (!isVisibleForScreenreader(frame) || findBy(context.frames, 'node', frame)) {
return;
}
context.frames.push(createFrameContext(frame, context));
diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js
index abda44ade1..74a7b347bc 100644
--- a/lib/core/base/rule.js
+++ b/lib/core/base/rule.js
@@ -8,9 +8,9 @@ import {
queue,
DqElement,
select,
- isHidden,
assert
} from '../utils';
+import { isVisibleForScreenreader } from '../../commons/dom';
import constants from '../constants';
import log from '../log';
@@ -130,8 +130,8 @@ Rule.prototype.matches = function matches() {
Rule.prototype.gather = function gather(context, options = {}) {
const markStart = 'mark_gather_start_' + this.id;
const markEnd = 'mark_gather_end_' + this.id;
- const markHiddenStart = 'mark_isHidden_start_' + this.id;
- const markHiddenEnd = 'mark_isHidden_end_' + this.id;
+ const markHiddenStart = 'mark_isVisibleForScreenreader_start_' + this.id;
+ const markHiddenEnd = 'mark_isVisibleForScreenreader_end_' + this.id;
if (options.performanceTimer) {
performanceTimer.mark(markStart);
@@ -144,13 +144,13 @@ Rule.prototype.gather = function gather(context, options = {}) {
}
elements = elements.filter(element => {
- return !isHidden(element.actualNode);
+ return isVisibleForScreenreader(element);
});
if (options.performanceTimer) {
performanceTimer.mark(markHiddenEnd);
performanceTimer.measure(
- 'rule_' + this.id + '#gather_axe.utils.isHidden',
+ 'rule_' + this.id + '#gather_axe.utils.isVisibleForScreenreader',
markHiddenStart,
markHiddenEnd
);
diff --git a/lib/core/core.js b/lib/core/core.js
index 2aded83b4a..b7690fe860 100644
--- a/lib/core/core.js
+++ b/lib/core/core.js
@@ -51,6 +51,17 @@ import {
getNodesMatchingExpression
} from './utils/selector-cache';
import { convertSelector } from './utils/matches';
+import {
+ nativelyHidden,
+ displayHidden,
+ visibilityHidden,
+ ariaHidden,
+ opacityHidden,
+ scrollHidden,
+ overflowHidden,
+ clipHidden,
+ areaHidden
+} from '../commons/dom/visibility-methods';
axe.constants = constants;
axe.log = log;
@@ -78,8 +89,22 @@ axe._thisWillBeDeletedDoNotUse.public = {
axe._thisWillBeDeletedDoNotUse.utils =
axe._thisWillBeDeletedDoNotUse.utils || {};
axe._thisWillBeDeletedDoNotUse.utils.cacheNodeSelectors = cacheNodeSelectors;
-axe._thisWillBeDeletedDoNotUse.utils.getNodesMatchingExpression = getNodesMatchingExpression;
+axe._thisWillBeDeletedDoNotUse.utils.getNodesMatchingExpression =
+ getNodesMatchingExpression;
axe._thisWillBeDeletedDoNotUse.utils.convertSelector = convertSelector;
+axe._thisWillBeDeletedDoNotUse.commons =
+ axe._thisWillBeDeletedDoNotUse.commons || {};
+axe._thisWillBeDeletedDoNotUse.commons.dom =
+ axe._thisWillBeDeletedDoNotUse.commons.dom || {};
+axe._thisWillBeDeletedDoNotUse.commons.dom.nativelyHidden = nativelyHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.displayHidden = displayHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.visibilityHidden = visibilityHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.ariaHidden = ariaHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.opacityHidden = opacityHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.scrollHidden = scrollHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.overflowHidden = overflowHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.clipHidden = clipHidden;
+axe._thisWillBeDeletedDoNotUse.commons.dom.areaHidden = areaHidden;
axe.imports = imports;
diff --git a/lib/core/utils/is-hidden.js b/lib/core/utils/is-hidden.js
index 94ce47f264..61f2bf1a11 100644
--- a/lib/core/utils/is-hidden.js
+++ b/lib/core/utils/is-hidden.js
@@ -7,6 +7,7 @@ import getNodeFromTree from './get-node-from-tree';
* @param {HTMLElement} el The HTMLElement
* @param {Boolean} recursed
* @return {Boolean} The element's visibilty status
+ * @deprecated use isVisibleToScreenreader
*/
function isHidden(el, recursed) {
const node = getNodeFromTree(el);
diff --git a/lib/core/utils/pollyfills.js b/lib/core/utils/pollyfills.js
index 2afe6d6cd5..fe1de7f354 100644
--- a/lib/core/utils/pollyfills.js
+++ b/lib/core/utils/pollyfills.js
@@ -6,8 +6,8 @@
- Array.prototype.find
*/
if (typeof Object.assign !== 'function') {
- (function() {
- Object.assign = function(target) {
+ (function () {
+ Object.assign = function (target) {
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
@@ -30,7 +30,7 @@ if (typeof Object.assign !== 'function') {
if (!Array.prototype.find) {
Object.defineProperty(Array.prototype, 'find', {
- value: function(predicate) {
+ value: function (predicate) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
@@ -55,7 +55,7 @@ if (!Array.prototype.find) {
if (!Array.prototype.findIndex) {
Object.defineProperty(Array.prototype, 'findIndex', {
- value: function(predicate, thisArg) {
+ value: function (predicate, thisArg) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
@@ -82,7 +82,7 @@ export function pollyfillElementsFromPoint() {
if (document.elementsFromPoint) return document.elementsFromPoint;
if (document.msElementsFromPoint) return document.msElementsFromPoint;
- var usePointer = (function() {
+ var usePointer = (function () {
var element = document.createElement('x');
element.style.cssText = 'pointer-events:auto';
return element.style.pointerEvents === 'auto';
@@ -96,7 +96,7 @@ export function pollyfillElementsFromPoint() {
? '* { pointer-events: all }'
: '* { visibility: visible }';
- return function(x, y) {
+ return function (x, y) {
var current, i, d;
var elements = [];
var previousPointerEvents = [];
@@ -153,7 +153,7 @@ if (typeof window.addEventListener === 'function') {
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
- value: function(searchElement) {
+ value: function (searchElement) {
var O = Object(this);
var len = parseInt(O.length, 10) || 0;
if (len === 0) {
@@ -190,7 +190,7 @@ if (!Array.prototype.includes) {
// Reference: http://es5.github.io/#x15.4.4.17
if (!Array.prototype.some) {
Object.defineProperty(Array.prototype, 'some', {
- value: function(fun) {
+ value: function (fun) {
if (this == null) {
throw new TypeError('Array.prototype.some called on null or undefined');
}
@@ -216,14 +216,14 @@ if (!Array.prototype.some) {
if (!Array.from) {
Object.defineProperty(Array, 'from', {
- value: (function() {
+ value: (function () {
var toStr = Object.prototype.toString;
- var isCallable = function(fn) {
+ var isCallable = function (fn) {
return (
typeof fn === 'function' || toStr.call(fn) === '[object Function]'
);
};
- var toInteger = function(value) {
+ var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
@@ -234,7 +234,7 @@ if (!Array.from) {
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
- var toLength = function(value) {
+ var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
@@ -307,7 +307,7 @@ if (!Array.from) {
}
if (!String.prototype.includes) {
- String.prototype.includes = function(search, start) {
+ String.prototype.includes = function (search, start) {
if (typeof start !== 'number') {
start = 0;
}
@@ -329,7 +329,7 @@ if (!Array.prototype.flat) {
return depth
? Array.prototype.reduce.call(
this,
- function(acc, cur) {
+ function (acc, cur) {
if (Array.isArray(cur)) {
acc.push.apply(acc, flat.call(cur, depth - 1));
} else {
@@ -345,3 +345,19 @@ if (!Array.prototype.flat) {
writable: true
});
}
+
+// linked from MDN docs on isConnected
+// @see https://gist.github.com/eligrey/f109a6d0bf4efe3461201c3d7b745e8f
+if (window.Node && !('isConnected' in Node.prototype)) {
+ Object.defineProperty(Node.prototype, 'isConnected', {
+ get() {
+ return (
+ !this.ownerDocument ||
+ !(
+ this.ownerDocument.compareDocumentPosition(this) &
+ this.DOCUMENT_POSITION_DISCONNECTED
+ )
+ );
+ }
+ });
+}
diff --git a/lib/rules/autocomplete-matches.js b/lib/rules/autocomplete-matches.js
index 88cb1454ac..9d0657ba31 100644
--- a/lib/rules/autocomplete-matches.js
+++ b/lib/rules/autocomplete-matches.js
@@ -1,6 +1,6 @@
import { sanitize } from '../commons/text';
import standards from '../standards';
-import { isVisible } from '../commons/dom';
+import { isVisibleForScreenreader, isVisibleOnScreen } from '../commons/dom';
function autocompleteMatches(node, virtualNode) {
const autocomplete = virtualNode.attr('autocomplete');
@@ -46,8 +46,8 @@ function autocompleteMatches(node, virtualNode) {
if (
tabIndex === '-1' &&
virtualNode.actualNode &&
- !isVisible(virtualNode.actualNode, false) &&
- !isVisible(virtualNode.actualNode, true)
+ !isVisibleOnScreen(virtualNode) &&
+ !isVisibleForScreenreader(virtualNode)
) {
return false;
}
diff --git a/lib/rules/is-visible-matches.js b/lib/rules/is-visible-matches.js
index 6625f7b3bf..9ecb7c827d 100644
--- a/lib/rules/is-visible-matches.js
+++ b/lib/rules/is-visible-matches.js
@@ -1,5 +1,5 @@
-import { isVisible } from '../commons/dom';
+import { isVisibleOnScreen } from '../commons/dom';
export default function hasVisibleTextMatches(node) {
- return isVisible(node, false);
+ return isVisibleOnScreen(node);
}
diff --git a/lib/rules/landmark-unique-matches.js b/lib/rules/landmark-unique-matches.js
index af5409c408..9f1d87c423 100644
--- a/lib/rules/landmark-unique-matches.js
+++ b/lib/rules/landmark-unique-matches.js
@@ -1,4 +1,4 @@
-import { findUpVirtual, isVisible } from '../commons/dom';
+import { findUpVirtual, isVisibleForScreenreader } from '../commons/dom';
import { getRole } from '../commons/aria';
import { getAriaRolesByType } from '../commons/standards';
import { accessibleTextVirtual } from '../commons/text';
@@ -44,7 +44,7 @@ function landmarkUniqueMatches(node, virtualNode) {
return landmarkRoles.indexOf(role) >= 0 || role === 'region';
}
- return isLandmarkVirtual(virtualNode) && isVisible(node, true);
+ return isLandmarkVirtual(virtualNode) && isVisibleForScreenreader(node);
}
export default landmarkUniqueMatches;
diff --git a/lib/rules/link-in-text-block-matches.js b/lib/rules/link-in-text-block-matches.js
index 3ef3a50519..6ef7a0c3cc 100644
--- a/lib/rules/link-in-text-block-matches.js
+++ b/lib/rules/link-in-text-block-matches.js
@@ -1,5 +1,5 @@
import { sanitize } from '../commons/text';
-import { isVisible, isInTextBlock } from '../commons/dom';
+import { isVisibleOnScreen, isInTextBlock } from '../commons/dom';
function linkInTextBlockMatches(node) {
var text = sanitize(node.textContent);
@@ -11,7 +11,7 @@ function linkInTextBlockMatches(node) {
if (!text) {
return false;
}
- if (!isVisible(node, false)) {
+ if (!isVisibleOnScreen(node)) {
return false;
}
diff --git a/test/checks/keyboard/accesskeys.js b/test/checks/keyboard/accesskeys.js
index cd770f55df..78cf520ec7 100644
--- a/test/checks/keyboard/accesskeys.js
+++ b/test/checks/keyboard/accesskeys.js
@@ -2,7 +2,7 @@ describe('accesskeys', function() {
'use strict';
var fixture = document.getElementById('fixture');
-
+ var fixtureSetup = axe.testUtils.fixtureSetup;
var checkContext = axe.testUtils.MockCheckContext();
afterEach(function() {
@@ -12,6 +12,7 @@ describe('accesskeys', function() {
it('should return true and record accesskey', function() {
fixture.innerHTML = '
';
+ fixtureSetup();
var node = fixture.querySelector('#target');
assert.isTrue(checks.accesskeys.evaluate.call(checkContext, node));
diff --git a/test/checks/label/multiple-label.js b/test/checks/label/multiple-label.js
index 55a4118559..ae0e5d673a 100644
--- a/test/checks/label/multiple-label.js
+++ b/test/checks/label/multiple-label.js
@@ -1,18 +1,19 @@
-describe('multiple-label', function() {
+describe('multiple-label', function () {
'use strict';
var fixture = document.getElementById('fixture');
var shadowSupported = axe.testUtils.shadowSupport.v1;
var checkContext = axe.testUtils.MockCheckContext();
+ var fixtureSetup = axe.testUtils.fixtureSetup;
- afterEach(function() {
- fixture.innerHTML = '';
+ afterEach(function () {
checkContext.reset();
});
- it('should return undefined if there are multiple implicit labels', function() {
- fixture.innerHTML =
- '';
+ it('should return undefined if there are multiple implicit labels', function () {
+ fixtureSetup(
+ ''
+ );
var target = fixture.querySelector('#target');
var l1 = fixture.querySelector('#l1');
var l2 = fixture.querySelector('#l2');
@@ -24,9 +25,8 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1, l2]);
});
- it('should return false if there is only one implicit label', function() {
- fixture.innerHTML =
- '';
+ it('should return false if there is only one implicit label', function () {
+ fixtureSetup('');
var target = fixture.querySelector('#target');
var l1 = fixture.querySelector('#l1');
assert.isFalse(
@@ -37,12 +37,13 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1]);
});
- it('should return undefined if there are multiple explicit labels', function() {
- fixture.innerHTML =
+ it('should return undefined if there are multiple explicit labels', function () {
+ fixtureSetup(
'Foo' +
- 'Bar' +
- 'Bat' +
- '';
+ 'Bar' +
+ 'Bat' +
+ ''
+ );
var target = fixture.querySelector('#target');
var l1 = fixture.querySelector('#l1');
var l2 = fixture.querySelector('#l2');
@@ -55,9 +56,10 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1, l2, l3]);
});
- it('should return false if there is only one explicit label', function() {
- fixture.innerHTML =
- 'Foo';
+ it('should return false if there is only one explicit label', function () {
+ fixtureSetup(
+ 'Foo'
+ );
var target = fixture.querySelector('#target');
var l1 = fixture.querySelector('#l1');
assert.isFalse(
@@ -68,11 +70,12 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1]);
});
- it('should return false if there are multiple explicit labels but one is hidden', function() {
- fixture.innerHTML =
+ it('should return false if there are multiple explicit labels but one is hidden', function () {
+ fixtureSetup(
'label one' +
- 'label two' +
- '';
+ 'label two' +
+ ''
+ );
var target = fixture.querySelector('#test-input2');
var l1 = fixture.querySelector('#l1');
assert.isFalse(
@@ -83,12 +86,13 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1]);
});
- it('should return undefined if there are multiple explicit labels but some are hidden', function() {
- fixture.innerHTML =
+ it('should return undefined if there are multiple explicit labels but some are hidden', function () {
+ fixtureSetup(
'visible' +
- 'hidden' +
- 'visible' +
- '';
+ 'hidden' +
+ 'visible' +
+ ''
+ );
var target = fixture.querySelector('#me');
var l1 = fixture.querySelector('#l1');
var l3 = fixture.querySelector('#l3');
@@ -100,9 +104,10 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1, l3]);
});
- it('should return undefined if there are implicit and explicit labels', function() {
- fixture.innerHTML =
- 'Foo';
+ it('should return undefined if there are implicit and explicit labels', function () {
+ fixtureSetup(
+ 'Foo'
+ );
var target = fixture.querySelector('#target');
var l1 = fixture.querySelector('#l1');
var l2 = fixture.querySelector('#l2');
@@ -114,9 +119,10 @@ describe('multiple-label', function() {
assert.deepEqual(checkContext._relatedNodes, [l1, l2]);
});
- it('should return false if there an implicit label uses for attribute', function() {
- fixture.innerHTML =
- 'Foo';
+ it('should return false if there an implicit label uses for attribute', function () {
+ fixtureSetup(
+ 'Foo'
+ );
var target = fixture.querySelector('#target');
assert.isFalse(
axe.testUtils
@@ -125,14 +131,16 @@ describe('multiple-label', function() {
);
});
- it('should return undefined given multiple labels and no aria-labelledby', function() {
- fixture.innerHTML = '';
- fixture.innerHTML += 'Please';
- fixture.innerHTML += 'Excuse';
- fixture.innerHTML += 'My';
- fixture.innerHTML += 'Dear';
- fixture.innerHTML += 'Aunt';
- fixture.innerHTML += 'Sally';
+ it('should return undefined given multiple labels and no aria-labelledby', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse' +
+ 'My' +
+ 'Dear' +
+ 'Aunt' +
+ 'Sally'
+ );
var target = fixture.querySelector('#A');
assert.isUndefined(
axe.testUtils
@@ -141,14 +149,16 @@ describe('multiple-label', function() {
);
});
- it('should return undefined given multiple labels, one label AT visible, and no aria-labelledby', function() {
- fixture.innerHTML = '';
- fixture.innerHTML += 'Please';
- fixture.innerHTML += 'Excuse';
- fixture.innerHTML += 'My';
- fixture.innerHTML += 'Dear';
- fixture.innerHTML += 'Aunt';
- fixture.innerHTML += 'Sally';
+ it('should return undefined given multiple labels, one label AT visible, and no aria-labelledby', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse' +
+ 'My' +
+ 'Dear' +
+ 'Aunt' +
+ 'Sally'
+ );
var target = fixture.querySelector('#B');
assert.isUndefined(
axe.testUtils
@@ -157,10 +167,12 @@ describe('multiple-label', function() {
);
});
- it('should return false given multiple labels, one label AT visible, and aria-labelledby for AT visible', function() {
- fixture.innerHTML = '';
- fixture.innerHTML += 'Please';
- fixture.innerHTML += 'Excuse';
+ it('should return false given multiple labels, one label AT visible, and aria-labelledby for AT visible', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse'
+ );
var target = fixture.querySelector('#D');
assert.isFalse(
axe.testUtils
@@ -169,11 +181,12 @@ describe('multiple-label', function() {
);
});
- it('should return false given multiple labels, one label AT visible, and aria-labelledby for all', function() {
- fixture.innerHTML = '';
- fixture.innerHTML +=
- 'Please';
- fixture.innerHTML += 'Excuse';
+ it('should return false given multiple labels, one label AT visible, and aria-labelledby for all', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse'
+ );
var target = fixture.querySelector('#F');
assert.isFalse(
axe.testUtils
@@ -182,10 +195,12 @@ describe('multiple-label', function() {
);
});
- it('should return false given multiple labels, one label visible, and no aria-labelledby', function() {
- fixture.innerHTML = '';
- fixture.innerHTML += 'Please';
- fixture.innerHTML += 'Excuse';
+ it('should return false given multiple labels, one label visible, and no aria-labelledby', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse'
+ );
var target = fixture.querySelector('#I');
assert.isFalse(
axe.testUtils
@@ -194,15 +209,16 @@ describe('multiple-label', function() {
);
});
- it('should return undefined given multiple labels, all visible, aria-labelledby for all', function() {
- fixture.innerHTML =
- '';
- fixture.innerHTML += 'Please';
- fixture.innerHTML += 'Excuse';
- fixture.innerHTML += 'My';
- fixture.innerHTML += 'Dear';
- fixture.innerHTML += 'Aunt';
- fixture.innerHTML += 'Sally';
+ it('should return undefined given multiple labels, all visible, aria-labelledby for all', function () {
+ fixtureSetup(
+ '' +
+ 'Please' +
+ 'Excuse' +
+ 'My' +
+ 'Dear' +
+ 'Aunt' +
+ 'Sally'
+ );
var target = fixture.querySelector('#J');
assert.isUndefined(
axe.testUtils
@@ -211,10 +227,12 @@ describe('multiple-label', function() {
);
});
- it('should return undefined given multiple labels, one AT visible, no aria-labelledby', function() {
- fixture.innerHTML = '';
- fixture.innerHTML += '';
- fixture.innerHTML += 'Excuse';
+ it('should return undefined given multiple labels, one AT visible, no aria-labelledby', function () {
+ fixtureSetup(
+ '' +
+ '' +
+ 'Excuse'
+ );
var target = fixture.querySelector('#Q');
assert.isUndefined(
axe.testUtils
@@ -225,13 +243,14 @@ describe('multiple-label', function() {
(shadowSupported ? it : xit)(
'should consider labels in the same document/shadow tree',
- function() {
+ function () {
fixture.innerHTML = '';
var target = document.querySelector('#target');
var shadowRoot = target.attachShadow({ mode: 'open' });
shadowRoot.innerHTML =
'normal';
var shadowTarget = target.shadowRoot;
+ fixtureSetup();
assert.isFalse(
axe.testUtils
.getCheckEvaluate('multiple-label')
@@ -242,7 +261,7 @@ describe('multiple-label', function() {
(shadowSupported ? it : xit)(
'should return false for valid multiple labels in the same document/shadow tree',
- function() {
+ function () {
fixture.innerHTML = '';
var target = document.querySelector('#target');
var shadowRoot = target.attachShadow({ mode: 'open' });
@@ -250,6 +269,7 @@ describe('multiple-label', function() {
innerHTML += 'Please';
innerHTML += 'Excuse';
shadowRoot.innerHTML = innerHTML;
+ fixtureSetup();
var shadowTarget = target.shadowRoot;
assert.isFalse(
axe.testUtils
@@ -261,7 +281,7 @@ describe('multiple-label', function() {
(shadowSupported ? it : xit)(
'should return undefined for invalid multiple labels in the same document/shadow tree',
- function() {
+ function () {
fixture.innerHTML = '';
var target = document.querySelector('#target');
var shadowRoot = target.attachShadow({ mode: 'open' });
@@ -269,6 +289,7 @@ describe('multiple-label', function() {
innerHTML += '';
innerHTML += 'Excuse';
shadowRoot.innerHTML = innerHTML;
+ fixtureSetup();
var shadowTarget = target.shadowRoot;
assert.isUndefined(
axe.testUtils
diff --git a/test/checks/navigation/skip-link.js b/test/checks/navigation/skip-link.js
index 59cf3b9db0..ff48fb285c 100644
--- a/test/checks/navigation/skip-link.js
+++ b/test/checks/navigation/skip-link.js
@@ -3,10 +3,6 @@ describe('skip-link', function() {
var fixture = document.getElementById('fixture');
- afterEach(function() {
- fixture.innerHTML = '';
- });
-
it('should return true if the href points to an element with an ID', function() {
fixture.innerHTML =
'Click Here
Introduction
';
@@ -25,6 +21,7 @@ describe('skip-link', function() {
it('should return false if the href points to a non-existent element', function() {
fixture.innerHTML =
'Click Here
';
- var node = fixture.querySelector('#target');
+ var vNode = queryFixture('
elm
');
- assert.isFalse(axe.testUtils.getCheckEvaluate('is-on-screen')(node));
+ assert.isFalse(axe.testUtils.getCheckEvaluate('is-on-screen')(vNode));
});
it('should return false for off screen elements', function() {
- fixture.innerHTML =
- '
elm
';
- var node = fixture.querySelector('#target');
+ var vNode = queryFixture( '
elm
');
- assert.isFalse(axe.testUtils.getCheckEvaluate('is-on-screen')(node));
+ assert.isFalse(axe.testUtils.getCheckEvaluate('is-on-screen')(vNode));
});
});
diff --git a/test/commons/aria/get-role.js b/test/commons/aria/get-role.js
index 166f78e3ba..f0436fe9a0 100644
--- a/test/commons/aria/get-role.js
+++ b/test/commons/aria/get-role.js
@@ -1,75 +1,75 @@
-describe('aria.getRole', function() {
+describe('aria.getRole', function () {
'use strict';
var aria = axe.commons.aria;
var flatTreeSetup = axe.testUtils.flatTreeSetup;
var fixture = document.querySelector('#fixture');
- afterEach(function() {
+ afterEach(function () {
fixture.innerHTML = '';
});
- it('returns valid roles', function() {
+ it('returns valid roles', function () {
var node = document.createElement('div');
node.setAttribute('role', 'button');
flatTreeSetup(node);
assert.equal(aria.getRole(node), 'button');
});
- it('handles case sensitivity', function() {
+ it('handles case sensitivity', function () {
var node = document.createElement('div');
node.setAttribute('role', 'BUTTON');
flatTreeSetup(node);
assert.equal(aria.getRole(node), 'button');
});
- it('handles whitespacing', function() {
+ it('handles whitespacing', function () {
var node = document.createElement('div');
node.setAttribute('role', ' button ');
flatTreeSetup(node);
assert.equal(aria.getRole(node), 'button');
});
- it('returns null when there is no role', function() {
+ it('returns null when there is no role', function () {
var node = document.createElement('div');
flatTreeSetup(node);
assert.isNull(aria.getRole(node));
});
- it('returns the explit role if it is valid and non-abstract', function() {
+ it('returns the explit role if it is valid and non-abstract', function () {
var node = document.createElement('li');
node.setAttribute('role', 'menuitem');
flatTreeSetup(node);
assert.equal(aria.getRole(node), 'menuitem');
});
- it('returns the implicit role if the explicit is invalid', function() {
+ it('returns the implicit role if the explicit is invalid', function () {
fixture.innerHTML = '
';
flatTreeSetup(fixture);
var node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'listitem');
});
- it('ignores fallback roles by default', function() {
+ it('ignores fallback roles by default', function () {
var node = document.createElement('div');
node.setAttribute('role', 'spinbutton button');
flatTreeSetup(node);
assert.isNull(aria.getRole(node));
});
- it('accepts virtualNode objects', function() {
+ it('accepts virtualNode objects', function () {
var node = document.createElement('div');
node.setAttribute('role', 'button');
var vNode = flatTreeSetup(node)[0];
assert.equal(aria.getRole(vNode), 'button');
});
- it('returns null if the node is not an element', function() {
+ it('returns null if the node is not an element', function () {
var node = document.createTextNode('foo bar baz');
flatTreeSetup(node);
assert.isNull(aria.getRole(node));
});
- it('runs role resolution with role=none', function() {
+ it('runs role resolution with role=none', function () {
fixture.innerHTML =
'
';
flatTreeSetup(fixture);
@@ -77,7 +77,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'listitem');
});
- it('runs role resolution with role=presentation', function() {
+ it('runs role resolution with role=presentation', function () {
fixture.innerHTML =
'
';
flatTreeSetup(fixture);
@@ -85,15 +85,15 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'listitem');
});
- it('handles focusable element with role="none"', function() {
- var node = document.createElement('button');
- node.setAttribute('role', 'none');
- flatTreeSetup(node);
+ it('handles focusable element with role="none"', function () {
+ fixture.innerHTML = '';
+ flatTreeSetup(fixture);
+ var node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'button');
});
- describe('presentational role inheritance', function() {
- it('handles presentation role inheritance for ul', function() {
+ describe('presentational role inheritance', function () {
+ it('handles presentation role inheritance for ul', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -101,7 +101,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for ol', function() {
+ it('handles presentation role inheritance for ol', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -109,7 +109,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for dt', function() {
+ it('handles presentation role inheritance for dt', function () {
fixture.innerHTML =
'
foo
bar>
';
flatTreeSetup(fixture);
@@ -117,7 +117,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for dd', function() {
+ it('handles presentation role inheritance for dd', function () {
fixture.innerHTML =
'
foo
bar>
';
flatTreeSetup(fixture);
@@ -125,7 +125,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for dt with div wrapper', function() {
+ it('handles presentation role inheritance for dt with div wrapper', function () {
fixture.innerHTML =
'
foo
bar>
';
flatTreeSetup(fixture);
@@ -133,7 +133,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for dd with div wrapper', function() {
+ it('handles presentation role inheritance for dd with div wrapper', function () {
fixture.innerHTML =
'
foo
bar>
';
flatTreeSetup(fixture);
@@ -141,7 +141,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for thead', function() {
+ it('handles presentation role inheritance for thead', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -149,7 +149,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for td', function() {
+ it('handles presentation role inheritance for td', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -157,7 +157,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for th', function() {
+ it('handles presentation role inheritance for th', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -165,7 +165,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for tbody', function() {
+ it('handles presentation role inheritance for tbody', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -173,7 +173,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for tr', function() {
+ it('handles presentation role inheritance for tr', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -181,7 +181,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('handles presentation role inheritance for tfoot', function() {
+ it('handles presentation role inheritance for tfoot', function () {
fixture.innerHTML =
'
hi
goodbye
hi
foo
';
flatTreeSetup(fixture);
@@ -189,7 +189,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('returns implicit role for presentation role inheritance if ancestor is not the required ancestor', function() {
+ it('returns implicit role for presentation role inheritance if ancestor is not the required ancestor', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -197,7 +197,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'listitem');
});
- it('does not override explicit role with presentation role inheritance', function() {
+ it('does not override explicit role with presentation role inheritance', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -205,7 +205,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'listitem');
});
- it('does not continue presentation role with explicit role in between', function() {
+ it('does not continue presentation role with explicit role in between', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -213,7 +213,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'cell');
});
- it('handles presentation role inheritance with invalid role in between', function() {
+ it('handles presentation role inheritance with invalid role in between', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -221,7 +221,7 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'presentation');
});
- it('does not continue presentation role through nested layers', function() {
+ it('does not continue presentation role through nested layers', function () {
fixture.innerHTML =
'
foo
';
flatTreeSetup(fixture);
@@ -229,32 +229,32 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node), 'listitem');
});
- it('throws an error if the tree is incomplete', function() {
+ it('throws an error if the tree is incomplete', function () {
fixture.innerHTML =
'
foo
';
var node = fixture.querySelector('#target');
flatTreeSetup(node);
- assert.throws(function() {
+ assert.throws(function () {
aria.getRole(node);
});
});
});
- describe('noImplicit', function() {
- it('returns the implicit role by default', function() {
+ describe('noImplicit', function () {
+ it('returns the implicit role by default', function () {
fixture.innerHTML = '
';
flatTreeSetup(fixture);
var node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'listitem');
});
- it('returns null rather than the implicit role with `noImplicit: true`', function() {
+ it('returns null rather than the implicit role with `noImplicit: true`', function () {
var node = document.createElement('li');
flatTreeSetup(node);
assert.isNull(aria.getRole(node, { noImplicit: true }));
});
- it('does not do role resolution if noImplicit: true', function() {
+ it('does not do role resolution if noImplicit: true', function () {
var node = document.createElement('li');
node.setAttribute('role', 'none');
node.setAttribute('aria-label', 'foo');
@@ -262,14 +262,14 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node, { noImplicit: true }), null);
});
- it('still returns the explicit role', function() {
+ it('still returns the explicit role', function () {
var node = document.createElement('li');
node.setAttribute('role', 'button');
flatTreeSetup(node);
assert.equal(aria.getRole(node, { noImplicit: true }), 'button');
});
- it('returns the implicit role with `noImplicit: false`', function() {
+ it('returns the implicit role with `noImplicit: false`', function () {
fixture.innerHTML = '
';
flatTreeSetup(fixture);
var node = fixture.querySelector('#target');
@@ -277,22 +277,22 @@ describe('aria.getRole', function() {
});
});
- describe('abstracts', function() {
- it('ignores abstract roles by default', function() {
+ describe('abstracts', function () {
+ it('ignores abstract roles by default', function () {
fixture.innerHTML = '
';
flatTreeSetup(fixture);
var node = fixture.querySelector('#target');
assert.equal(aria.getRole(node), 'listitem');
});
- it('returns abstract roles with `abstracts: true`', function() {
+ it('returns abstract roles with `abstracts: true`', function () {
var node = document.createElement('li');
node.setAttribute('role', 'section');
flatTreeSetup(node);
assert.equal(aria.getRole(node, { abstracts: true }), 'section');
});
- it('does not returns abstract roles with `abstracts: false`', function() {
+ it('does not returns abstract roles with `abstracts: false`', function () {
fixture.innerHTML = '
';
flatTreeSetup(fixture);
var node = fixture.querySelector('#target');
@@ -300,22 +300,22 @@ describe('aria.getRole', function() {
});
});
- describe('dpub', function() {
- it('ignores DPUB roles by default', function() {
+ describe('dpub', function () {
+ it('ignores DPUB roles by default', function () {
var node = document.createElement('section');
node.setAttribute('role', 'doc-chapter');
flatTreeSetup(node);
assert.isNull(aria.getRole(node));
});
- it('returns DPUB roles with `dpub: true`', function() {
+ it('returns DPUB roles with `dpub: true`', function () {
var node = document.createElement('section');
node.setAttribute('role', 'doc-chapter');
flatTreeSetup(node);
assert.equal(aria.getRole(node, { dpub: true }), 'doc-chapter');
});
- it('does not returns DPUB roles with `dpub: false`', function() {
+ it('does not returns DPUB roles with `dpub: false`', function () {
var node = document.createElement('section');
node.setAttribute('role', 'doc-chapter');
flatTreeSetup(node);
@@ -323,29 +323,29 @@ describe('aria.getRole', function() {
});
});
- describe('fallback', function() {
- it('returns the first valid item in the list', function() {
+ describe('fallback', function () {
+ it('returns the first valid item in the list', function () {
var node = document.createElement('div');
node.setAttribute('role', 'link button');
flatTreeSetup(node);
assert.equal(aria.getRole(node, { fallback: true }), 'link');
});
- it('skips over invalid roles', function() {
+ it('skips over invalid roles', function () {
var node = document.createElement('div');
node.setAttribute('role', 'foobar button');
flatTreeSetup(node);
assert.equal(aria.getRole(node, { fallback: true }), 'button');
});
- it('returns the null if all roles are invalid and there is no implicit role', function() {
+ it('returns the null if all roles are invalid and there is no implicit role', function () {
var node = document.createElement('div');
node.setAttribute('role', 'foo bar baz');
flatTreeSetup(node);
assert.isNull(aria.getRole(node, { fallback: true }));
});
- it('respects the defaults', function() {
+ it('respects the defaults', function () {
fixture.innerHTML =
'
';
flatTreeSetup(fixture);
@@ -353,14 +353,14 @@ describe('aria.getRole', function() {
assert.equal(aria.getRole(node, { fallback: true }), 'listitem');
});
- it('respect the `noImplicit` option', function() {
+ it('respect the `noImplicit` option', function () {
var node = document.createElement('li');
node.setAttribute('role', 'doc-chapter section');
flatTreeSetup(node);
assert.isNull(aria.getRole(node, { fallback: true, noImplicit: true }));
});
- it('respect the `abstracts` option', function() {
+ it('respect the `abstracts` option', function () {
var node = document.createElement('li');
node.setAttribute('role', 'doc-chapter section');
flatTreeSetup(node);
@@ -370,7 +370,7 @@ describe('aria.getRole', function() {
);
});
- it('respect the `dpub` option', function() {
+ it('respect the `dpub` option', function () {
var node = document.createElement('li');
node.setAttribute('role', 'doc-chapter section');
flatTreeSetup(node);
@@ -381,8 +381,8 @@ describe('aria.getRole', function() {
});
});
- describe('noPresentational is honored', function() {
- it('handles no inheritance role = presentation', function() {
+ describe('noPresentational is honored', function () {
+ it('handles no inheritance role = presentation', function () {
fixture.innerHTML =
'
';
flatTreeSetup(fixture);
@@ -398,14 +398,14 @@ describe('aria.getRole', function() {
assert.isNull(aria.getRole(node, { noPresentational: true }));
});
- it('handles implicit role', function() {
+ it('handles implicit role', function () {
var node = document.createElement('img');
node.setAttribute('alt', '');
flatTreeSetup(node);
assert.isNull(aria.getRole(node, { noPresentational: true }));
});
- it('handles role = none', function() {
+ it('handles role = none', function () {
var node = document.createElement('div');
node.setAttribute('role', 'none');
flatTreeSetup(node);
diff --git a/test/commons/dom/is-hidden-for-everyone.js b/test/commons/dom/is-hidden-for-everyone.js
new file mode 100644
index 0000000000..655c08e1fa
--- /dev/null
+++ b/test/commons/dom/is-hidden-for-everyone.js
@@ -0,0 +1,299 @@
+describe('dom.isHiddenForEveryone', function () {
+ 'use strict';
+
+ var fixture = document.getElementById('fixture');
+ var shadowSupported = axe.testUtils.shadowSupport.v1;
+ var isHiddenForEveryone = axe.commons.dom.isHiddenForEveryone;
+ var queryFixture = axe.testUtils.queryFixture;
+
+ function createContentSlotted(mainProps, targetProps) {
+ var group = document.createElement('div');
+ group.innerHTML =
+ '';
+ return group;
+ }
+
+ function makeShadowTree(node, mainProps, targetProps) {
+ var root = node.attachShadow({ mode: 'open' });
+ var node = createContentSlotted(mainProps, targetProps);
+ root.appendChild(node);
+ }
+
+ it('should return false on static-positioned, visible element', function () {
+ var vNode = queryFixture('
I am visible
');
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true on static-positioned, hidden element', function () {
+ var vNode = queryFixture(
+ '
I am not visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return false on absolutely positioned elements that are on-screen', function () {
+ var vNode = queryFixture(
+ '
I am visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false for off-screen and aria-hidden element', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on fixed position elements that are on-screen', function () {
+ var vNode = queryFixture(
+ '
I am visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false for off-screen absolutely positioned element', function () {
+ var vNode = queryFixture(
+ '
I am visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false for off-screen fixed positioned element', function () {
+ var vNode = queryFixture(
+ '
I am visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true on detached elements', function () {
+ var el = document.createElement('div');
+ el.innerHTML = 'I am not visible because I am detached!';
+ axe.testUtils.flatTreeSetup(el);
+ var actual = isHiddenForEveryone(el);
+ assert.isTrue(actual);
+ });
+
+ it('should return false on body', function () {
+ axe.testUtils.flatTreeSetup(document.body);
+ var actual = isHiddenForEveryone(document.body);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on html', function () {
+ axe.testUtils.flatTreeSetup(document.documentElement);
+ var actual = isHiddenForEveryone(document.documentElement);
+ assert.isFalse(actual);
+ });
+
+ it('should return false if static-position but top/left is set', function () {
+ var vNode = queryFixture(
+ '
I am visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false, and not be affected by `aria-hidden`', function () {
+ var vNode = queryFixture(
+ '
I am visible with css (although hidden to screen readers)
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true for STYLE node', function () {
+ var vNode = queryFixture(
+ ""
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return true for SCRIPT node', function () {
+ var vNode = queryFixture(
+ ""
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return true for if parent of element set to `display:none`', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am not visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return false for if parent of element set to `display:block`', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ // `visibility` test
+ it('should return true for element that has `visibility:hidden`', function () {
+ var vNode = queryFixture(
+ '
I am not visible
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return false and compute how `visibility` of self and parent is configured', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false and compute how `visibility` of self and parent is configured', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true and as parent is set to `visibility:hidden`', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am not visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ // mixing display and visibility
+ it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am not visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return false and compute using both `display` and `visibility` set on element and parent(s)', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am not visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ it('should return true and compute using both `display` and `visibility` set on element and parent(s)', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
' +
+ '
I am not visible
' +
+ '
' +
+ '
'
+ );
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ });
+
+ (shadowSupported ? it : it.skip)(
+ 'should return true if `display:none` inside shadowDOM',
+ function () {
+ fixture.innerHTML = '';
+ makeShadowTree(fixture.firstChild, 'display:none;', '');
+ var tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ var vNode = axe.utils.querySelectorAll(tree, 'p')[0];
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ }
+ );
+
+ (shadowSupported ? it : xit)(
+ 'should return true as parent shadowDOM host is set to `visibility:hidden`',
+ function () {
+ fixture.innerHTML = '';
+ makeShadowTree(fixture.firstChild, 'visibility:hidden', '');
+ var tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ var vNode = axe.utils.querySelectorAll(tree, 'p')[0];
+ var actual = isHiddenForEveryone(vNode);
+ assert.isTrue(actual);
+ }
+ );
+
+ (shadowSupported ? it : xit)(
+ 'should return false as parent shadowDOM host set to `visibility:hidden` is overriden',
+ function () {
+ fixture.innerHTML = '';
+ makeShadowTree(
+ fixture.firstChild,
+ 'visibility:hidden',
+ 'visibility:visible'
+ );
+ var tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ var vNode = axe.utils.querySelectorAll(tree, 'p')[0];
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ }
+ );
+
+ describe('SerialVirtualNode', function () {
+ it('should return false on detached virtual nodes', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'div'
+ });
+ var actual = isHiddenForEveryone(vNode);
+ assert.isFalse(actual);
+ });
+ });
+});
diff --git a/test/commons/dom/is-offscreen.js b/test/commons/dom/is-offscreen.js
index 282f2e79de..e755eda1a2 100644
--- a/test/commons/dom/is-offscreen.js
+++ b/test/commons/dom/is-offscreen.js
@@ -1,14 +1,14 @@
-describe('dom.isOffscreen', function() {
+describe('dom.isOffscreen', function () {
'use strict';
var fixture = document.getElementById('fixture');
var shadowSupport = axe.testUtils.shadowSupport;
- afterEach(function() {
+ afterEach(function () {
fixture.innerHTML = '';
document.body.style.direction = 'ltr';
});
- it('should detect elements positioned outside the left edge', function() {
+ it('should detect elements positioned outside the left edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -16,7 +16,7 @@ describe('dom.isOffscreen', function() {
assert.isTrue(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned to but not beyond the left edge', function() {
+ it('should detect elements positioned to but not beyond the left edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -24,7 +24,7 @@ describe('dom.isOffscreen', function() {
assert.isTrue(axe.commons.dom.isOffscreen(el));
});
- it('should not detect elements at the left edge with a zero width', function() {
+ it('should not detect elements at the left edge with a zero width', function () {
fixture.innerHTML =
'';
var el = document.getElementById('target');
@@ -32,14 +32,14 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned outside the top edge', function() {
+ it('should detect elements positioned outside the top edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
assert.isTrue(axe.commons.dom.isOffscreen(el));
});
- it('should never detect elements positioned outside the bottom edge', function() {
+ it('should never detect elements positioned outside the bottom edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -47,7 +47,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned that bleed inside the left edge', function() {
+ it('should detect elements positioned that bleed inside the left edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -55,7 +55,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned outside the right edge', function() {
+ it('should detect elements positioned outside the right edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -63,7 +63,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned outside the top edge', function() {
+ it('should detect elements positioned outside the top edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -71,7 +71,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned outside the bottom edge', function() {
+ it('should detect elements positioned outside the bottom edge', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -79,7 +79,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements that are made off-screen by a parent', function() {
+ it('should detect elements that are made off-screen by a parent', function () {
fixture.innerHTML =
'
' +
'
Offscreen?
' +
@@ -90,7 +90,7 @@ describe('dom.isOffscreen', function() {
assert.isTrue(axe.commons.dom.isOffscreen(el));
});
- it('should NOT detect elements positioned outside the right edge on LTR documents', function() {
+ it('should NOT detect elements positioned outside the right edge on LTR documents', function () {
fixture.innerHTML =
'
Offscreen?
';
var el = document.getElementById('target');
@@ -98,7 +98,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should detect elements positioned outside the right edge on RTL documents', function() {
+ it('should detect elements positioned outside the right edge on RTL documents', function () {
document.body.style.direction = 'rtl';
fixture.innerHTML =
'
Offscreen?
';
@@ -107,7 +107,7 @@ describe('dom.isOffscreen', function() {
assert.isTrue(axe.commons.dom.isOffscreen(el));
});
- it('should NOT detect elements positioned outside the left edge on RTL documents', function() {
+ it('should NOT detect elements positioned outside the left edge on RTL documents', function () {
document.body.style.direction = 'rtl';
fixture.innerHTML =
'
Offscreen?
';
@@ -116,7 +116,7 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(el));
});
- it('should not detect elements positioned because of a scroll', function() {
+ it('should not detect elements positioned because of a scroll', function () {
fixture.innerHTML =
'
' +
'
goobye
' +
@@ -130,9 +130,13 @@ describe('dom.isOffscreen', function() {
assert.isFalse(axe.commons.dom.isOffscreen(viz));
});
+ it('should return undefined if actual ndoe is undefined', function () {
+ assert.isUndefined(axe.commons.dom.isOffscreen());
+ });
+
(shadowSupport.v1 ? it : xit)(
'should detect on screen shadow nodes',
- function() {
+ function () {
fixture.innerHTML = '';
var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' });
shadow.innerHTML = '
Offscreen?
';
@@ -144,7 +148,7 @@ describe('dom.isOffscreen', function() {
(shadowSupport.v1 ? it : xit)(
'should detect off screen shadow nodes',
- function() {
+ function () {
fixture.innerHTML = '';
var shadow = fixture.querySelector('div').attachShadow({ mode: 'open' });
shadow.innerHTML =
diff --git a/test/commons/dom/is-visible-for-screenreader.js b/test/commons/dom/is-visible-for-screenreader.js
new file mode 100644
index 0000000000..9c5e314412
--- /dev/null
+++ b/test/commons/dom/is-visible-for-screenreader.js
@@ -0,0 +1,234 @@
+describe('dom.isVisibleForScreenreader', function () {
+ 'use strict';
+
+ var fixture = document.querySelector('#fixture');
+ var queryFixture = axe.testUtils.queryFixture;
+ var shadowSupported = axe.testUtils.shadowSupport.v1;
+ var isVisibleForScreenreader = axe.commons.dom.isVisibleForScreenreader;
+
+ function createContentHidden() {
+ var group = document.createElement('div');
+ group.innerHTML =
+ 'Label';
+ return group;
+ }
+
+ function makeShadowTreeHidden(node) {
+ var root = node.attachShadow({ mode: 'open' });
+ var div = document.createElement('div');
+ div.className = 'parent';
+ root.appendChild(div);
+ div.appendChild(createContentHidden());
+ }
+
+ it('should return false on detached elements', function () {
+ var el = document.createElement('div');
+ el.innerHTML = 'I am not visible because I am detached!';
+ axe.testUtils.flatTreeSetup(el);
+ assert.isFalse(isVisibleForScreenreader(el));
+ });
+
+ it('should return true on body', function () {
+ axe.testUtils.flatTreeSetup(document.body);
+ var actual = isVisibleForScreenreader(document.body);
+ assert.isTrue(actual);
+ });
+
+ it('should return true on html', function () {
+ axe.testUtils.flatTreeSetup(document.documentElement);
+ var actual = isVisibleForScreenreader(document.documentElement);
+ assert.isTrue(actual);
+ });
+
+ it('should return true for visible element', function () {
+ var vNode = queryFixture('
Visible
');
+ assert.isTrue(isVisibleForScreenreader(vNode));
+ });
+
+ it('should return true for visible area element', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ assert.isTrue(isVisibleForScreenreader(vNode));
+ });
+
+ it('should return false if `aria-hidden` is set', function () {
+ var vNode = queryFixture(
+ '
Hidden from screen readers
'
+ );
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ });
+
+ it('should return false if `display: none` is set', function () {
+ var vNode = queryFixture(
+ '
Hidden from screen readers
'
+ );
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ });
+
+ it('should return false if `aria-hidden` is set on parent', function () {
+ var vNode = queryFixture(
+ '
Hidden from screen readers
'
+ );
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ });
+
+ it('should know how `visibility` works', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
Hi
' +
+ '
'
+ );
+ assert.isTrue(isVisibleForScreenreader(vNode));
+ });
+
+ it('returns false for `AREA` without closest `MAP` element', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleForScreenreader(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns false for `AREA` with closest `MAP` with no name attribute', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleForScreenreader(vNode);
+ assert.isFalse(actual);
+ });
+
+ (shadowSupported ? it : xit)(
+ 'returns false for `AREA` element that is inside shadowDOM',
+ function () {
+ fixture.innerHTML = '';
+ var container = fixture.querySelector('#container');
+ var shadow = container.attachShadow({ mode: 'open' });
+ shadow.innerHTML =
+ '';
+ axe.testUtils.flatTreeSetup(fixture);
+
+ var target = shadow.querySelector('#target');
+ var actual = isVisibleForScreenreader(target);
+ assert.isFalse(actual);
+ }
+ );
+
+ it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleForScreenreader(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleForScreenreader(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleForScreenreader(vNode);
+ assert.isTrue(actual);
+ });
+
+ (shadowSupported ? it : xit)(
+ 'not hidden: should work when the element is inside shadow DOM',
+ function () {
+ var tree, node;
+ // shadow DOM v1 - note: v0 is compatible with this code, so no need
+ // to specifically test this
+ fixture.innerHTML = '';
+ makeShadowTreeHidden(fixture.firstChild);
+ tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ node = axe.utils.querySelectorAll(tree, 'input')[0];
+ assert.isTrue(isVisibleForScreenreader(node));
+ }
+ );
+
+ (shadowSupported ? it : xit)(
+ 'hidden: should work when the element is inside shadow DOM',
+ function () {
+ var tree, node;
+ // shadow DOM v1 - note: v0 is compatible with this code, so no need
+ // to specifically test this
+ fixture.innerHTML = '';
+ makeShadowTreeHidden(fixture.firstChild);
+ tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ node = axe.utils.querySelectorAll(tree, 'input')[0];
+ assert.isFalse(isVisibleForScreenreader(node));
+ }
+ );
+
+ (shadowSupported ? it : xit)(
+ 'should work with hidden slotted elements',
+ function () {
+ function createContentSlotted() {
+ var group = document.createElement('div');
+ group.innerHTML =
+ '
Stuff
';
+ return group;
+ }
+ function makeShadowTree(node) {
+ var root = node.attachShadow({ mode: 'open' });
+ var div = document.createElement('div');
+ root.appendChild(div);
+ div.appendChild(createContentSlotted());
+ }
+ fixture.innerHTML = '
';
+ makeShadowTree(fixture.firstChild);
+ var tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ var vNode = axe.utils.querySelectorAll(tree, 'a')[0];
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ }
+ );
+
+ describe('SerialVirtualNode', function () {
+ it('should return false if `aria-hidden` is set', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'div',
+ attributes: {
+ 'aria-hidden': true
+ }
+ });
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ });
+
+ it('should return false if `aria-hidden` is set on parent', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'div'
+ });
+ var parentVNode = new axe.SerialVirtualNode({
+ nodeName: 'div',
+ attributes: {
+ 'aria-hidden': true
+ }
+ });
+ parentVNode.children = [vNode];
+ vNode.parent = parentVNode;
+ assert.isFalse(isVisibleForScreenreader(vNode));
+ });
+ });
+});
diff --git a/test/commons/dom/is-visible-on-screen.js b/test/commons/dom/is-visible-on-screen.js
new file mode 100644
index 0000000000..12b3f29ea3
--- /dev/null
+++ b/test/commons/dom/is-visible-on-screen.js
@@ -0,0 +1,434 @@
+describe('dom.isVisibleOnScreen', function () {
+ 'use strict';
+
+ var fixture = document.querySelector('#fixture');
+ var queryFixture = axe.testUtils.queryFixture;
+ var isIE11 = axe.testUtils.isIE11;
+ var shadowSupported = axe.testUtils.shadowSupport.v1;
+ var isVisibleOnScreen = axe.commons.dom.isVisibleOnScreen;
+
+ it('should return true on statically-positioned, visible elements', function () {
+ var vNode = queryFixture('
Hello!
');
+
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should return true on absolutely positioned elements that are on-screen', function () {
+ var vNode = queryFixture(
+ '
'
+ );
+
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should properly calculate offsets according the offsetParent', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
Hi
' +
+ '
'
+ );
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false if moved offscreen with left', function () {
+ var vNode = queryFixture(
+ '
Hi
'
+ );
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false if moved offscreen with top', function () {
+ var vNode = queryFixture(
+ '
Hi
'
+ );
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false on detached elements', function () {
+ var el = document.createElement('div');
+ el.innerHTML = 'I am not visible because I am detached!';
+ axe.testUtils.flatTreeSetup(el);
+ assert.isFalse(isVisibleOnScreen(el));
+ });
+
+ it('should return true on body', function () {
+ axe.testUtils.flatTreeSetup(document.body);
+ var actual = isVisibleOnScreen(document.body);
+ assert.isTrue(actual);
+ });
+
+ it('should return true on html', function () {
+ axe.testUtils.flatTreeSetup(document.documentElement);
+ var actual = isVisibleOnScreen(document.documentElement);
+ assert.isTrue(actual);
+ });
+
+ it('should return false on STYLE tag', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on NOSCRIPT tag', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on TEMPLATE tag', function () {
+ var vNode = queryFixture(
+ '
Name:
'
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return true if positioned statically but top/left is set', function () {
+ var vNode = queryFixture(
+ '
Hi
'
+ );
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should not be affected by `aria-hidden`', function () {
+ var vNode = queryFixture(
+ '
Hidden from screen readers
'
+ );
+
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should not calculate position on parents', function () {
+ var vNode = queryFixture(
+ '
' +
+ '
Hi
' +
+ '
'
+ );
+
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should know how `visibility` works', function () {
+ var vNode = queryFixture(
+ '
');
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false for display: none', function () {
+ var vNode = queryFixture(
+ '
Hello!
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false for opacity: 0', function () {
+ var vNode = queryFixture(
+ '
Hello!
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false for 0 height scrollable region', function () {
+ var vNode = queryFixture(
+ '
Hello!
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false for 0 width scrollable region', function () {
+ var vNode = queryFixture(
+ '
Hello!
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ it('returns false for `AREA` without closest `MAP` element', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns false for `AREA` with closest `MAP` with no name attribute', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ (shadowSupported ? it : xit)(
+ 'returns false for `AREA` element that is inside shadowDOM',
+ function () {
+ fixture.innerHTML = '';
+ var container = fixture.querySelector('#container');
+ var shadow = container.attachShadow({ mode: 'open' });
+ shadow.innerHTML =
+ '';
+ axe.testUtils.flatTreeSetup(fixture);
+
+ var target = shadow.querySelector('#target');
+ var actual = isVisibleOnScreen(target);
+ assert.isFalse(actual);
+ }
+ );
+
+ it('returns false for `AREA` with closest `MAP` with name but not referred by an `IMG` usemap attribute', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns false for `AREA` with `MAP` and used in `IMG` which is not visible', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('returns true for `AREA` with `MAP` and used in `IMG` which is visible', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ var actual = isVisibleOnScreen(vNode);
+ assert.isTrue(actual);
+ });
+
+ // IE11 either only supports clip paths defined by url() or not at all,
+ // MDN and caniuse.com give different results...
+ (isIE11 ? it.skip : it)(
+ 'should detect clip-path hidden text technique',
+ function () {
+ var vNode = queryFixture(
+ '
Hi
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ }
+ );
+
+ (isIE11 ? it.skip : it)(
+ 'should detect clip-path hidden text technique on parent',
+ function () {
+ var vNode = queryFixture(
+ '
' +
+ '
Hi
' +
+ '
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ }
+ );
+
+ (shadowSupported ? it : xit)(
+ 'should correctly handle visible slotted elements',
+ function () {
+ function createContentSlotted() {
+ var group = document.createElement('div');
+ group.innerHTML = '
Stuff
';
+ return group;
+ }
+ function makeShadowTree(node) {
+ var root = node.attachShadow({ mode: 'open' });
+ var div = document.createElement('div');
+ root.appendChild(div);
+ div.appendChild(createContentSlotted());
+ }
+ fixture.innerHTML = '
';
+ makeShadowTree(fixture.firstChild);
+ var tree = axe.utils.getFlattenedTree(fixture.firstChild);
+ var el = axe.utils.querySelectorAll(tree, 'a')[0];
+ assert.isFalse(isVisibleOnScreen(el.actualNode));
+ }
+ );
+ it('should return false if element is visually hidden using position absolute, overflow hidden, and a very small height', function () {
+ var vNode = queryFixture(
+ '
StickySticky
'
+ );
+
+ assert.isFalse(isVisibleOnScreen(vNode));
+ });
+
+ describe('SerialVirtualNode', function () {
+ it('should return true on statically-positioned, visible elements', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'div'
+ });
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+
+ it('should return false on STYLE tag', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'style'
+ });
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on NOSCRIPT tag', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'noscript'
+ });
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should return false on TEMPLATE tag', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'template'
+ });
+ var actual = isVisibleOnScreen(vNode);
+ assert.isFalse(actual);
+ });
+
+ it('should not be affected by `aria-hidden`', function () {
+ var vNode = new axe.SerialVirtualNode({
+ nodeName: 'div',
+ attributes: {
+ 'aria-hidden': true
+ }
+ });
+ assert.isTrue(isVisibleOnScreen(vNode));
+ });
+ });
+});
diff --git a/test/commons/dom/visibility-methods.js b/test/commons/dom/visibility-methods.js
new file mode 100644
index 0000000000..f5d2e12b08
--- /dev/null
+++ b/test/commons/dom/visibility-methods.js
@@ -0,0 +1,390 @@
+describe('dom.visibility-methods', function () {
+ var queryFixture = axe.testUtils.queryFixture;
+ var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
+ var nativelyHidden =
+ axe._thisWillBeDeletedDoNotUse.commons.dom.nativelyHidden;
+ var displayHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.displayHidden;
+ var visibilityHidden =
+ axe._thisWillBeDeletedDoNotUse.commons.dom.visibilityHidden;
+ var ariaHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.ariaHidden;
+ var opacityHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.opacityHidden;
+ var scrollHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.scrollHidden;
+ var overflowHidden =
+ axe._thisWillBeDeletedDoNotUse.commons.dom.overflowHidden;
+ var clipHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.clipHidden;
+ var areaHidden = axe._thisWillBeDeletedDoNotUse.commons.dom.areaHidden;
+
+ describe('nativelyHidden', function () {
+ var nativelyHiddenElements = ['style', 'script', 'noscript', 'template'];
+
+ it('should return true for hidden elements', function () {
+ nativelyHiddenElements.forEach(function (nodeName) {
+ var vNode = new axe.VirtualNode(document.createElement(nodeName));
+ assert.isTrue(nativelyHidden(vNode), (nodeName = ' is not hidden'));
+ });
+ });
+
+ it('should return false for visible elements', function () {
+ Object.keys(axe._audit.standards.htmlElms)
+ .filter(function (nodeName) {
+ return !nativelyHiddenElements.includes(nodeName);
+ })
+ .forEach(function (nodeName) {
+ var vNode = new axe.VirtualNode(document.createElement(nodeName));
+ assert.isFalse(nativelyHidden(vNode), nodeName + ' is not visible');
+ });
+ });
+ });
+
+ describe('displayHidden', function () {
+ it('should return true for element with "display:none`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isTrue(displayHidden(vNode));
+ });
+
+ it('should return false for element with "display:block`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isFalse(displayHidden(vNode));
+ });
+
+ it('should return false for element without display', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(displayHidden(vNode));
+ });
+
+ it('should return false for area element', function () {
+ var vNode = queryFixture(
+ '' +
+ ''
+ );
+ assert.isFalse(displayHidden(vNode));
+ });
+ });
+
+ describe('visibilityHidden', function () {
+ it('should return true for element with "visibility:hidden`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isTrue(visibilityHidden(vNode));
+ });
+
+ it('should return true for element with "visibility:collapse`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isTrue(visibilityHidden(vNode));
+ });
+
+ it('should return false for element with "visibility:visible`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isFalse(visibilityHidden(vNode));
+ });
+
+ it('should return false for element without visibility', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(visibilityHidden(vNode));
+ });
+
+ it('should return false for if passed "isAncestor:true`', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isFalse(visibilityHidden(vNode, { isAncestor: true }));
+ });
+ });
+
+ describe('ariaHidden', function () {
+ it('should return true for element with "aria-hidden=true`', function () {
+ var vNode = queryFixture('');
+ assert.isTrue(ariaHidden(vNode));
+ });
+
+ it('should return false for element with "aria-hidden=false`', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(ariaHidden(vNode));
+ });
+
+ it('should return false for element without aria-hidden', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(ariaHidden(vNode));
+ });
+ });
+
+ describe('opacityHidden', function () {
+ it('should return true for element with "opacity:0`', function () {
+ var vNode = queryFixture('');
+ assert.isTrue(opacityHidden(vNode));
+ });
+
+ it('should return false for element with "opacity:0.1`', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(opacityHidden(vNode));
+ });
+
+ it('should return false for element without opacity', function () {
+ var vNode = queryFixture('');
+ assert.isFalse(opacityHidden(vNode));
+ });
+ });
+
+ describe('scrollHidden', function () {
+ it('should return true for element with scroll and "width:0`', function () {
+ var vNode = queryFixture(
+ '
scroll hidden
'
+ );
+ assert.isTrue(scrollHidden(vNode));
+ });
+
+ it('should return true for element with scroll and "height:0`', function () {
+ var vNode = queryFixture(
+ '
scroll hidden
'
+ );
+ assert.isTrue(scrollHidden(vNode));
+ });
+
+ it('should return false for element with scroll and width > 0', function () {
+ var vNode = queryFixture(
+ '
scroll hidden
'
+ );
+ assert.isFalse(scrollHidden(vNode));
+ });
+
+ it('should return false for element with scroll and height > 0', function () {
+ var vNode = queryFixture(
+ '
scroll hidden
'
+ );
+ assert.isFalse(scrollHidden(vNode));
+ });
+
+ it('should return false for element without scroll', function () {
+ var vNode = queryFixture('
scroll hidden
');
+ assert.isFalse(scrollHidden(vNode));
+ });
+ });
+
+ describe('overflowHidden', function () {
+ it('should return true for element with "overflow:hidden" and "width:0`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isTrue(overflowHidden(vNode));
+ });
+
+ it('should return true for element with "overflow:hidden" and "height:0`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isTrue(overflowHidden(vNode));
+ });
+
+ it('should return true for element with "overflow:hidden" and "width:1`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isTrue(overflowHidden(vNode));
+ });
+
+ it('should return true for element with "overflow:hidden" and "height:1`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isTrue(overflowHidden(vNode));
+ });
+
+ it('should return true for element with "overflow:hidden" and width > 1', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return true for element with "overflow:hidden" and height > 1', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element with "position:fixed`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element with "position:relative`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element without position', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element with "overflow:visible`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element with "overflow:auto`', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+
+ it('should return false for element without overflow', function () {
+ var vNode = queryFixture(
+ '
overflow hidden
'
+ );
+ assert.isFalse(overflowHidden(vNode));
+ });
+ });
+
+ describe('clipHidden', function () {
+ it('should return true for element with clip-path and inset >= 50%', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return false for element with clip-path and inset < 50%', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isFalse(clipHidden(vNode));
+ });
+
+ it('should return true for element with clip-path and circle=0', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return false for element with clip-path and circle > 0', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isFalse(clipHidden(vNode));
+ });
+
+ it('should return true for element with clip and "position:absolute`', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return true for element with clip and "position:fixed"', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return true for element with clip using commas', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return true for poorly hidden clip rects', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isTrue(clipHidden(vNode));
+ });
+
+ it('should return false for element with clip and "position:relative"', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isFalse(clipHidden(vNode));
+ });
+
+ it('should return false for element with clip and without position', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isFalse(clipHidden(vNode));
+ });
+
+ it('should return false for element without clip or clip-path', function () {
+ var vNode = queryFixture('
Hello world
');
+ assert.isFalse(clipHidden(vNode));
+ });
+
+ it('should return false for element with visible clip', function () {
+ var vNode = queryFixture(
+ '
Hello world
'
+ );
+ assert.isFalse(clipHidden(vNode));
+ });
+ });
+
+ describe('areaHidden', function () {
+ it('should return true for element without map parent', function () {
+ var vNode = queryFixture('');
+ assert.isTrue(areaHidden(vNode));
+ });
+
+ it('should return true for map without a name', function () {
+ var vNode = queryFixture('');
+ assert.isTrue(areaHidden(vNode));
+ });
+
+ it('should return true if map is in shadowDOM', function () {
+ var vNode = shadowCheckSetup(
+ '',
+ ''
+ )[2];
+ assert.isTrue(areaHidden(vNode));
+ });
+
+ it('should return true if img does not use map', function () {
+ var vNode = queryFixture('');
+ assert.isTrue(areaHidden(vNode));
+ });
+
+ it('should return true if img uses map but is hidden', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isTrue(
+ areaHidden(vNode, function () {
+ return false;
+ })
+ );
+ });
+
+ it('should return false if img uses map and is visible', function () {
+ var vNode = queryFixture(
+ ''
+ );
+ assert.isFalse(
+ areaHidden(vNode, function () {
+ return true;
+ })
+ );
+ });
+ });
+});
diff --git a/test/rule-matches/is-visible-matches.js b/test/rule-matches/is-visible-matches.js
index a08410496c..7dc3a87822 100644
--- a/test/rule-matches/is-visible-matches.js
+++ b/test/rule-matches/is-visible-matches.js
@@ -1,31 +1,32 @@
-describe('is-visible-matches', function() {
+describe('is-visible-matches', function () {
'use strict';
var rule;
var fixture = document.getElementById('fixture');
+ var fixtureSetup = axe.testUtils.fixtureSetup;
- beforeEach(function() {
+ beforeEach(function () {
fixture.innerHTML = '';
rule = axe.utils.getRule('avoid-inline-spacing');
});
- it('returns true for visible elements', function() {
- fixture.innerHTML = '
Hello world
';
+ it('returns true for visible elements', function () {
+ fixtureSetup('
Hello world
');
assert.isTrue(rule.matches(fixture.firstChild));
});
- it('returns false for elements with hidden', function() {
- fixture.innerHTML = '
Hello world
'
+ it('returns false for elements with hidden', function () {
+ fixtureSetup('
Hello world
');
assert.isFalse(rule.matches(fixture.firstChild));
});
- it('returns true for visible elements with aria-hidden="true"', function() {
- fixture.innerHTML = '
Hello world
'
+ it('returns true for visible elements with aria-hidden="true"', function () {
+ fixtureSetup('
Hello world
');
assert.isTrue(rule.matches(fixture.firstChild));
});
- it('returns false for opacity:0 elements with accessible text', function() {
- fixture.innerHTML = '
Hello world
';
+ it('returns false for opacity:0 elements with accessible text', function () {
+ fixtureSetup('