UNPKG

axe-core

Version:

Accessibility engine for automated Web UI testing

212 lines (191 loc) • 6.04 kB
/*eslint indent: 0*/ var testUtils = {}; /** * Create a check context for mocking/resetting data and relatedNodes in tests * * @return Object */ testUtils.MockCheckContext = function () { 'use strict'; return { _relatedNodes: [], _data: null, // When using this.async() in a check, assign a function to _onAsync // to catch the response. _onAsync: null, async: function () { var self = this; return function (result) { // throws if _onAsync isn't set self._onAsync(result, self); } }, data: function (d) { this._data = d; }, relatedNodes: function (nodes) { this._relatedNodes = Array.isArray(nodes) ? nodes : [nodes]; }, reset: function () { this._data = null; this._relatedNodes = []; this._onAsync = null; } }; }; /** * Provide an API for determining Shadow DOM v0 and v1 support in tests. * PhantomJS doesn't have Shadow DOM support, while some browsers do. * * @param HTMLDocumentElement The document of the current context * @return Object */ testUtils.shadowSupport = (function(document) { 'use strict'; var v0 = document.body && typeof document.body.createShadowRoot === 'function', v1 = document.body && typeof document.body.attachShadow === 'function'; return { v0: (v0 === true), v1: (v1 === true), undefined: ( document.body && typeof document.body.attachShadow === 'undefined' && typeof document.body.createShadowRoot === 'undefined' ) }; })(document); /** * Method for injecting content into a fixture and caching * the flattened DOM tree (light and Shadow DOM together) * * @param Node|String Stuff to go into the fixture (html or DOM node) * @return HTMLElement */ testUtils.fixtureSetup = function (content) { 'use strict'; var fixture = document.querySelector('#fixture'); if (typeof content !== 'undefined') { fixture.innerHTML = ''; } if (typeof content === 'string') { fixture.innerHTML = content; } else if (content instanceof Node) { fixture.appendChild(content); } else if (Array.isArray(content)) { content.forEach(function (node) { fixture.appendChild(node); }); } axe._tree = axe.utils.getFlattenedTree(fixture); axe._selectorData = axe.utils.getSelectorData(axe._tree); return fixture; }; /** * Create check arguments * * @param Node|String Stuff to go into the fixture (html or node) * @param Object Options argument for the check (optional, default: {}) * @param String Target for the check, CSS selector (default: '#target') * @return Array */ testUtils.checkSetup = function (content, options, target) { 'use strict'; // Normalize the params if (typeof options !== 'object') { target = options; options = {}; } // Normalize target, allow it to be the inserted node or '#target' target = target || (content instanceof Node ? content : '#target'); testUtils.fixtureSetup(content); var node; if (typeof target === 'string') { node = axe.utils.querySelectorAll(axe._tree[0], target)[0]; } else if (target instanceof Node) { node = axe.utils.getNodeFromTree(axe._tree[0], target); } else { node = target; } return [node.actualNode, options, node]; }; /** * Create check arguments with Shadow DOM. Target can be inside or outside of Shadow DOM, queried by * adding `id="target"` to a fragment. Or specify a custom selector as the `targetSelector` argument. * * @param Node|String Stuff to go into the fixture (html string or DOM Node) * @param Node|String Stuff to go into the shadow boundary (html or node) * @param Object Options argument for the check (optional, default: {}) * @param String Target selector for the check, can be inside or outside of Shadow DOM (optional, default: '#target') * @return Array */ testUtils.shadowCheckSetup = function (content, shadowContent, options, targetSelector) { 'use strict'; // Normalize target, allow it to be the provided string or use '#target' to query composed tree if (typeof targetSelector !== 'string') { targetSelector = '#target'; } // Normalize the object params if (typeof options !== 'object') { options = {}; } var fixture = testUtils.fixtureSetup(content); var targetCandidate = fixture.querySelector(targetSelector); var container = targetCandidate; if (!targetCandidate) { // check if content specifies a shadow container container = fixture.querySelector('#shadow'); if (!container) { container = fixture.firstChild; } } // attach a shadowRoot with the content provided var shadowRoot = container.attachShadow({ mode: 'open' }); if (typeof shadowContent === 'string') { shadowRoot.innerHTML = shadowContent; } else if (content instanceof Node) { shadowRoot.appendChild(shadowContent); } if (!targetCandidate) { targetCandidate = shadowRoot.querySelector(targetSelector); } if (!targetSelector && !targetCandidate) { throw 'shadowCheckSetup requires at least one fragment to have #target, or a provided targetSelector'; } // query the composed tree AFTER shadowDOM has been attached axe._tree = axe.utils.getFlattenedTree(fixture); var node = axe.utils.getNodeFromTree(axe._tree[0], targetCandidate); return [node.actualNode, options, node]; }; /** * Wait for all nested frames to be loaded * * @param Object Window to wait for (optional) * @param function Callback, called once resolved */ testUtils.awaitNestedLoad = function awaitNestedLoad(win, cb) { 'use strict'; if (typeof win === 'function') { cb = win; win = window; } var document = win.document; var q = axe.utils.queue(); // Wait for page load q.defer(function (resolve) { if (document.readyState === 'complete') { resolve(); } else { win.addEventListener('load', resolve); } }); // Wait for all frames to be loaded Array.from(document.querySelectorAll('iframe')).forEach(function (frame) { q.defer(function (resolve) { return awaitNestedLoad(frame.contentWindow, resolve); }); }); // Complete (don't pass the args on to the callback) q.then(function () { cb(); }); }; axe.testUtils = testUtils;