From 321f4f14d972fc257eb085528290b455198f407c Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 5 Jun 2018 14:55:34 +0100 Subject: [PATCH 01/43] feat(rule): css-orientation-lock raw structure --- doc/rule-descriptions.md | 1 + lib/checks/mobile/css-orientation-lock.js | 1 + lib/checks/mobile/css-orientation-lock.json | 11 +++++ lib/rules/css-orientation-lock-matches.js | 1 + lib/rules/css-orientation-lock.json | 18 ++++++++ test/checks/mobile/css-orientation-lock.js | 3 ++ .../css-orientation-lock-matches.js | 3 ++ test/sandbox-css-orientation-lock.css | 4 ++ test/sandbox-css-orientation-lock.html | 44 +++++++++++++++++++ 9 files changed, 86 insertions(+) create mode 100644 lib/checks/mobile/css-orientation-lock.js create mode 100644 lib/checks/mobile/css-orientation-lock.json create mode 100644 lib/rules/css-orientation-lock-matches.js create mode 100644 lib/rules/css-orientation-lock.json create mode 100644 test/checks/mobile/css-orientation-lock.js create mode 100644 test/rule-matches/css-orientation-lock-matches.js create mode 100644 test/sandbox-css-orientation-lock.css create mode 100644 test/sandbox-css-orientation-lock.html diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 21622cb687..cfc63a479b 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -17,6 +17,7 @@ | bypass | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o | true | | checkboxgroup | Ensures related <input type="checkbox"> elements have a group and that the group designation is consistent | Critical | cat.forms, best-practice | true | | color-contrast | Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143 | true | +| css-orientation-lock | Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape) | Serious | cat.structure, wcag262, wcag21aa | true | | definition-list | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131 | true | | dlitem | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131 | true | | document-title | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242 | true | diff --git a/lib/checks/mobile/css-orientation-lock.js b/lib/checks/mobile/css-orientation-lock.js new file mode 100644 index 0000000000..92b4e929ee --- /dev/null +++ b/lib/checks/mobile/css-orientation-lock.js @@ -0,0 +1 @@ +return false; \ No newline at end of file diff --git a/lib/checks/mobile/css-orientation-lock.json b/lib/checks/mobile/css-orientation-lock.json new file mode 100644 index 0000000000..652d19b936 --- /dev/null +++ b/lib/checks/mobile/css-orientation-lock.json @@ -0,0 +1,11 @@ +{ + "id": "css-orientation-lock", + "evaluate": "css-orientation-lock.js", + "metadata": { + "impact": "serious", + "messages": { + "pass": "Display is operable, and orientation lock does not exist", + "fail": "CSS Orientation lock is applied, and makes display inoperable" + } + } +} diff --git a/lib/rules/css-orientation-lock-matches.js b/lib/rules/css-orientation-lock-matches.js new file mode 100644 index 0000000000..92b4e929ee --- /dev/null +++ b/lib/rules/css-orientation-lock-matches.js @@ -0,0 +1 @@ +return false; \ No newline at end of file diff --git a/lib/rules/css-orientation-lock.json b/lib/rules/css-orientation-lock.json new file mode 100644 index 0000000000..bf952d75ec --- /dev/null +++ b/lib/rules/css-orientation-lock.json @@ -0,0 +1,18 @@ +{ + "id": "css-orientation-lock", + "matches": "css-orientation-lock-matches.js", + "tags": [ + "cat.structure", + "wcag262", + "wcag21aa" + ], + "metadata": { + "description": "Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape)", + "help": "Content is operable in all both landscape and portriat orientations" + }, + "all": [ + "css-orientation-lock" + ], + "any": [], + "none": [] +} \ No newline at end of file diff --git a/test/checks/mobile/css-orientation-lock.js b/test/checks/mobile/css-orientation-lock.js new file mode 100644 index 0000000000..0c3b256586 --- /dev/null +++ b/test/checks/mobile/css-orientation-lock.js @@ -0,0 +1,3 @@ +describe('css-orientation-lock', function () { + 'use strict'; +}); \ No newline at end of file diff --git a/test/rule-matches/css-orientation-lock-matches.js b/test/rule-matches/css-orientation-lock-matches.js new file mode 100644 index 0000000000..bf8b7971e3 --- /dev/null +++ b/test/rule-matches/css-orientation-lock-matches.js @@ -0,0 +1,3 @@ +describe('css-orientation-lock-matches', function () { + 'use strict'; +}); \ No newline at end of file diff --git a/test/sandbox-css-orientation-lock.css b/test/sandbox-css-orientation-lock.css new file mode 100644 index 0000000000..b3ed93e193 --- /dev/null +++ b/test/sandbox-css-orientation-lock.css @@ -0,0 +1,4 @@ +html, +body { + +} \ No newline at end of file diff --git a/test/sandbox-css-orientation-lock.html b/test/sandbox-css-orientation-lock.html new file mode 100644 index 0000000000..9cf703dde8 --- /dev/null +++ b/test/sandbox-css-orientation-lock.html @@ -0,0 +1,44 @@ + + + + + + Sandbox::Css-Orienation-Lock + + + + + + + + + + + + + +
foo
+ + + + + \ No newline at end of file From 33205ece191bc527c943223ec51ae59e2d66d82d Mon Sep 17 00:00:00 2001 From: Jey Date: Wed, 6 Jun 2018 09:38:08 +0100 Subject: [PATCH 02/43] feat: adding preload config object. --- lib/core/constants.js | 4 +++- lib/core/public/configure.js | 16 +++++++++++++++- test/sandbox-css-orientation-lock.html | 26 +++++++++++++++++++++----- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index 8f6169725e..f73d5967a6 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -27,7 +27,9 @@ results: [], resultGroups: [], resultGroupMap: {}, - impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']) + impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']), + preload: Object.freeze(['cssom', 'aom']), + preloadDefaultTimeout: 30000 }; definitions.forEach(function (definition) { diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index 0a0d12111f..95e4fb90f9 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -1,10 +1,11 @@ /* global reporters */ function configureChecksRulesAndBranding(spec) { - /*eslint max-statements: ["error",20]*/ + /*eslint max-statements: ["error",20]*/ 'use strict'; var audit; audit = axe._audit; + if (!audit) { throw new Error('No audit configured'); } @@ -44,6 +45,19 @@ function configureChecksRulesAndBranding(spec) { if (spec.tagExclude) { audit.tagExclude = spec.tagExclude; } + + // handle preload + if (typeof spec.preload !== 'undefined') { + // preload assets requested + let requestedAssets = null; + let requestedAssetsTimeout = axe.contants.preloadDefaultTimeout; + if (spec.preload.assets !== 'undefined' && Array.isArray(spec.preload.assets) && spec.preload.assets.length > 0) { + requestedAssets = spec.preload.assets + } else { + throw new Error('preload configuration property is wrongly set-up.'); + } + } + } axe.configure = configureChecksRulesAndBranding; diff --git a/test/sandbox-css-orientation-lock.html b/test/sandbox-css-orientation-lock.html index 9cf703dde8..1c97763d25 100644 --- a/test/sandbox-css-orientation-lock.html +++ b/test/sandbox-css-orientation-lock.html @@ -1,5 +1,6 @@ + @@ -9,7 +10,7 @@ - + - - + + + + + + -
foo
- - + + - + \ No newline at end of file From b78ac30cedce15c58f0d34a273d2008bcc869915 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 19 Jun 2018 09:45:46 +0100 Subject: [PATCH 05/43] style: update eslint to allow spread operator for object spreading. --- .eslintrc | 170 +++++++++++++++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3255bd7ca0..02b130e1c4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,79 +1,93 @@ { - "env": { - "node": true - }, - "globals": { - "axe": true - }, - "rules": { - "no-bitwise": 2, - "camelcase": 2, - "curly": 2, - "eqeqeq": 2, - "guard-for-in": 2, - "wrap-iife": [ - 2, - "any" - ], - "indent": ["error", "tab", {"SwitchCase": 0}], - "no-use-before-define": [ - 2, - { - "functions": false - } - ], - "new-cap": 2, - "no-caller": 2, - "no-empty": 2, - "no-new": 2, - "no-plusplus": 0, - "quotes": [ - 2, - "single" - ], - "no-undef": 2, - "no-unused-vars": 2, - "strict": 0, - "max-params": [ - 2, - 5 - ], - "max-depth": [ - 2, - 5 - ], - "max-statements": [ - 2, - 15 - ], - "complexity": [ - 1, - 12 - ], - "max-len": [ - 2, - { - "code": 120, - "ignoreComments": true - } - ], - "semi": 0, - "no-cond-assign": 0, - "no-debugger": 0, - "no-eq-null": 0, - "no-eval": 0, - "no-unused-expressions": 0, - "block-scoped-var": 0, - "no-iterator": 0, - "linebreak-style": 0, - "comma-style": ["error", "last"], - "no-loop-func": 0, - "no-multi-str": 0, - "no-proto": 0, - "no-script-url": 0, - "no-shadow": 0, - "dot-notation": 2, - "no-new-func": 0, - "no-new-wrappers": 0 - } -} + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + }, + "env": { + "node": true + }, + "globals": { + "axe": true + }, + "rules": { + "no-bitwise": 2, + "camelcase": 2, + "curly": 2, + "eqeqeq": 2, + "guard-for-in": 2, + "wrap-iife": [ + 2, + "any" + ], + "indent": [ + "error", + "tab", + { + "SwitchCase": 0 + } + ], + "no-use-before-define": [ + 2, + { + "functions": false + } + ], + "new-cap": 2, + "no-caller": 2, + "no-empty": 2, + "no-new": 2, + "no-plusplus": 0, + "quotes": [ + 2, + "single" + ], + "no-undef": 2, + "no-unused-vars": 2, + "strict": 0, + "max-params": [ + 2, + 5 + ], + "max-depth": [ + 2, + 5 + ], + "max-statements": [ + 2, + 15 + ], + "complexity": [ + 1, + 12 + ], + "max-len": [ + 2, + { + "code": 120, + "ignoreComments": true + } + ], + "semi": 0, + "no-cond-assign": 0, + "no-debugger": 0, + "no-eq-null": 0, + "no-eval": 0, + "no-unused-expressions": 0, + "block-scoped-var": 0, + "no-iterator": 0, + "linebreak-style": 0, + "comma-style": [ + "error", + "last" + ], + "no-loop-func": 0, + "no-multi-str": 0, + "no-proto": 0, + "no-script-url": 0, + "no-shadow": 0, + "dot-notation": 2, + "no-new-func": 0, + "no-new-wrappers": 0 + } +} \ No newline at end of file From 4e590a556243cc47b5441ec2645359823bb717db Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 19 Jun 2018 09:58:30 +0100 Subject: [PATCH 06/43] feat: cssom preloading async --- build/templates.js | 2 +- lib/core/base/audit.js | 170 ++++++++++++++++++------- lib/core/base/check.js | 14 +- lib/core/base/rule.js | 6 + lib/core/constants.js | 2 +- lib/core/public/run.js | 14 -- lib/core/utils/preload-cssom.js | 101 +++++++++++++++ lib/core/utils/preload.js | 62 +++++++++ lib/core/utils/xhr-promise.js | 67 ++++++++++ lib/rules/css-orientation-lock.json | 11 +- test/sandbox-css-orientation-lock.html | 20 ++- 11 files changed, 391 insertions(+), 78 deletions(-) create mode 100644 lib/core/utils/preload-cssom.js create mode 100644 lib/core/utils/preload.js create mode 100644 lib/core/utils/xhr-promise.js diff --git a/build/templates.js b/build/templates.js index dffc44ada6..24f2d2bbda 100644 --- a/build/templates.js +++ b/build/templates.js @@ -1,5 +1,5 @@ module.exports = { - evaluate: 'function (node, options, virtualNode) {\n<%=source%>\n}', + evaluate: 'function ({ node, options, virtualNode, preloadAssets }) {\n<%=source%>\n}', after: 'function (results, options) {\n<%=source%>\n}', gather: 'function (context) {\n<%=source%>\n}', matches: 'function (node, virtualNode) {\n<%=source%>\n}', diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index f3a5a519aa..335e90f214 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -74,7 +74,6 @@ Audit.prototype._init = function () { /** * Adds a new command to the audit */ - Audit.prototype.registerCommand = function (command) { 'use strict'; this.commands[command.id] = command.callback; @@ -114,11 +113,12 @@ Audit.prototype.addCheck = function (spec) { this.data.checks[spec.id] = metadata; // Transform messages into functions: if (typeof metadata.messages === 'object') { - Object.keys(metadata.messages) - .filter( prop => - metadata.messages.hasOwnProperty(prop) && - typeof metadata.messages[prop] === 'string' - ).forEach( prop => { + Object + .keys(metadata.messages) + .filter((prop) => { + return (metadata.messages.hasOwnProperty(prop) && typeof metadata.messages[prop] === 'string') + }) + .forEach((prop) => { if (metadata.messages[prop].indexOf('function') === 0) { metadata.messages[prop] = (new Function('return ' + metadata.messages[prop] + ';'))(); } @@ -133,6 +133,69 @@ Audit.prototype.addCheck = function (spec) { } }; +const noop = () => { }; + +const measurePerformance = (id) => { + const marker = { + start: `mark_rule_start_${id}`, + end: `mark_rule_end_${id}` + } + axe.utils.performanceTimer.mark(marker.start); + return () => { + axe.utils.performanceTimer.mark(marker.end); + axe.utils.performanceTimer.measure(`rule_${id}`, marker.start, marker.end); + }; +}; + +const runRule = ({ + rule, + context, + options, + performanceMeasurer, + resolve, + reject +}) => { + rule.run( + context, + options, + (out) => { + performanceMeasurer(); + resolve(out); + }, + (err) => { + if (!options.debug) { + const errResult = Object.assign(new RuleResult(rule), { + result: axe.constants.CANTTELL, + description: 'An error occured while running this rule', + message: err.message, + stack: err.stack, + error: err + }); + resolve(errResult); + } else { + reject(err); + } + } + ); +}; + +const getRulesQ = ({ rules, conditionFn, extend }) => { + return rules + .reduce((out, r) => { + if (conditionFn(r)) { + out.defer((resolve, reject) => { + runRule({ + ...r, + ...extend, + resolve, + reject + }) + }); + } + return out; + }, axe.utils.queue()) +}; + /** * Runs the Audit; which in turn should call `run` on each rule. * @async @@ -142,46 +205,63 @@ Audit.prototype.addCheck = function (spec) { */ Audit.prototype.run = function (context, options, resolve, reject) { 'use strict'; + this.normalizeOptions(options); axe._selectCache = []; - var q = axe.utils.queue(); - this.rules.forEach(function (rule) { - if (axe.utils.ruleShouldRun(rule, context, options)) { - if (options.performanceTimer) { - var markEnd = 'mark_rule_end_' + rule.id; - var markStart = 'mark_rule_start_' + rule.id; - axe.utils.performanceTimer.mark(markStart); + + const toRunRules = this.rules + .reduce((out, rule) => { + if (axe.utils.ruleShouldRun(rule, context, options)) { + out.push({ + rule, + context, + options, + performanceMeasurer: noop, + // performanceMeasurer: options.performanceTimer + // ? measurePerformance(rule.id) : noop, + preload: rule.preload.length > 0 + }); } - q.defer(function (res, rej) { - rule.run(context, options, function(out) { - if (options.performanceTimer) { - axe.utils.performanceTimer.mark(markEnd); - axe.utils.performanceTimer.measure('rule_'+rule.id, markStart, markEnd); - } - res(out); - }, function (err) { - if (!options.debug) { - var errResult = Object.assign(new RuleResult(rule), { - result: axe.constants.CANTTELL, - description: 'An error occured while running this rule', - message: err.message, - stack: err.stack, - error: err - }); - res(errResult); - - } else { - rej(err); + return out; + }, []); + + Promise + .all([ + // run rules which do not need immediate run + getRulesQ({ + rules: toRunRules, + conditionFn: (r) => !r.preload + }), + // start preloading + axe.utils.preload(options) + ]) + .then((results) => { + const axeResults = results[0]; + const preloadAssets = results[1]; + // run rules which need preloaded assets + return new Promise((res, rej) => { + + getRulesQ({ + rules: toRunRules, + conditionFn: (r) => r.preload, + extend: { + preloadAssets: preloadAssets } - }); + }).then((results) => { + res([...axeResults, ...results]) + }).catch(rej); }); - } - }); - q.then(function (results) { - axe._selectCache = undefined; // remove the cache - resolve(results.filter(function (result) { return !!result; })); - }).catch(reject); + }) + .then((results) => { + axe._selectCache = undefined; // remove the cache + resolve( + results.filter((result) => { + return !!result; + }) + ); + }) + .catch(reject); }; /** @@ -248,11 +328,11 @@ Audit.prototype.normalizeOptions = function (options) { only.type = 'rule'; only.values.forEach(function (ruleId) { if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); + throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); } }); - // Validate 'tags' (e.g. anything not 'rule') + // Validate 'tags' (e.g. anything not 'rule') } else if (['tag', 'tags', undefined].includes(only.type)) { only.type = 'tag'; const unmatchedTags = audit.rules.reduce((unmatchedTags, rule) => { @@ -274,7 +354,7 @@ Audit.prototype.normalizeOptions = function (options) { Object.keys(options.rules) .forEach(function (ruleId) { if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.rules'); + throw new Error('unknown rule `' + ruleId + '` in options.rules'); } }); } @@ -307,9 +387,9 @@ Audit.prototype.setBranding = function (branding) { /** * For all the rules, create the helpUrl and add it to the data for that rule */ -function getHelpUrl ({brand, application}, ruleId, version) { +function getHelpUrl({ brand, application }, ruleId, version) { return axe.constants.helpUrlBase + brand + - '/' + ( version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + + '/' + (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + '/' + ruleId + '?application=' + application; } diff --git a/lib/core/base/check.js b/lib/core/base/check.js index c9eafac0de..a353c8a416 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -70,7 +70,11 @@ Check.prototype.run = function (node, options, resolve, reject) { var result; try { - result = this.evaluate.call(checkHelper, node.actualNode, checkOptions, node); + result = this.evaluate.call(checkHelper, { + node: node.actualNode, + options: checkOptions, + virtualNode: node + }); } catch (e) { reject(e); return; @@ -96,10 +100,10 @@ Check.prototype.run = function (node, options, resolve, reject) { Check.prototype.configure = function (spec) { ['options', 'enabled'] - .filter( prop => spec.hasOwnProperty(prop) ) - .forEach( prop => this[prop] = spec[prop] ); + .filter(prop => spec.hasOwnProperty(prop)) + .forEach(prop => this[prop] = spec[prop]); ['evaluate', 'after'] - .filter( prop => spec.hasOwnProperty(prop) ) - .forEach( prop => this[prop] = createExecutionContext(spec[prop]) ); + .filter(prop => spec.hasOwnProperty(prop)) + .forEach(prop => this[prop] = createExecutionContext(spec[prop])); }; diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index 2fb4d92fa9..4cab3677ea 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -61,6 +61,12 @@ function Rule(spec, parentAudit) { */ this.tags = spec.tags || []; + /** + * Preload assets to be fetched for the rule + * @type {Array} + */ + this.preload = spec.preload || []; + if (spec.matches) { /** * Optional function to test if rule should be run against a node, overrides Rule#matches diff --git a/lib/core/constants.js b/lib/core/constants.js index f73d5967a6..e00c99a5ba 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -29,7 +29,7 @@ resultGroupMap: {}, impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']), preload: Object.freeze(['cssom', 'aom']), - preloadDefaultTimeout: 30000 + preloadTimeout: 30000 }; definitions.forEach(function (definition) { diff --git a/lib/core/public/run.js b/lib/core/public/run.js index 335a4200a1..6e5ab7a1fe 100644 --- a/lib/core/public/run.js +++ b/lib/core/public/run.js @@ -109,20 +109,6 @@ axe.run = function (context, options, callback) { axe.utils.performanceTimer.start(); } - // bolt on fetching preload assets if passed via options - if (typeof options.preload !== 'undefined') { - if (options.preload.assets !== 'undefined' && - Array.isArray(options.preload.assets) && - options.preload.assets.length > 0) { - // let requestedAssets = options.preload.assets; - // let requestedAssetsTimeout = axe.contants.preloadDefaultTimeout; - // trigger a util to start fetching... - } else { - throw new Error('preload configuration property is wrongly set-up.'); - //TODO:JEY: document preload object in run config/ options - } - } - let p; let reject = noop; let resolve = noop; diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js new file mode 100644 index 0000000000..0bee9f77e9 --- /dev/null +++ b/lib/core/utils/preload-cssom.js @@ -0,0 +1,101 @@ +const getCssSheet = (cssText) => { + let htmlHead = document.implementation.createHTMLDocument().head; + + // create style node with css text + let style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(cssText)); + + // added style to temporary document + htmlHead.appendChild(style); + + // cleanup style and temporary document after return + setTimeout(() => { + htmlHead.removeChild(style); + htmlHead = undefined; + }); + + return style.sheet; +} + + +const loadCssom = (ownerDocument, timeout) => { + const sheets = Array.from(ownerDocument.styleSheets); + + const fetchingSheets = sheets + .filter((sheet) => { + return !sheet.disabled; + }) + .map((sheet) => { + try { + // relative or same domain styles + return sheet.cssRules && sheet; + } + catch (e) { + // external styles + // TODO:JEY cache results of external sheets? + return axe.utils + .xhrPromise({ + url: sheet.href, + timeout + }) + .then((response) => { + return getCssSheet(response.responseText); + }); + } + }); + + return Promise.all(fetchingSheets); +} + + +/** + * + * @method preloadCssom + * @memberof axe.utils + * @instance + * @param {Array} treeRoot + * @param + * TODO:JEY + */ +const preloadCssom = ({ + response, + reject, + asset, + timeout, + treeRoot = axe._tree[0], +}) => { + const ids = []; + // TODO:JEY This looks for all unique ownerDocument props on the page. + // There is probably a better way to do this though since getFlattenedTree + // has all of them. Maybe we can index all the roots? + const documents = axe.utils + .querySelectorAllFilter(treeRoot, '*', (node) => { + if (ids.includes(node.shadowId)) { + return false; + } + ids.push(node.shadowId) + return true; + }) + .map((node) => { + return node.actualNode.ownerDocument; + }); + + const asyncDocuments = documents + .map((ownerDocument) => { + return loadCssom(ownerDocument, timeout) + .then((sheets) => { + response({ + [asset]: sheets + }); + }) + .catch(reject) + }); + + return Promise.all(asyncDocuments); +}; + + +axe.utils.preloadCssom = preloadCssom; + +// TODO:JEY - test diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js new file mode 100644 index 0000000000..55716ac330 --- /dev/null +++ b/lib/core/utils/preload.js @@ -0,0 +1,62 @@ +/*eslint */ + +axe.utils.ruleShouldPreload = (options) => { + if (typeof options.preload !== 'undefined' && + options.preload.hasOwnProperty('assets') && + Array.isArray(options.preload.assets) && + options.preload.assets.length) { + return true; + } + return false; +} + + +axe.utils.preload = (options) => { + const preloadFunctionsMap = { + 'cssom': axe.utils.preloadCssom, + 'aom': axe.utils.preloadCssom + }; + + const q = axe.utils.queue(); + + if (axe.utils.ruleShouldPreload(options)) { + options.preload.assets + // unique assets to load, incase user had requested same asset type many times. + .reduce((out, asset) => { + let a = asset.toLowerCase(); + if (!out.includes(a)) { + out.push(a); + } + return out; + }, []) + .forEach((asset) => { + if (axe.constants.preload.includes(asset)) { + q.defer((response, reject) => { + preloadFunctionsMap[asset]({ + response, + reject, + asset: asset, + timeout: options.preload.timeout || axe.constants.preloadTimeout + }); + }); + } + }); + } + + return new Promise((resolve, reject) => { + q.then((results) => { + const out = results.reduce((out, asset) => { + return { + ...out, + ...asset + }; + }, {}); + resolve(out); + }).catch((err) => { + reject(err); + }) + }); + +} + +// TODO:JEY test \ No newline at end of file diff --git a/lib/core/utils/xhr-promise.js b/lib/core/utils/xhr-promise.js new file mode 100644 index 0000000000..fc52c1de8b --- /dev/null +++ b/lib/core/utils/xhr-promise.js @@ -0,0 +1,67 @@ +axe.utils.xhrPromise = (config) => { + 'use strict'; + + const request = new XMLHttpRequest(); // IE7+ friendly + + return new Promise((resolve, reject) => { + // wire up timeout + request.timeout = config.timeout; + + // listen for timeout + request.ontimeout = () => { + reject({ + status: request.status, + statusText: request.statusText + }); + } + + // monitor ready state + request.onreadystatechange = () => { + // request is not complete. + if (request.readyState !== 4) { + return; + } + // process the response + if (request.status >= 200 && request.status <= 300) { + // success + resolve(request); + } else { + // failure + reject({ + status: request.status, + statusText: request.statusText + }); + } + }; + + // setup request + request.open(config.method || 'GET', config.url, true); + + // add headers if any + if (config.headers) { + Object + .keys(config.headers) + .forEach((k) => { + request + .setRequestHeader(k, config.headers[k]); + }); + } + + // enumerate and construct params + let params = config.params; + if (params && + typeof params === 'object') { + params = Object.keys(params) + .map((k) => { + return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`; + }) + .join('&'); + } + + // send + request.send(params); + }); + +} + +// TODO:JEY test cases for the same. \ No newline at end of file diff --git a/lib/rules/css-orientation-lock.json b/lib/rules/css-orientation-lock.json index 3d22b29f6d..15a7ee2caa 100644 --- a/lib/rules/css-orientation-lock.json +++ b/lib/rules/css-orientation-lock.json @@ -10,15 +10,12 @@ "description": "Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape)", "help": "Content is operable in all both landscape and portriat orientations" }, - "preload": { - "assets": [ - "CSSOM" - ], - "timeout": 5000 - }, "all": [ "css-orientation-lock" ], "any": [], - "none": [] + "none": [], + "preload": [ + "cssom" + ] } \ No newline at end of file diff --git a/test/sandbox-css-orientation-lock.html b/test/sandbox-css-orientation-lock.html index 88d06574fb..6bc8fc9219 100644 --- a/test/sandbox-css-orientation-lock.html +++ b/test/sandbox-css-orientation-lock.html @@ -32,13 +32,23 @@ From 18b87e3a2006e90b516adc0044c5be94f5ef142d Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 19 Jun 2018 11:38:12 +0100 Subject: [PATCH 07/43] refactor: remove css-orientation-lock work from cssom preloading. --- doc/rule-descriptions.md | 1 - lib/checks/mobile/css-orientation-lock.js | 1 - lib/checks/mobile/css-orientation-lock.json | 11 ---- lib/rules/css-orientation-lock-matches.js | 1 - lib/rules/css-orientation-lock.json | 21 ------- test/checks/mobile/css-orientation-lock.js | 3 - .../css-orientation-lock-matches.js | 3 - test/sandbox-css-orientation-lock.css | 4 -- test/sandbox-css-orientation-lock.html | 57 ------------------- 9 files changed, 102 deletions(-) delete mode 100644 lib/checks/mobile/css-orientation-lock.js delete mode 100644 lib/checks/mobile/css-orientation-lock.json delete mode 100644 lib/rules/css-orientation-lock-matches.js delete mode 100644 lib/rules/css-orientation-lock.json delete mode 100644 test/checks/mobile/css-orientation-lock.js delete mode 100644 test/rule-matches/css-orientation-lock-matches.js delete mode 100644 test/sandbox-css-orientation-lock.css delete mode 100644 test/sandbox-css-orientation-lock.html diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index cfc63a479b..21622cb687 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -17,7 +17,6 @@ | bypass | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o | true | | checkboxgroup | Ensures related <input type="checkbox"> elements have a group and that the group designation is consistent | Critical | cat.forms, best-practice | true | | color-contrast | Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds | Serious | cat.color, wcag2aa, wcag143 | true | -| css-orientation-lock | Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape) | Serious | cat.structure, wcag262, wcag21aa | true | | definition-list | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131 | true | | dlitem | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131 | true | | document-title | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242 | true | diff --git a/lib/checks/mobile/css-orientation-lock.js b/lib/checks/mobile/css-orientation-lock.js deleted file mode 100644 index 92b4e929ee..0000000000 --- a/lib/checks/mobile/css-orientation-lock.js +++ /dev/null @@ -1 +0,0 @@ -return false; \ No newline at end of file diff --git a/lib/checks/mobile/css-orientation-lock.json b/lib/checks/mobile/css-orientation-lock.json deleted file mode 100644 index 652d19b936..0000000000 --- a/lib/checks/mobile/css-orientation-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "css-orientation-lock", - "evaluate": "css-orientation-lock.js", - "metadata": { - "impact": "serious", - "messages": { - "pass": "Display is operable, and orientation lock does not exist", - "fail": "CSS Orientation lock is applied, and makes display inoperable" - } - } -} diff --git a/lib/rules/css-orientation-lock-matches.js b/lib/rules/css-orientation-lock-matches.js deleted file mode 100644 index 92b4e929ee..0000000000 --- a/lib/rules/css-orientation-lock-matches.js +++ /dev/null @@ -1 +0,0 @@ -return false; \ No newline at end of file diff --git a/lib/rules/css-orientation-lock.json b/lib/rules/css-orientation-lock.json deleted file mode 100644 index 15a7ee2caa..0000000000 --- a/lib/rules/css-orientation-lock.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "css-orientation-lock", - "matches": "css-orientation-lock-matches.js", - "tags": [ - "cat.structure", - "wcag262", - "wcag21aa" - ], - "metadata": { - "description": "Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape)", - "help": "Content is operable in all both landscape and portriat orientations" - }, - "all": [ - "css-orientation-lock" - ], - "any": [], - "none": [], - "preload": [ - "cssom" - ] -} \ No newline at end of file diff --git a/test/checks/mobile/css-orientation-lock.js b/test/checks/mobile/css-orientation-lock.js deleted file mode 100644 index 0c3b256586..0000000000 --- a/test/checks/mobile/css-orientation-lock.js +++ /dev/null @@ -1,3 +0,0 @@ -describe('css-orientation-lock', function () { - 'use strict'; -}); \ No newline at end of file diff --git a/test/rule-matches/css-orientation-lock-matches.js b/test/rule-matches/css-orientation-lock-matches.js deleted file mode 100644 index bf8b7971e3..0000000000 --- a/test/rule-matches/css-orientation-lock-matches.js +++ /dev/null @@ -1,3 +0,0 @@ -describe('css-orientation-lock-matches', function () { - 'use strict'; -}); \ No newline at end of file diff --git a/test/sandbox-css-orientation-lock.css b/test/sandbox-css-orientation-lock.css deleted file mode 100644 index b3ed93e193..0000000000 --- a/test/sandbox-css-orientation-lock.css +++ /dev/null @@ -1,4 +0,0 @@ -html, -body { - -} \ No newline at end of file diff --git a/test/sandbox-css-orientation-lock.html b/test/sandbox-css-orientation-lock.html deleted file mode 100644 index 6bc8fc9219..0000000000 --- a/test/sandbox-css-orientation-lock.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - Sandbox::Css-Orienation-Lock - - - - - - - - - - - - - - - - \ No newline at end of file From 771bd887f7bb432671569ca984367f84333514e9 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 19 Jun 2018 13:07:44 +0100 Subject: [PATCH 08/43] refactor: comments and clean-up --- lib/core/base/audit.js | 6 ++-- lib/core/utils/xhr-promise.js | 2 ++ test/sandbox-preload.css | 4 +++ test/sandbox-preload.html | 56 +++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 test/sandbox-preload.css create mode 100644 test/sandbox-preload.html diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 335e90f214..32b517fd35 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -156,7 +156,7 @@ const runRule = ({ reject }) => { rule.run( - context, + context, options, (out) => { performanceMeasurer(); @@ -226,9 +226,11 @@ Audit.prototype.run = function (context, options, resolve, reject) { return out; }, []); + + // TODO:JEY - do not use promise - try using q. Promise .all([ - // run rules which do not need immediate run + // all rules which do not need preload getRulesQ({ rules: toRunRules, conditionFn: (r) => !r.preload diff --git a/lib/core/utils/xhr-promise.js b/lib/core/utils/xhr-promise.js index fc52c1de8b..f70694eef8 100644 --- a/lib/core/utils/xhr-promise.js +++ b/lib/core/utils/xhr-promise.js @@ -1,6 +1,8 @@ axe.utils.xhrPromise = (config) => { 'use strict'; + // TODO:JEY - do not use promise, use Q + const request = new XMLHttpRequest(); // IE7+ friendly return new Promise((resolve, reject) => { diff --git a/test/sandbox-preload.css b/test/sandbox-preload.css new file mode 100644 index 0000000000..ae684b0e37 --- /dev/null +++ b/test/sandbox-preload.css @@ -0,0 +1,4 @@ +html, +body { + font-size: inherit; +} \ No newline at end of file diff --git a/test/sandbox-preload.html b/test/sandbox-preload.html new file mode 100644 index 0000000000..0e5fcc6f1b --- /dev/null +++ b/test/sandbox-preload.html @@ -0,0 +1,56 @@ + + + + + + + Sandbox::Preload + + + + + + + + + + + + + + + + \ No newline at end of file From eac06ac295f73238d7bf897ac53552a82319d6b0 Mon Sep 17 00:00:00 2001 From: Jey Date: Wed, 20 Jun 2018 10:04:38 +0100 Subject: [PATCH 09/43] feat: audit run queue to await cssom fetching. --- build/templates.js | 2 +- lib/checks/aria/aria-hidden-body.js | 2 +- lib/checks/aria/aria-hidden-body.json | 20 ++--- lib/core/base/audit.js | 116 ++++++++++---------------- lib/core/base/check.js | 20 +++-- lib/core/base/rule.js | 88 ++++++++++++------- lib/core/constants.js | 4 +- lib/core/utils/preload.js | 108 ++++++++++++++++-------- test/sandbox-preload.html | 8 +- 9 files changed, 201 insertions(+), 167 deletions(-) diff --git a/build/templates.js b/build/templates.js index 24f2d2bbda..ad6f2263e8 100644 --- a/build/templates.js +++ b/build/templates.js @@ -1,5 +1,5 @@ module.exports = { - evaluate: 'function ({ node, options, virtualNode, preloadAssets }) {\n<%=source%>\n}', + evaluate: 'function (node, options, virtualNode, preloadedAssets) {\n<%=source%>\n}', after: 'function (results, options) {\n<%=source%>\n}', gather: 'function (context) {\n<%=source%>\n}', matches: 'function (node, virtualNode) {\n<%=source%>\n}', diff --git a/lib/checks/aria/aria-hidden-body.js b/lib/checks/aria/aria-hidden-body.js index 2cb98c9513..b46bd1095a 100644 --- a/lib/checks/aria/aria-hidden-body.js +++ b/lib/checks/aria/aria-hidden-body.js @@ -1 +1 @@ -return node.getAttribute('aria-hidden') !== 'true'; +return node.getAttribute('aria-hidden') !== 'true'; \ No newline at end of file diff --git a/lib/checks/aria/aria-hidden-body.json b/lib/checks/aria/aria-hidden-body.json index c4d2d85cca..889fd68d2a 100644 --- a/lib/checks/aria/aria-hidden-body.json +++ b/lib/checks/aria/aria-hidden-body.json @@ -1,11 +1,11 @@ { - "id": "aria-hidden-body", - "evaluate": "aria-hidden-body.js", - "metadata": { - "impact": "critical", - "messages": { - "pass": "No aria-hidden attribute is present on document body", - "fail": "aria-hidden=true should not be present on the document body" - } - } -} + "id": "aria-hidden-body", + "evaluate": "aria-hidden-body.js", + "metadata": { + "impact": "critical", + "messages": { + "pass": "No aria-hidden attribute is present on document body", + "fail": "aria-hidden=true should not be present on the document body" + } + } +} \ No newline at end of file diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 32b517fd35..d3c3bc3e8c 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -133,7 +133,6 @@ Audit.prototype.addCheck = function (spec) { } }; -const noop = () => { }; const measurePerformance = (id) => { const marker = { @@ -147,22 +146,27 @@ const measurePerformance = (id) => { }; }; + const runRule = ({ rule, context, options, + preloadedAssets, performanceMeasurer, resolve, reject }) => { - rule.run( + rule.run({ context, options, - (out) => { - performanceMeasurer(); + preloadedAssets, + resolve: (out) => { + if (performanceMeasurer) { + performanceMeasurer(); + } resolve(out); }, - (err) => { + reject: (err) => { if (!options.debug) { const errResult = Object.assign(new RuleResult(rule), { result: axe.constants.CANTTELL, @@ -176,25 +180,9 @@ const runRule = ({ reject(err); } } - ); + }); }; -const getRulesQ = ({ rules, conditionFn, extend }) => { - return rules - .reduce((out, r) => { - if (conditionFn(r)) { - out.defer((resolve, reject) => { - runRule({ - ...r, - ...extend, - resolve, - reject - }) - }); - } - return out; - }, axe.utils.queue()) -}; /** * Runs the Audit; which in turn should call `run` on each rule. @@ -210,62 +198,41 @@ Audit.prototype.run = function (context, options, resolve, reject) { axe._selectCache = []; - const toRunRules = this.rules - .reduce((out, rule) => { - if (axe.utils.ruleShouldRun(rule, context, options)) { - out.push({ - rule, - context, - options, - performanceMeasurer: noop, - // performanceMeasurer: options.performanceTimer - // ? measurePerformance(rule.id) : noop, - preload: rule.preload.length > 0 - }); - } - return out; - }, []); - - - // TODO:JEY - do not use promise - try using q. - Promise - .all([ - // all rules which do not need preload - getRulesQ({ - rules: toRunRules, - conditionFn: (r) => !r.preload - }), - // start preloading - axe.utils.preload(options) - ]) - .then((results) => { - const axeResults = results[0]; - const preloadAssets = results[1]; - // run rules which need preloaded assets - return new Promise((res, rej) => { - - getRulesQ({ - rules: toRunRules, - conditionFn: (r) => r.preload, - extend: { - preloadAssets: preloadAssets + // preload assets if necessary + axe.utils.preload(options) + .then((preloadedAssets) => { + // deduce a queue of rules to execute, extended with preloadedAssets passed to checks + this.rules + .reduce((out, rule) => { + if (axe.utils.ruleShouldRun(rule, context, options)) { + out.defer((resolve, reject) => { + runRule({ + rule, + context, + options, + performanceMeasurer: options.performanceTimer && measurePerformance(rule.id), + preloadedAssets, + resolve, + reject + }) + }); } - }).then((results) => { - res([...axeResults, ...results]) - }).catch(rej); - }); - }) - .then((results) => { - axe._selectCache = undefined; // remove the cache - resolve( - results.filter((result) => { - return !!result; + return out; + }, axe.utils.queue()) + .then((results) => { + axe._selectCache = undefined; // remove the cache + resolve( + results.filter((result) => { + return !!result; + }) + ); }) - ); + .catch(reject) }) .catch(reject); }; + /** * Runs Rule `after` post processing functions * @param {Array} results Array of RuleResults to postprocess @@ -287,6 +254,7 @@ Audit.prototype.after = function (results, options) { }); }; + /** * Get the rule with a given ID * @param {string} @@ -296,6 +264,7 @@ Audit.prototype.getRule = function (ruleId) { return this.rules.find(rule => rule.id === ruleId); }; + /** * Ensure all rules that are expected to run exist * @throws {Error} If any tag or rule specified in options is unknown @@ -364,11 +333,11 @@ Audit.prototype.normalizeOptions = function (options) { return options; }; + /* * Updates the default options and then applies them * @param {Mixed} options Options object */ - Audit.prototype.setBranding = function (branding) { 'use strict'; let previous = { @@ -386,6 +355,7 @@ Audit.prototype.setBranding = function (branding) { this._constructHelpUrls(previous); }; + /** * For all the rules, create the helpUrl and add it to the data for that rule */ diff --git a/lib/core/base/check.js b/lib/core/base/check.js index a353c8a416..16c8f92f2c 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -58,8 +58,13 @@ Check.prototype.enabled = true; * information for the check * @param {Function} callback Function to fire when check is complete */ -Check.prototype.run = function (node, options, resolve, reject) { - 'use strict'; +Check.prototype.run = function ({ + node, + options, + preloadedAssets, + resolve, + reject +}) { options = options || {}; var enabled = options.hasOwnProperty('enabled') ? options.enabled : this.enabled, checkOptions = options.options || this.options; @@ -70,11 +75,12 @@ Check.prototype.run = function (node, options, resolve, reject) { var result; try { - result = this.evaluate.call(checkHelper, { - node: node.actualNode, - options: checkOptions, - virtualNode: node - }); + result = this.evaluate.call(checkHelper, + node.actualNode, + checkOptions, + node, + preloadedAssets + ); } catch (e) { reject(e); return; diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index 4cab3677ea..2de3e72849 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -104,16 +104,27 @@ Rule.prototype.gather = function (context) { return elements; }; -Rule.prototype.runChecks = function (type, node, options, resolve, reject) { - 'use strict'; - +Rule.prototype.runChecks = function ({ + type, + node, + options, + preloadedAssets, + resolve, + reject +}) { var self = this; var checkQueue = axe.utils.queue(); this[type].forEach(function (c) { var check = self._audit.checks[c.id || c]; var option = axe.utils.getCheckOption(check, self.id, options); checkQueue.defer(function (res, rej) { - check.run(node, option, res, rej); + check.run({ + node, + options: option, + preloadedAssets, + resolve: res, + reject: rej + }); }); }); @@ -132,7 +143,13 @@ Rule.prototype.runChecks = function (type, node, options, resolve, reject) { * @param {Mixed} options Options specific to this rule * @param {Function} callback Function to call when evaluate is complete; receives a RuleResult instance */ -Rule.prototype.run = function (context, options, resolve, reject) { +Rule.prototype.run = function ({ + context, + options, + preloadedAssets, + resolve, + reject +}) { /*eslint max-statements: ["error",17] */ const q = axe.utils.queue(); const ruleResult = new RuleResult(this); @@ -146,47 +163,54 @@ Rule.prototype.run = function (context, options, resolve, reject) { .filter(node => this.matches(node.actualNode, node)); } catch (error) { // Exit the rule execution if matches fails - reject(new SupportError({cause: error, ruleId: this.id})); + reject(new SupportError({ cause: error, ruleId: this.id })); return; } if (options.performanceTimer) { - axe.log('gather (', nodes.length, '):', axe.utils.performanceTimer.timeElapsed()+'ms'); + axe.log('gather (', nodes.length, '):', axe.utils.performanceTimer.timeElapsed() + 'ms'); axe.utils.performanceTimer.mark(markStart); } nodes.forEach(node => { q.defer((resolveNode, rejectNode) => { var checkQueue = axe.utils.queue(); - checkQueue.defer((res, rej) => { - this.runChecks('any', node, options, res, rej); - }); - checkQueue.defer((res, rej) => { - this.runChecks('all', node, options, res, rej); - }); - checkQueue.defer((res, rej) => { - this.runChecks('none', node, options, res, rej); - }); - checkQueue.then(function (results) { - if (results.length) { - var hasResults = false, result = {}; - results.forEach(function (r) { - var res = r.results.filter(function (result) { - return result; + ['any', 'all', 'none'] + .forEach((type) => { + checkQueue.defer((res, rej) => { + this.runChecks({ + type, + node, + options, + preloadedAssets, + resolve: res, + reject: rej }); - result[r.type] = res; - if (res.length) { - hasResults = true; - } }); - if (hasResults) { - result.node = new axe.utils.DqElement(node.actualNode, options); - ruleResult.nodes.push(result); + }); + + checkQueue + .then(function (results) { + if (results.length) { + var hasResults = false, result = {}; + results.forEach(function (r) { + var res = r.results.filter(function (result) { + return result; + }); + result[r.type] = res; + if (res.length) { + hasResults = true; + } + }); + if (hasResults) { + result.node = new axe.utils.DqElement(node.actualNode, options); + ruleResult.nodes.push(result); + } } - } - resolveNode(); - }).catch(err => rejectNode(err)); + resolveNode(); + }) + .catch(err => rejectNode(err)); }); }); diff --git a/lib/core/constants.js b/lib/core/constants.js index e00c99a5ba..c2d2a540bd 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -28,8 +28,8 @@ resultGroups: [], resultGroupMap: {}, impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']), - preload: Object.freeze(['cssom', 'aom']), - preloadTimeout: 30000 + preloadAssets: Object.freeze(['cssom']), + preloadAssetsTimeout: 30000 }; definitions.forEach(function (definition) { diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index 55716ac330..8fff290fcd 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -1,50 +1,87 @@ /*eslint */ -axe.utils.ruleShouldPreload = (options) => { - if (typeof options.preload !== 'undefined' && - options.preload.hasOwnProperty('assets') && - Array.isArray(options.preload.assets) && - options.preload.assets.length) { - return true; +//TODO:JEY doc +const getPreloadConfig = (options) => { + /** + * Possible values for `preload` + * true | false (default) | { assets: ['cssom'], timeout: 30000 (optional) } + */ + const p = options.preload; + + // default fallback config + let out = { + preload: false, + assets: axe.constants.preloadAssets, + timeout: p && p.timeout + ? Number(p.timeout) + : axe.constants.preloadAssetsTimeout + }; + + // by type is boolean + if (typeof (p) == typeof (true)) { + out.preload = p; + } else { + // if type is object - ensure an array of assets to load is specified + if (p.hasOwnProperty('assets') && + Array.isArray(p.assets) && + p.assets.length) { + out.preload = true; + out.assets = p.assets + .map((a) => { + if (axe.constants.preloadAssets.includes(a)) { + return a; + } else { + const e = `Requested asset: ${a}, not supported by aXe.` + + `Supported assets are: ${axe.constants.preloadAssets.map(_ => _).join(', ')}.`; + console.error(e); + throw new Error(e); + } + }) + .reduce((out, asset) => { + const a = asset.toLowerCase(); + if (!out.includes(a)) { // unique assets to load, incase user had requested same asset type many times. + out.push(a); + } + return out; + }, []); + } else { + const e = 'No assets configured for preload in aXe run configuration'; + console.error(e); + throw new Error(e); + } } - return false; -} + return out; +} +//TODO:JEY doc axe.utils.preload = (options) => { + const preloadFunctionsMap = { - 'cssom': axe.utils.preloadCssom, - 'aom': axe.utils.preloadCssom + 'cssom': axe.utils.preloadCssom }; - const q = axe.utils.queue(); - - if (axe.utils.ruleShouldPreload(options)) { - options.preload.assets - // unique assets to load, incase user had requested same asset type many times. - .reduce((out, asset) => { - let a = asset.toLowerCase(); - if (!out.includes(a)) { - out.push(a); - } - return out; - }, []) - .forEach((asset) => { - if (axe.constants.preload.includes(asset)) { - q.defer((response, reject) => { - preloadFunctionsMap[asset]({ - response, - reject, - asset: asset, - timeout: options.preload.timeout || axe.constants.preloadTimeout - }); - }); - } + const loadQ = axe.utils.queue(); + + const preloadConfig = getPreloadConfig(options); + + if (preloadConfig.preload) { + preloadConfig.assets.forEach((asset) => { + loadQ.defer((response, reject) => { + preloadFunctionsMap[asset]({ + response, + reject, + asset, + timeout: preloadConfig.timeout + }); }); + }); } - return new Promise((resolve, reject) => { - q.then((results) => { + let outQ = axe.utils.queue(); + + outQ.defer((resolve, reject) => { + loadQ.then((results) => { const out = results.reduce((out, asset) => { return { ...out, @@ -57,6 +94,7 @@ axe.utils.preload = (options) => { }) }); + return outQ; } // TODO:JEY test \ No newline at end of file diff --git a/test/sandbox-preload.html b/test/sandbox-preload.html index 0e5fcc6f1b..4522c08b41 100644 --- a/test/sandbox-preload.html +++ b/test/sandbox-preload.html @@ -10,7 +10,7 @@ - + + + + + + `; + + afterEach(function () { + fixture.innerHTML = ''; + axe._tree = undefined; + }); + + it('should be a function', function () { + assert.isFunction(axe.utils.preloadCssom); + }); + + it('should return a queue', function () { + fixture.innerHTML = pageTpl; + const tree = axe._tree = axe.utils.getFlattenedTree(fixture); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + assert.isObject(actual); + }); + + it('should ensure queue is defer(able)', function (done) { + fixture.innerHTML = pageTpl; + const tree = axe._tree = axe.utils.getFlattenedTree(fixture); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + actual + .defer((function (res, rej) { + res(true); + assert.isOk(true); + done(); + })); + }); + + it('should ensure queue is then(able)', function (done) { + fixture.innerHTML = pageTpl; + const tree = axe._tree = axe.utils.getFlattenedTree(fixture); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + actual + .then((function (results) { + assert.isOk(true); + done(); + })); + }); + + it('should ensure result has cssom property', function (done) { + fixture.innerHTML = pageTpl; + const tree = axe._tree = axe.utils.getFlattenedTree(fixture); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + actual + .then((function (results) { + const r = results[0]; + assert.property(r, 'cssom'); + done(); + })); + }); + + it('should ensure result is an array', function (done) { + fixture.innerHTML = pageTpl; + const target = fixture.children[0]; + const tree = axe._tree = axe.utils.getFlattenedTree(target); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + actual + .then((function (results) { + const sheets = results[0].cssom; + assert.isTrue(Array.isArray(sheets)); + assert.lengthOf(sheets, 2); + done(); + })); + }); + + it('should ensure all returned stylesheet is defined and has readable sheet/ cssrules', function (done) { + fixture.innerHTML = pageTpl; + const target = fixture.children[0]; + const tree = axe._tree = axe.utils.getFlattenedTree(target); + const args = { + asset: ['cssom'], + timeout: 30000, + treeRoot: tree + }; + const actual = axe.utils.preloadCssom(args); + actual + .then(function (results) { + const sheets = results[0].cssom; + sheets.forEach(function(s){ + assert.isDefined(s); + assert.property(s, 'cssRules'); + }); + done(); + }); + }); + +}); \ No newline at end of file diff --git a/test/core/utils/preload.js b/test/core/utils/preload.js new file mode 100644 index 0000000000..b2da79bc83 --- /dev/null +++ b/test/core/utils/preload.js @@ -0,0 +1,43 @@ + + +describe('axe.utils.preload', function () { + 'use strict'; + + it('should be a function', function () { + assert.isFunction(axe.utils.preload); + }); + + it('should return a queue', function () { + const options = { + preload: true + }; + const actual = axe.utils.preload(options); + assert.isObject(actual); + }); + + it('should ensure queue is defer(able)', function (done) { + const options = { + preload: false + }; + const actual = axe.utils.preload(options); + actual + .defer((function (res, rej) { + res(true); + assert.isOk(true); + done(); + })); + }); + + it('should ensure queue is then(able)', function (done) { + const options = { + preload: false + }; + const actual = axe.utils.preload(options); + actual + .then((function (results) { + assert.isOk(true); + done(); + })); + }); + +}); \ No newline at end of file diff --git a/test/core/utils/xhr-q.js b/test/core/utils/xhr-q.js new file mode 100644 index 0000000000..7a056a5272 --- /dev/null +++ b/test/core/utils/xhr-q.js @@ -0,0 +1,106 @@ +describe('axe.utils.xhrQ', function () { + 'use strict'; + + beforeEach(function () { + this.xhr = sinon.useFakeXMLHttpRequest(); + this.requests = []; + this.xhr.onCreate = function (xhr) { + this.requests.push(xhr); + }.bind(this); + }); + + afterEach(function () { + this.xhr.restore(); + }); + + it('should reject queue on 500 error', function (done) { + const config = { + url: '/kaBoom' + }; + const fakeResponse = { + status: 500, + contentType: { 'Content-Type': 'text/json' }, + dataJson: { message: 'some dummy data.' } + }; + + axe.utils.xhrQ(config) + .then(function (response) { + assert.fail('should not have resolved the queue'); + done(); + }) + .catch(function (err) { + assert.equal(err.status, 500); + done(); + }); + + this.requests[0] + .respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); + }); + + it('should resolve queue for status 200', function (done) { + const config = { + url: '/gotMe' + }; + const fakeResponse = { + status: 200, + contentType: { 'Content-Type': 'text/json' }, + dataJson: { message: 'some dummy data.' } + }; + + axe.utils.xhrQ(config) + .then(function (results) { + const response = results[0]; + assert.equal(response.status, 200); + done(); + }) + .catch(function () { + assert.fail('should not have rejected the queue.') + done(); + }); + + this.requests[0] + .respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); + }); + + it('should populate response', function (done) { + const config = { + url: '/gotMe' + }; + const fakeResponse = { + status: 200, + contentType: { 'Content-Type': 'text/json' }, + dataJson: { + status: 200, + responseText: 'My Expected Data!', + } + }; + + axe.utils.xhrQ(config) + .then(function (results) { + const response = results[0]; + assert.isObject(response); + assert.deepEqual(JSON.parse(response.responseText), fakeResponse.dataJson); + done(); + }) + .catch(function () { + assert.fail('should not have rejected the queue.'); + done(); + }); + + this.requests[0] + .respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); + }); + +}); \ No newline at end of file diff --git a/test/integration/rules/runner.tmpl b/test/integration/rules/runner.tmpl index 8b714221e4..85009a643e 100644 --- a/test/integration/rules/runner.tmpl +++ b/test/integration/rules/runner.tmpl @@ -7,12 +7,14 @@ + <% files.forEach(function (file) { %> diff --git a/test/runner.tmpl b/test/runner.tmpl index d53be6d1b6..301677d7e4 100644 --- a/test/runner.tmpl +++ b/test/runner.tmpl @@ -7,12 +7,14 @@ + <% files.forEach(function (file) { %> diff --git a/test/sandbox.html b/test/sandbox.html new file mode 100644 index 0000000000..8cfe3389a3 --- /dev/null +++ b/test/sandbox.html @@ -0,0 +1,33 @@ + + + + + + + Sandbox::Temp + + + + + + + + + + + + + + + \ No newline at end of file From 50229a03a5878b3fc529575384f4184a0207e589 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 25 Jun 2018 16:34:47 +0100 Subject: [PATCH 16/43] style: formamtting updates. --- lib/core/base/audit.js | 9 +++--- test/core/base/audit.js | 21 +++++++------ test/core/public/run.js | 69 +++++++++++++++++++++-------------------- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 9307b74536..1bc70eb6b9 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -165,6 +165,8 @@ Audit.prototype.run = function (context, options, resolve, reject) { axe._selectCache = []; + + // preload assets if necessary axe.utils.preload(options) .then(([preloadedAssets]) => { // q - retuns array -> destructure/ pluck the first item, is an key value map of preloadedAssets @@ -174,15 +176,15 @@ Audit.prototype.run = function (context, options, resolve, reject) { preloadedAssets }; // deduce a queue of rules to execute, extended with preloadedAssets passed to checks - const q = axe.utils.queue(); - this.rules .forEach((rule) => { if (axe.utils.ruleShouldRun(rule, context, options)) { const marker = performanceMarker(rule.id, options.performanceTimer); q.defer((resolve, reject) => { - rule.run(context, options, + rule.run( + context, + options, (result) => { marker(); resolve(result); @@ -213,7 +215,6 @@ Audit.prototype.run = function (context, options, resolve, reject) { }) ); }).catch(reject); - }) .catch(reject); }; diff --git a/test/core/base/audit.js b/test/core/base/audit.js index 9ed7124515..e900ea0e29 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -5,7 +5,7 @@ describe('Audit', function () { var isNotCalled = function (err) { throw err || new Error('Reject should not be called'); }; - var noop = function () {}; + var noop = function () { }; var mockChecks = [{ id: 'positive1-check1', @@ -355,14 +355,14 @@ describe('Audit', function () { assert.equal(audit.checks.target, undefined); audit.addCheck({ id: 'target', - metadata: {guy:'bob'} + metadata: { guy: 'bob' } }); assert.ok(audit.checks.target); assert.equal(audit.data.checks.target.guy, 'bob'); }); it('should reconfigure existing check', function () { var audit = new Audit(); - var myTest = function () {}; + var myTest = function () { }; audit.addCheck({ id: 'target', evaluate: myTest, @@ -535,16 +535,19 @@ describe('Audit', function () { var targetRule = mockRules[mockRules.length - 1], rule = axe.utils.findBy(a.rules, 'id', targetRule.id), passed = false, - orig, options; + orig, + options; fixture.innerHTML = 'link'; orig = rule.run; rule.run = function (node, o, callback) { - assert.deepEqual(o, options); + assert.property(o, 'rules'); passed = true; callback({}); }; - options = {rules: {}}; + + options = { rules: {} }; + (options.rules[targetRule.id] = {}).data = 'monkeys'; a.run({ include: [document] }, options, function () { assert.ok(passed); @@ -564,7 +567,7 @@ describe('Audit', function () { } })); - audit.run({ include: [ document.body ], page: false }, {}, function (results) { + audit.run({ include: [document.body], page: false }, {}, function (results) { assert.deepEqual(results, []); }, isNotCalled); @@ -592,7 +595,7 @@ describe('Audit', function () { 'values': ['throw1'] } }, function (results) { - assert.lengthOf(results,1); + assert.lengthOf(results, 1); assert.equal(results[0].result, 'cantTell'); assert.equal(results[0].message, err.message); assert.equal(results[0].stack, err.stack); @@ -813,7 +816,7 @@ describe('Audit', function () { assert.throws(function () { a.normalizeOptions({ rules: { - fakeRule: { enabled: false} + fakeRule: { enabled: false } } }); }); diff --git a/test/core/public/run.js b/test/core/public/run.js index e01a914780..6d59a4c6f4 100644 --- a/test/core/public/run.js +++ b/test/core/public/run.js @@ -2,7 +2,7 @@ describe('axe.run', function () { 'use strict'; var fixture = document.getElementById('fixture'); - var noop = function () {}; + var noop = function () { }; var origRunRules = axe._runRules; beforeEach(function () { @@ -32,9 +32,9 @@ describe('axe.run', function () { fixture.innerHTML = '
'; var options = { runOnly: { - type: 'rule', - values: ['test'] - } + type: 'rule', + values: ['test'] + } }; axe.run(['#t1'], options, function () { @@ -61,7 +61,7 @@ describe('axe.run', function () { }); it('works with performance logging enabled', function (done) { - axe.run(document, {performanceTimer: true}, function (err, result) { + axe.run(document, { performanceTimer: true }, function (err, result) { assert.isObject(result); done(); }); @@ -69,11 +69,11 @@ describe('axe.run', function () { it('treats objects with include or exclude as the context object', function (done) { axe._runRules = function (ctxt) { - assert.deepEqual(ctxt, {include: '#BoggyB'}); + assert.deepEqual(ctxt, { include: '#BoggyB' }); done(); }; - axe.run({include: '#BoggyB'}, noop); + axe.run({ include: '#BoggyB' }, noop); }); it('treats objects with neither include or exclude as the option object', function (done) { @@ -82,7 +82,7 @@ describe('axe.run', function () { done(); }; - axe.run({HHG: 'hallelujah'}, noop); + axe.run({ HHG: 'hallelujah' }, noop); }); it('does not fail if no callback is specified', function (done) { @@ -186,10 +186,10 @@ describe('axe.run', function () { var p = axe.run({ reporter: 'raw' }); p.then(noop) - .catch(function (err) { - assert.equal(err, 'I surrender!'); - done(); - }); + .catch(function (err) { + assert.equal(err, 'I surrender!'); + done(); + }); assert.instanceOf(p, window.Promise); }); @@ -215,16 +215,16 @@ describe('axe.run', function () { }; axe.run() - .then(function () { - throw new Error('err'); - }, function (e) { - assert.isNotOk(e, 'Caught callback error in the wrong place'); - done(); - - }).catch(function (e) { - assert.equal(e.message, 'err'); - done(); - }); + .then(function () { + throw new Error('err'); + }, function (e) { + assert.isNotOk(e, 'Caught callback error in the wrong place'); + done(); + + }).catch(function (e) { + assert.equal(e.message, 'err'); + done(); + }); }); promiseIt('is called after cleanup', function (done) { @@ -232,17 +232,17 @@ describe('axe.run', function () { axe._runRules = function (ctxt, opt, resolve) { axe._runRules = origRunRules; // Check that cleanup is called before the callback is executed - resolve('MB Bomb', function cleanup () { + resolve('MB Bomb', function cleanup() { isClean = true; }); }; axe.run({ reporter: 'raw' }) - .then(function () { - assert(isClean, 'cleanup must be called first'); - done(); - }) - .catch(done); + .then(function () { + assert(isClean, 'cleanup must be called first'); + done(); + }) + .catch(done); }); }); @@ -275,7 +275,7 @@ describe('axe.run', function () { done(); }; axe._audit.reporter = null; - axe.run(document, {reporter: 'raw'}, noop); + axe.run(document, { reporter: 'raw' }, noop); }); }); @@ -448,15 +448,16 @@ describe('axe.run iframes', function () { axe.run('#fixture', {}, function (err, result) { assert.equal(result.violations.length, 1); + var violation = result.violations[0]; assert.equal(violation.nodes.length, 2, - 'one node for top frame, one for iframe'); - assert.isTrue(violation.nodes.some(function(node) { + 'one node for top frame, one for iframe'); + assert.isTrue(violation.nodes.some(function (node) { return node.target.length === 1 && node.target[0] === '#target'; }), 'one result from top frame'); - assert.isTrue(violation.nodes.some(function(node) { + assert.isTrue(violation.nodes.some(function (node) { return node.target.length === 2 && - node.target[0] === '#fixture > iframe'; + node.target[0] === '#fixture > iframe'; }), 'one result from iframe'); window.clearTimeout(safetyTimeout); done(); @@ -479,7 +480,7 @@ describe('axe.run iframes', function () { assert.equal(result.violations.length, 1); var violation = result.violations[0]; assert.equal(violation.nodes.length, 1, - 'only top frame'); + 'only top frame'); assert.equal(violation.nodes[0].target.length, 1); assert.equal(violation.nodes[0].target[0], '#target'); window.clearTimeout(safetyTimeout); From 8426fab46ff239b3ecfa2625c64858c3206d31b7 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 25 Jun 2018 17:09:51 +0100 Subject: [PATCH 17/43] style: update tests to not have any es6 keywords --- test/core/utils/preload-config.js | 40 +++++++-------- test/core/utils/preload-cssom.js | 85 +++++++++++-------------------- test/core/utils/preload.js | 14 ++--- test/core/utils/xhr-q.js | 20 ++++---- 4 files changed, 67 insertions(+), 92 deletions(-) diff --git a/test/core/utils/preload-config.js b/test/core/utils/preload-config.js index 761c0a02fb..2617774d89 100644 --- a/test/core/utils/preload-config.js +++ b/test/core/utils/preload-config.js @@ -1,5 +1,3 @@ - - describe('axe.utils.preloadConfig', function () { 'use strict'; @@ -8,8 +6,8 @@ describe('axe.utils.preloadConfig', function () { }); it('should return default preload configuration if no preload options', function () { - const actual = axe.utils.preloadConfig({}); - const expected = { + var actual = axe.utils.preloadConfig({}); + var expected = { preload: false, assets: ['cssom'], timeout: 30000 @@ -18,60 +16,60 @@ describe('axe.utils.preloadConfig', function () { }); it('should return default preload value as false', function () { - const actual = axe.utils.preloadConfig({}).preload; - const expected = false; + var actual = axe.utils.preloadConfig({}).preload; + var expected = false; assert.strictEqual(actual, expected); }); it('should return default assets if preload options is set to true', function () { - const options = { + var options = { preload: true }; - const actual = axe.utils.preloadConfig(options).assets; - const expected = ['cssom']; + var actual = axe.utils.preloadConfig(options).assets; + var expected = ['cssom']; assert.deepEqual(actual, expected); }); it('should return default timeout value if not configured', function () { - const options = { + var options = { preload: true, }; - const actual = axe.utils.preloadConfig(options).timeout; - const expected = 30000; + var actual = axe.utils.preloadConfig(options).timeout; + var expected = 30000; assert.equal(actual, expected); }); it('should throw error if requested asset type is not supported', function () { - const options = { + var options = { preload: { assets: ['aom'] } }; - const actual = function () { axe.utils.preloadConfig(options); } - const expected = Error; + var actual = function () { axe.utils.preloadConfig(options); } + var expected = Error; assert.throws(actual, expected); }); it('should throw error if assets array is empty in options for preload', function () { - const options = { + var options = { preload: { assets: [] } }; - const actual = function () { axe.utils.preloadConfig(options); } - const expected = Error; + var actual = function () { axe.utils.preloadConfig(options); } + var expected = Error; assert.throws(actual, expected); }); it('should unique assets requested if repeated assets are passed via options', function () { - const options = { + var options = { preload: { assets: ['cssom', 'cssom'], timeout: 15000 } }; - const actual = axe.utils.preloadConfig(options); - const expected = { + var actual = axe.utils.preloadConfig(options); + var expected = { preload: true, assets: ['cssom'], timeout: 15000 diff --git a/test/core/utils/preload-cssom.js b/test/core/utils/preload-cssom.js index 28904b56e3..70ffc727b8 100644 --- a/test/core/utils/preload-cssom.js +++ b/test/core/utils/preload-cssom.js @@ -1,37 +1,8 @@ - - describe('axe.utils.preloadCssom', function () { 'use strict'; - - let fixture = document.getElementById('fixture'); - const pageTpl = ` - - - - - - Sandbox::Preload - - - - - - - - - `; + + var fixture = document.getElementById('fixture'); + var pageTpl = ''; afterEach(function () { fixture.innerHTML = ''; @@ -44,28 +15,29 @@ describe('axe.utils.preloadCssom', function () { it('should return a queue', function () { fixture.innerHTML = pageTpl; - const tree = axe._tree = axe.utils.getFlattenedTree(fixture); - const args = { + var tree = axe._tree = axe.utils.getFlattenedTree(fixture); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); assert.isObject(actual); }); it('should ensure queue is defer(able)', function (done) { fixture.innerHTML = pageTpl; - const tree = axe._tree = axe.utils.getFlattenedTree(fixture); - const args = { + var tree = axe._tree = axe.utils.getFlattenedTree(fixture); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); actual .defer((function (res, rej) { res(true); + assert.isFunction(rej); assert.isOk(true); done(); })); @@ -73,15 +45,16 @@ describe('axe.utils.preloadCssom', function () { it('should ensure queue is then(able)', function (done) { fixture.innerHTML = pageTpl; - const tree = axe._tree = axe.utils.getFlattenedTree(fixture); - const args = { + var tree = axe._tree = axe.utils.getFlattenedTree(fixture); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); actual .then((function (results) { + assert.isDefined(results); assert.isOk(true); done(); })); @@ -89,16 +62,16 @@ describe('axe.utils.preloadCssom', function () { it('should ensure result has cssom property', function (done) { fixture.innerHTML = pageTpl; - const tree = axe._tree = axe.utils.getFlattenedTree(fixture); - const args = { + var tree = axe._tree = axe.utils.getFlattenedTree(fixture); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); actual .then((function (results) { - const r = results[0]; + var r = results[0]; assert.property(r, 'cssom'); done(); })); @@ -106,17 +79,17 @@ describe('axe.utils.preloadCssom', function () { it('should ensure result is an array', function (done) { fixture.innerHTML = pageTpl; - const target = fixture.children[0]; - const tree = axe._tree = axe.utils.getFlattenedTree(target); - const args = { + var target = fixture.children[0]; + var tree = axe._tree = axe.utils.getFlattenedTree(target); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); actual .then((function (results) { - const sheets = results[0].cssom; + var sheets = results[0].cssom; assert.isTrue(Array.isArray(sheets)); assert.lengthOf(sheets, 2); done(); @@ -125,18 +98,18 @@ describe('axe.utils.preloadCssom', function () { it('should ensure all returned stylesheet is defined and has readable sheet/ cssrules', function (done) { fixture.innerHTML = pageTpl; - const target = fixture.children[0]; - const tree = axe._tree = axe.utils.getFlattenedTree(target); - const args = { + var target = fixture.children[0]; + var tree = axe._tree = axe.utils.getFlattenedTree(target); + var args = { asset: ['cssom'], timeout: 30000, treeRoot: tree }; - const actual = axe.utils.preloadCssom(args); + var actual = axe.utils.preloadCssom(args); actual .then(function (results) { - const sheets = results[0].cssom; - sheets.forEach(function(s){ + var sheets = results[0].cssom; + sheets.forEach(function (s) { assert.isDefined(s); assert.property(s, 'cssRules'); }); diff --git a/test/core/utils/preload.js b/test/core/utils/preload.js index b2da79bc83..31b7c2ea93 100644 --- a/test/core/utils/preload.js +++ b/test/core/utils/preload.js @@ -8,20 +8,21 @@ describe('axe.utils.preload', function () { }); it('should return a queue', function () { - const options = { + var options = { preload: true }; - const actual = axe.utils.preload(options); + var actual = axe.utils.preload(options); assert.isObject(actual); }); it('should ensure queue is defer(able)', function (done) { - const options = { + var options = { preload: false }; - const actual = axe.utils.preload(options); + var actual = axe.utils.preload(options); actual .defer((function (res, rej) { + assert.isFunction(rej); res(true); assert.isOk(true); done(); @@ -29,12 +30,13 @@ describe('axe.utils.preload', function () { }); it('should ensure queue is then(able)', function (done) { - const options = { + var options = { preload: false }; - const actual = axe.utils.preload(options); + var actual = axe.utils.preload(options); actual .then((function (results) { + assert.isDefined(results); assert.isOk(true); done(); })); diff --git a/test/core/utils/xhr-q.js b/test/core/utils/xhr-q.js index 7a056a5272..c344f179ce 100644 --- a/test/core/utils/xhr-q.js +++ b/test/core/utils/xhr-q.js @@ -1,3 +1,5 @@ +/*global sinon */ + describe('axe.utils.xhrQ', function () { 'use strict'; @@ -14,17 +16,17 @@ describe('axe.utils.xhrQ', function () { }); it('should reject queue on 500 error', function (done) { - const config = { + var config = { url: '/kaBoom' }; - const fakeResponse = { + var fakeResponse = { status: 500, contentType: { 'Content-Type': 'text/json' }, dataJson: { message: 'some dummy data.' } }; axe.utils.xhrQ(config) - .then(function (response) { + .then(function () { assert.fail('should not have resolved the queue'); done(); }) @@ -42,10 +44,10 @@ describe('axe.utils.xhrQ', function () { }); it('should resolve queue for status 200', function (done) { - const config = { + var config = { url: '/gotMe' }; - const fakeResponse = { + var fakeResponse = { status: 200, contentType: { 'Content-Type': 'text/json' }, dataJson: { message: 'some dummy data.' } @@ -53,7 +55,7 @@ describe('axe.utils.xhrQ', function () { axe.utils.xhrQ(config) .then(function (results) { - const response = results[0]; + var response = results[0]; assert.equal(response.status, 200); done(); }) @@ -71,10 +73,10 @@ describe('axe.utils.xhrQ', function () { }); it('should populate response', function (done) { - const config = { + var config = { url: '/gotMe' }; - const fakeResponse = { + var fakeResponse = { status: 200, contentType: { 'Content-Type': 'text/json' }, dataJson: { @@ -85,7 +87,7 @@ describe('axe.utils.xhrQ', function () { axe.utils.xhrQ(config) .then(function (results) { - const response = results[0]; + var response = results[0]; assert.isObject(response); assert.deepEqual(JSON.parse(response.responseText), fakeResponse.dataJson); done(); From 46ae47c5a7643591634b0e8efcc9d014f16bdf7f Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 25 Jun 2018 22:18:05 +0100 Subject: [PATCH 18/43] fix: valid-lang integration tests. --- build/templates.js | 2 +- lib/checks/language/valid-lang.js | 15 +++++++++++++-- lib/core/base/check.js | 9 ++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/build/templates.js b/build/templates.js index ad6f2263e8..dffc44ada6 100644 --- a/build/templates.js +++ b/build/templates.js @@ -1,5 +1,5 @@ module.exports = { - evaluate: 'function (node, options, virtualNode, preloadedAssets) {\n<%=source%>\n}', + evaluate: 'function (node, options, virtualNode) {\n<%=source%>\n}', after: 'function (results, options) {\n<%=source%>\n}', gather: 'function (context) {\n<%=source%>\n}', matches: 'function (node, virtualNode) {\n<%=source%>\n}', diff --git a/lib/checks/language/valid-lang.js b/lib/checks/language/valid-lang.js index 0feacd25da..7d388fceed 100644 --- a/lib/checks/language/valid-lang.js +++ b/lib/checks/language/valid-lang.js @@ -1,11 +1,22 @@ -function getBaseLang (lang) { +function getBaseLang(lang) { return lang.trim().split('-')[0].toLowerCase(); } var langs, invalid; -langs = (options ? options : axe.commons.utils.validLangs()).map(getBaseLang); +/** + * + * Note from Jey: + * the below is a hack as options is an array in certain checks, and an object is other checks, + * TODO: + * need to come up with a more robust typing of args to prevent complex object mutation + */ +langs = ( + options && Array.isArray(options) + ? options + : axe.commons.utils.validLangs() +).map(getBaseLang); invalid = ['lang', 'xml:lang'].reduce(function (invalid, langAttr) { var langVal = node.getAttribute(langAttr); diff --git a/lib/core/base/check.js b/lib/core/base/check.js index a31c3f2266..76d94f6150 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -59,7 +59,7 @@ Check.prototype.enabled = true; * @param {Function} callback Function to fire when check is complete */ Check.prototype.run = function (node, options, resolve, reject) { - + options = options || {}; const enabled = options.hasOwnProperty('enabled') @@ -68,10 +68,6 @@ Check.prototype.run = function (node, options, resolve, reject) { const checkOptions = options.options || this.options; - const preloadedAssets = options.hasOwnProperty('preloadedAssets') - ? options.preloadedAssets - : undefined; - if (enabled) { var checkResult = new CheckResult(this); var checkHelper = axe.utils.checkHelper(checkResult, options, resolve, reject); @@ -81,8 +77,7 @@ Check.prototype.run = function (node, options, resolve, reject) { result = this.evaluate.call(checkHelper, node.actualNode, checkOptions, - node, - preloadedAssets + node ); } catch (e) { reject(e); From b03981b2f35bd0efc455fff0508744a41f42fafe Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 25 Jun 2018 22:37:04 +0100 Subject: [PATCH 19/43] docs: add preload configuration to api documentation --- doc/API.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/API.md b/doc/API.md index 93cd44053a..f7e47fb691 100644 --- a/doc/API.md +++ b/doc/API.md @@ -324,7 +324,7 @@ The options parameter is flexible way to configure how `axe.run` operates. The d Additionally, there are a number or properties that allow configuration of different options: | Property | Default | Description | -|-----------------|:-------:|:----------------------------:| +|-----------------|:-------|:----------------------------| | `runOnly` | n/a | Limit which rules are executed, based on names or tags | `rules` | n/a | Allow customizing a rule's properties (including { enable: false }) | `reporter` | `v1` | Which reporter to use (see [Configuration](#api-name-axeconfigure)) @@ -335,6 +335,8 @@ Additionally, there are a number or properties that allow configuration of diffe | `elementRef` | `false` | Return element references in addition to the target | `restoreScroll` | `false` | Scrolls elements back to before axe started | `frameWaitTime` | `60000` | How long (in milliseconds) axe waits for a response from embedded frames before timing out +| `preload` | `false` | Any additional assets (eg: cssom) to preload before running rules. Accepts a boolean (true/ false) or an object where an array of assets can be specified. Eg: `preload: true`, or `preload: false`, or `preload: { assets: ['cssom'], timeout: 50000 }`. The `timeout` attribute in the object configuration is `optional` and defaults to `30000` as defined in `axe.constants`, the `timeout` is essential for any network dependent assets that are preloaded. + ###### Options Parameter Examples From 0ba0ef9aef8797150077d252a2620bcb353da21d Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 25 Jun 2018 22:55:47 +0100 Subject: [PATCH 20/43] docs: fix markdown lint. --- doc/API.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/API.md b/doc/API.md index f7e47fb691..50f09544e2 100644 --- a/doc/API.md +++ b/doc/API.md @@ -335,8 +335,7 @@ Additionally, there are a number or properties that allow configuration of diffe | `elementRef` | `false` | Return element references in addition to the target | `restoreScroll` | `false` | Scrolls elements back to before axe started | `frameWaitTime` | `60000` | How long (in milliseconds) axe waits for a response from embedded frames before timing out -| `preload` | `false` | Any additional assets (eg: cssom) to preload before running rules. Accepts a boolean (true/ false) or an object where an array of assets can be specified. Eg: `preload: true`, or `preload: false`, or `preload: { assets: ['cssom'], timeout: 50000 }`. The `timeout` attribute in the object configuration is `optional` and defaults to `30000` as defined in `axe.constants`, the `timeout` is essential for any network dependent assets that are preloaded. - +| `preload` | `false` | Any additional assets (eg: cssom) to preload before running rules. Accepts a boolean (true/ false) or an object where an array of assets can be specified. Eg: `preload: true`, or `preload: false`, or `preload: { assets: ['cssom'], timeout: 50000 }`. The `timeout` attribute in the object configuration is `optional` and defaults to `30000` as defined in `axe.constants`, the `timeout` is essential for any network dependent assets that are preloaded. ###### Options Parameter Examples From ba00d6b8fde0df2fd4c991bbc52edcbf69db3528 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 26 Jun 2018 09:47:03 +0100 Subject: [PATCH 21/43] refactor: lint, test & docs updates. --- lib/checks/language/valid-lang.js | 3 ++- lib/core/base/audit.js | 29 ++++++++++++--------------- lib/core/base/check.js | 6 +----- lib/core/base/rule.js | 7 ------- test/core/public/run-rules.js | 3 --- test/core/public/run.js | 2 +- test/sandbox.html | 33 ------------------------------- 7 files changed, 17 insertions(+), 66 deletions(-) delete mode 100644 test/sandbox.html diff --git a/lib/checks/language/valid-lang.js b/lib/checks/language/valid-lang.js index 7d388fceed..3d7da69c96 100644 --- a/lib/checks/language/valid-lang.js +++ b/lib/checks/language/valid-lang.js @@ -8,7 +8,8 @@ var langs, invalid; /** * * Note from Jey: - * the below is a hack as options is an array in certain checks, and an object is other checks, + * the below is a hack as options is an array in certain checks, and an object is other checks. + * this was to get some integration/ full tests passing. * TODO: * need to come up with a more robust typing of args to prevent complex object mutation */ diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 1bc70eb6b9..77571ea180 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -165,8 +165,6 @@ Audit.prototype.run = function (context, options, resolve, reject) { axe._selectCache = []; - - // preload assets if necessary axe.utils.preload(options) .then(([preloadedAssets]) => { // q - retuns array -> destructure/ pluck the first item, is an key value map of preloadedAssets @@ -175,13 +173,12 @@ Audit.prototype.run = function (context, options, resolve, reject) { ...options, preloadedAssets }; - // deduce a queue of rules to execute, extended with preloadedAssets passed to checks - const q = axe.utils.queue(); + // construct a queue of rules to execute this.rules - .forEach((rule) => { + .reduce((out, rule) => { if (axe.utils.ruleShouldRun(rule, context, options)) { const marker = performanceMarker(rule.id, options.performanceTimer); - q.defer((resolve, reject) => { + out.defer((resolve, reject) => { rule.run( context, options, @@ -205,16 +202,16 @@ Audit.prototype.run = function (context, options, resolve, reject) { }); }); } - }); - - q.then((results) => { - axe._selectCache = undefined; // remove the cache - resolve( - results.filter((result) => { - return !!result; - }) - ); - }).catch(reject); + return out; + }, axe.utils.queue()) + .then((results) => { + axe._selectCache = undefined; // remove the cache + resolve( + results.filter((result) => { + return !!result; + }) + ); + }).catch(reject); }) .catch(reject); }; diff --git a/lib/core/base/check.js b/lib/core/base/check.js index 76d94f6150..94039888d0 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -74,11 +74,7 @@ Check.prototype.run = function (node, options, resolve, reject) { var result; try { - result = this.evaluate.call(checkHelper, - node.actualNode, - checkOptions, - node - ); + result = this.evaluate.call(checkHelper, node.actualNode, checkOptions, node); } catch (e) { reject(e); return; diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index bb5543409b..bd0db0c5fa 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -61,12 +61,6 @@ function Rule(spec, parentAudit) { */ this.tags = spec.tags || []; - /** - * Preload assets to be fetched for the rule - * @type {Array} - */ - this.preload = spec.preload || []; - if (spec.matches) { /** * Optional function to test if rule should be run against a node, overrides Rule#matches @@ -74,7 +68,6 @@ function Rule(spec, parentAudit) { */ this.matches = createExecutionContext(spec.matches); } - } /** diff --git a/test/core/public/run-rules.js b/test/core/public/run-rules.js index 84124fa504..15a2ad2511 100644 --- a/test/core/public/run-rules.js +++ b/test/core/public/run-rules.js @@ -220,9 +220,6 @@ describe('runRules', function () { }); it('should reject if the context is invalid', function (done) { - before(function() { - axe._selectorData = {}; - }); axe._load({ rules: [{ id: 'div#target', diff --git a/test/core/public/run.js b/test/core/public/run.js index 6d59a4c6f4..d2766cda61 100644 --- a/test/core/public/run.js +++ b/test/core/public/run.js @@ -447,8 +447,8 @@ describe('axe.run iframes', function () { }, 1000); axe.run('#fixture', {}, function (err, result) { + assert.isDefined(result); assert.equal(result.violations.length, 1); - var violation = result.violations[0]; assert.equal(violation.nodes.length, 2, 'one node for top frame, one for iframe'); diff --git a/test/sandbox.html b/test/sandbox.html deleted file mode 100644 index 8cfe3389a3..0000000000 --- a/test/sandbox.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - Sandbox::Temp - - - - - - - - - - - - - - - \ No newline at end of file From 2074cc91893a97b2029ec4600ddce074bbd3cf76 Mon Sep 17 00:00:00 2001 From: Jey Date: Sun, 1 Jul 2018 19:25:34 +0100 Subject: [PATCH 22/43] docs: update api documentation for preload configuration --- doc/API.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/doc/API.md b/doc/API.md index 50f09544e2..bbc6eb1e94 100644 --- a/doc/API.md +++ b/doc/API.md @@ -335,7 +335,7 @@ Additionally, there are a number or properties that allow configuration of diffe | `elementRef` | `false` | Return element references in addition to the target | `restoreScroll` | `false` | Scrolls elements back to before axe started | `frameWaitTime` | `60000` | How long (in milliseconds) axe waits for a response from embedded frames before timing out -| `preload` | `false` | Any additional assets (eg: cssom) to preload before running rules. Accepts a boolean (true/ false) or an object where an array of assets can be specified. Eg: `preload: true`, or `preload: false`, or `preload: { assets: ['cssom'], timeout: 50000 }`. The `timeout` attribute in the object configuration is `optional` and defaults to `30000` as defined in `axe.constants`, the `timeout` is essential for any network dependent assets that are preloaded. +| `preload` | `false` | Any additional assets (eg: cssom) to preload before running rules. [See here for configuration details](#preload-configuration-details) ###### Options Parameter Examples @@ -457,6 +457,27 @@ Additionally, there are a number or properties that allow configuration of diffe ``` This example will process all of the "violations", "incomplete", and "inapplicable" result types. Since "passes" was not specified, it will only process the first pass for each rule, if one exists. As a result, the results object's `passes` array will have a length of either `0` or `1`. On a series of extremely large pages, this would improve performance considerably. +###### Preload Configuration in Options Parameter + +The preload attribute in options parameter accepts a `boolean` or an `object` where an array of assets can be specified. + +1. Specifying a `boolean` + +```js +preload: true +``` + +2. Specifying an `object` +```js +preload: { + assets: ['cssom'], + timeout: 50000 +} +``` +The `assets` attribute expects an array of preload(able) constraints to be fetched. + +The `timeout` attribute in the object configuration is `optional` and has a fallback default value. The `timeout` is essential for any network dependent assets that are preloaded. + ##### Callback Parameter The callback parameter is a function that will be called when the asynchronous `axe.run` function completes. The callback function is passed two parameters. The first parameter will be an error thrown inside of aXe if axe.run could not complete. If axe completed correctly the first parameter will be null, and the second parameter will be the results object. From 9f57cdbfe9c2eebd8fff09fa8f87dfe32f396ea8 Mon Sep 17 00:00:00 2001 From: Jey Date: Sun, 1 Jul 2018 19:26:59 +0100 Subject: [PATCH 23/43] chore: remove merge redundant files --- lib/checks/language/valid-lang.js.orig | 51 -- lib/core/base/audit.js.orig | 486 ------------ lib/core/base/check.js.orig | 136 ---- lib/core/base/rule.js.orig | 394 ---------- lib/core/public/configure.js.orig | 60 -- lib/core/public/run-rules.js.orig | 90 --- lib/core/public/run.js.orig | 202 ----- lib/core/utils/get-check-option.js.orig | 56 -- test/core/base/audit.js.orig | 972 ------------------------ test/core/public/run.js.orig | 494 ------------ 10 files changed, 2941 deletions(-) delete mode 100644 lib/checks/language/valid-lang.js.orig delete mode 100644 lib/core/base/audit.js.orig delete mode 100644 lib/core/base/check.js.orig delete mode 100644 lib/core/base/rule.js.orig delete mode 100644 lib/core/public/configure.js.orig delete mode 100644 lib/core/public/run-rules.js.orig delete mode 100644 lib/core/public/run.js.orig delete mode 100644 lib/core/utils/get-check-option.js.orig delete mode 100644 test/core/base/audit.js.orig delete mode 100644 test/core/public/run.js.orig diff --git a/lib/checks/language/valid-lang.js.orig b/lib/checks/language/valid-lang.js.orig deleted file mode 100644 index 56de4455dd..0000000000 --- a/lib/checks/language/valid-lang.js.orig +++ /dev/null @@ -1,51 +0,0 @@ -<<<<<<< HEAD - -function getBaseLang(lang) { - return lang.trim().split('-')[0].toLowerCase(); -======= -function getBaseLang(lang) { - return lang - .trim() - .split('-')[0] - .toLowerCase(); ->>>>>>> develop -} - -var langs, invalid; - -/** - * - * Note from Jey: - * the below is a hack as options is an array in certain checks, and an object is other checks. - * this was to get some integration/ full tests passing. - * TODO: - * need to come up with a more robust typing of args to prevent complex object mutation - */ -langs = ( - options && Array.isArray(options) - ? options - : axe.commons.utils.validLangs() -).map(getBaseLang); - -invalid = ['lang', 'xml:lang'].reduce(function(invalid, langAttr) { - var langVal = node.getAttribute(langAttr); - if (typeof langVal !== 'string') { - return invalid; - } - - var baselangVal = getBaseLang(langVal); - - // Edge sets lang to an empty string when xml:lang is set - // so we need to ignore empty strings here - if (baselangVal !== '' && langs.indexOf(baselangVal) === -1) { - invalid.push(langAttr + '="' + node.getAttribute(langAttr) + '"'); - } - return invalid; -}, []); - -if (invalid.length) { - this.data(invalid); - return true; -} - -return false; diff --git a/lib/core/base/audit.js.orig b/lib/core/base/audit.js.orig deleted file mode 100644 index bcb5ffb80b..0000000000 --- a/lib/core/base/audit.js.orig +++ /dev/null @@ -1,486 +0,0 @@ -/*global Rule, Check, RuleResult, commons: true */ -/*eslint no-unused-vars: 0*/ -function getDefaultConfiguration(audit) { - 'use strict'; - var config; - if (audit) { - config = axe.utils.clone(audit); - // Commons are configured into axe like everything else, - // however because things go funky if we have multiple commons objects - // we're not using the copy of that. - config.commons = audit.commons; - } else { - config = {}; - } - - config.reporter = config.reporter || null; - config.rules = config.rules || []; - config.checks = config.checks || []; - config.data = Object.assign( - { - checks: {}, - rules: {} - }, - config.data - ); - - return config; -} - -function unpackToObject(collection, audit, method) { - 'use strict'; - - var i, l; - for (i = 0, l = collection.length; i < l; i++) { - audit[method](collection[i]); - } -} - -/** - * Constructor which holds configured rules and information about the document under test - */ -function Audit(audit) { - // defaults - this.brand = 'axe'; - this.application = 'axeAPI'; - this.tagExclude = ['experimental']; - - this.defaultConfig = audit; - this._init(); -} - -/** - * Initializes the rules and checks - */ -Audit.prototype._init = function() { - var audit = getDefaultConfiguration(this.defaultConfig); - - axe.commons = commons = audit.commons; - - this.reporter = audit.reporter; - this.commands = {}; - this.rules = []; - this.checks = {}; - - unpackToObject(audit.rules, this, 'addRule'); - unpackToObject(audit.checks, this, 'addCheck'); - - this.data = {}; - this.data.checks = (audit.data && audit.data.checks) || {}; - this.data.rules = (audit.data && audit.data.rules) || {}; - this.data.failureSummaries = - (audit.data && audit.data.failureSummaries) || {}; - this.data.incompleteFallbackMessage = - (audit.data && audit.data.incompleteFallbackMessage) || ''; - - this._constructHelpUrls(); // create default helpUrls -}; - -/** - * Adds a new command to the audit - */ -<<<<<<< HEAD -Audit.prototype.registerCommand = function (command) { -======= - -Audit.prototype.registerCommand = function(command) { ->>>>>>> develop - 'use strict'; - this.commands[command.id] = command.callback; -}; - -/** - * Adds a new rule to the Audit. If a rule with specified ID already exists, it will be overridden - * @param {Object} spec Rule specification object - */ -Audit.prototype.addRule = function(spec) { - 'use strict'; - - if (spec.metadata) { - this.data.rules[spec.id] = spec.metadata; - } - - let rule = this.getRule(spec.id); - if (rule) { - rule.configure(spec); - } else { - this.rules.push(new Rule(spec, this)); - } -}; - -/** - * Adds a new check to the Audit. If a Check with specified ID already exists, it will be - * reconfigured - * - * @param {Object} spec Check specification object - */ -Audit.prototype.addCheck = function(spec) { - /*eslint no-eval: 0 */ - 'use strict'; - let metadata = spec.metadata; - - if (typeof metadata === 'object') { - this.data.checks[spec.id] = metadata; - // Transform messages into functions: - if (typeof metadata.messages === 'object') { -<<<<<<< HEAD - Object - .keys(metadata.messages) - .filter((prop) => { - return (metadata.messages.hasOwnProperty(prop) && typeof metadata.messages[prop] === 'string') - }) - .forEach((prop) => { -======= - Object.keys(metadata.messages) - .filter( - prop => - metadata.messages.hasOwnProperty(prop) && - typeof metadata.messages[prop] === 'string' - ) - .forEach(prop => { ->>>>>>> develop - if (metadata.messages[prop].indexOf('function') === 0) { - metadata.messages[prop] = new Function( - 'return ' + metadata.messages[prop] + ';' - )(); - } - }); - } - } - - if (this.checks[spec.id]) { - this.checks[spec.id].configure(spec); - } else { - this.checks[spec.id] = new Check(spec); - } -}; - - -const performanceMarker = (id, shouldMark) => { - if (shouldMark) { - const marker = { - start: `mark_rule_start_${id}`, - end: `mark_rule_end_${id}` - } - axe.utils.performanceTimer.mark(marker.start); - return () => { - axe.utils.performanceTimer.mark(marker.end); - axe.utils.performanceTimer.measure(`rule_${id}`, marker.start, marker.end); - }; - } else { - return () => { }; - } -}; - - -/** - * Runs the Audit; which in turn should call `run` on each rule. - * @async - * @param {Context} context The scope definition/context for analysis (include/exclude) - * @param {Object} options Options object to pass into rules and/or disable rules or checks - * @param {Function} fn Callback function to fire when audit is complete - */ -Audit.prototype.run = function(context, options, resolve, reject) { - 'use strict'; - - this.normalizeOptions(options); - - axe._selectCache = []; -<<<<<<< HEAD - - // preload assets if necessary - axe.utils.preload(options) - .then(([preloadedAssets]) => { // q - retuns array -> destructure/ pluck the first item, is an key value map of preloadedAssets - // mutate options with preloadedAssets - options = { - ...options, - preloadedAssets - }; - // construct a queue of rules to execute - this.rules - .reduce((out, rule) => { - if (axe.utils.ruleShouldRun(rule, context, options)) { - const marker = performanceMarker(rule.id, options.performanceTimer); - out.defer((resolve, reject) => { - rule.run( - context, - options, - (result) => { - marker(); - resolve(result); - }, - (err) => { - if (!options.debug) { - const errResult = Object.assign(new RuleResult(rule), { - result: axe.constants.CANTTELL, - description: 'An error occured while running this rule', - message: err.message, - stack: err.stack, - error: err - }); - resolve(errResult); - } else { - reject(err); - } - }); - }); - } - return out; - }, axe.utils.queue()) - .then((results) => { - axe._selectCache = undefined; // remove the cache - resolve( - results.filter((result) => { - return !!result; - }) - ); - }).catch(reject); - }) - .catch(reject); -======= - var q = axe.utils.queue(); - this.rules.forEach(function(rule) { - if (axe.utils.ruleShouldRun(rule, context, options)) { - if (options.performanceTimer) { - var markEnd = 'mark_rule_end_' + rule.id; - var markStart = 'mark_rule_start_' + rule.id; - axe.utils.performanceTimer.mark(markStart); - } - q.defer(function(res, rej) { - rule.run( - context, - options, - function(out) { - if (options.performanceTimer) { - axe.utils.performanceTimer.mark(markEnd); - axe.utils.performanceTimer.measure( - 'rule_' + rule.id, - markStart, - markEnd - ); - } - res(out); - }, - function(err) { - if (!options.debug) { - var errResult = Object.assign(new RuleResult(rule), { - result: axe.constants.CANTTELL, - description: 'An error occured while running this rule', - message: err.message, - stack: err.stack, - error: err - }); - res(errResult); - } else { - rej(err); - } - } - ); - }); - } - }); - q.then(function(results) { - axe._selectCache = undefined; // remove the cache - resolve( - results.filter(function(result) { - return !!result; - }) - ); - }).catch(reject); ->>>>>>> develop -}; - - -/** - * Runs Rule `after` post processing functions - * @param {Array} results Array of RuleResults to postprocess - * @param {Mixed} options Options object to pass into rules and/or disable rules or checks - */ -Audit.prototype.after = function(results, options) { - 'use strict'; - - var rules = this.rules; - - return results.map(function(ruleResult) { - var rule = axe.utils.findBy(rules, 'id', ruleResult.id); - if (!rule) { - // If you see this, you're probably running the Mocha tests with the aXe extension installed - throw new Error( - 'Result for unknown rule. You may be running mismatch aXe-core versions' - ); - } - - return rule.after(ruleResult, options); - }); -}; - - -/** - * Get the rule with a given ID - * @param {string} - * @return {Rule} - */ -Audit.prototype.getRule = function(ruleId) { - return this.rules.find(rule => rule.id === ruleId); -}; - - -/** - * Ensure all rules that are expected to run exist - * @throws {Error} If any tag or rule specified in options is unknown - * @param {Object} options Options object - * @return {Object} Validated options object - */ -Audit.prototype.normalizeOptions = function(options) { - /* eslint max-statements: ["error", 22] */ - 'use strict'; - var audit = this; - - // Validate runOnly - if (typeof options.runOnly === 'object') { - if (Array.isArray(options.runOnly)) { - options.runOnly = { - type: 'tag', - values: options.runOnly - }; - } - const only = options.runOnly; - if (only.value && !only.values) { - only.values = only.value; - delete only.value; - } - - if (!Array.isArray(only.values) || only.values.length === 0) { - throw new Error('runOnly.values must be a non-empty array'); - } - - // Check if every value in options.runOnly is a known rule ID - if (['rule', 'rules'].includes(only.type)) { - only.type = 'rule'; - only.values.forEach(function(ruleId) { - if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); - } - }); - - // Validate 'tags' (e.g. anything not 'rule') - } else if (['tag', 'tags', undefined].includes(only.type)) { - only.type = 'tag'; - const unmatchedTags = audit.rules.reduce((unmatchedTags, rule) => { - return unmatchedTags.length - ? unmatchedTags.filter(tag => !rule.tags.includes(tag)) - : unmatchedTags; - }, only.values); - - if (unmatchedTags.length !== 0) { - throw new Error( - 'Could not find tags `' + unmatchedTags.join('`, `') + '`' - ); - } - } else { - throw new Error(`Unknown runOnly type '${only.type}'`); - } - } - - if (typeof options.rules === 'object') { -<<<<<<< HEAD - Object.keys(options.rules) - .forEach(function (ruleId) { - if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.rules'); - } - }); -======= - Object.keys(options.rules).forEach(function(ruleId) { - if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.rules'); - } - }); ->>>>>>> develop - } - - return options; -}; - - -/* - * Updates the default options and then applies them - * @param {Mixed} options Options object - */ -<<<<<<< HEAD -Audit.prototype.setBranding = function (branding) { -======= - -Audit.prototype.setBranding = function(branding) { ->>>>>>> develop - 'use strict'; - let previous = { - brand: this.brand, - application: this.application - }; - if ( - branding && - branding.hasOwnProperty('brand') && - branding.brand && - typeof branding.brand === 'string' - ) { - this.brand = branding.brand; - } - if ( - branding && - branding.hasOwnProperty('application') && - branding.application && - typeof branding.application === 'string' - ) { - this.application = branding.application; - } - this._constructHelpUrls(previous); -}; - - -/** - * For all the rules, create the helpUrl and add it to the data for that rule - */ -function getHelpUrl({ brand, application }, ruleId, version) { -<<<<<<< HEAD - return axe.constants.helpUrlBase + brand + - '/' + (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + - '/' + ruleId + '?application=' + application; -======= - return ( - axe.constants.helpUrlBase + - brand + - '/' + - (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + - '/' + - ruleId + - '?application=' + - application - ); ->>>>>>> develop -} - -Audit.prototype._constructHelpUrls = function(previous = null) { - var version = (axe.version.match(/^[1-9][0-9]*\.[0-9]+/) || ['x.y'])[0]; - this.rules.forEach(rule => { - if (!this.data.rules[rule.id]) { - this.data.rules[rule.id] = {}; - } - let metaData = this.data.rules[rule.id]; - if ( - typeof metaData.helpUrl !== 'string' || - (previous && metaData.helpUrl === getHelpUrl(previous, rule.id, version)) - ) { - metaData.helpUrl = getHelpUrl(this, rule.id, version); - } - }); -}; - -/** - * Reset the default rules, checks and meta data - */ - -Audit.prototype.resetRulesAndChecks = function() { - 'use strict'; - this._init(); -}; diff --git a/lib/core/base/check.js.orig b/lib/core/base/check.js.orig deleted file mode 100644 index a256582657..0000000000 --- a/lib/core/base/check.js.orig +++ /dev/null @@ -1,136 +0,0 @@ -/*global CheckResult */ - -function createExecutionContext(spec) { - /*eslint no-eval:0 */ - 'use strict'; - if (typeof spec === 'string') { - return new Function('return ' + spec + ';')(); - } - return spec; -} - -function Check(spec) { - if (spec) { - this.id = spec.id; - this.configure(spec); - } -} - -/** - * Unique ID for the check. Checks may be re-used, so there may be additional instances of checks - * with the same ID. - * @type {String} - */ -// Check.prototype.id; - -/** - * Free-form options that are passed as the second parameter to the `evaluate` - * @type {Mixed} - */ -// Check.prototype.options; - -/** - * The actual code, accepts 2 parameters: node (the node under test), options (see this.options). - * This function is run in the context of a checkHelper, which has the following methods - * - `async()` - if called, the check is considered to be asynchronous; returns a callback function - * - `data()` - free-form data object, associated to the `CheckResult` which is specific to each node - * @type {Function} - */ -// Check.prototype.evaluate; - -/** - * Optional. Filter and/or modify checks for all nodes - * @type {Function} - */ -// Check.prototype.after; - -/** - * enabled by default, if false, this check will not be included in the rule's evaluation - * @type {Boolean} - */ -Check.prototype.enabled = true; - -/** - * Run the check's evaluate function (call `this.evaluate(node, options)`) - * @param {HTMLElement} node The node to test - * @param {Object} options The options that override the defaults and provide additional - * information for the check - * @param {Function} callback Function to fire when check is complete - */ -<<<<<<< HEAD -Check.prototype.run = function (node, options, resolve, reject) { - - options = options || {}; - - const enabled = options.hasOwnProperty('enabled') - ? options.enabled - : this.enabled; - - const checkOptions = options.options || this.options; -======= -Check.prototype.run = function(node, options, resolve, reject) { - 'use strict'; - options = options || {}; - var enabled = options.hasOwnProperty('enabled') - ? options.enabled - : this.enabled, - checkOptions = options.options || this.options; ->>>>>>> develop - - if (enabled) { - var checkResult = new CheckResult(this); - var checkHelper = axe.utils.checkHelper( - checkResult, - options, - resolve, - reject - ); - var result; - - try { - result = this.evaluate.call( - checkHelper, - node.actualNode, - checkOptions, - node - ); - } catch (e) { - reject(e); - return; - } - - if (!checkHelper.isAsync) { - checkResult.result = result; - setTimeout(function() { - resolve(checkResult); - }, 0); - } - } else { - resolve(null); - } -}; - -/** - * Override a check's settings after construction to allow for changing options - * without having to implement the entire check - * - * @param {Object} spec - the specification of the attributes to be changed - */ - -Check.prototype.configure = function(spec) { - ['options', 'enabled'] - .filter(prop => spec.hasOwnProperty(prop)) -<<<<<<< HEAD - .forEach(prop => this[prop] = spec[prop]); - - ['evaluate', 'after'] - .filter(prop => spec.hasOwnProperty(prop)) - .forEach(prop => this[prop] = createExecutionContext(spec[prop])); -======= - .forEach(prop => (this[prop] = spec[prop])); - - ['evaluate', 'after'] - .filter(prop => spec.hasOwnProperty(prop)) - .forEach(prop => (this[prop] = createExecutionContext(spec[prop]))); ->>>>>>> develop -}; diff --git a/lib/core/base/rule.js.orig b/lib/core/base/rule.js.orig deleted file mode 100644 index c493c5cc8f..0000000000 --- a/lib/core/base/rule.js.orig +++ /dev/null @@ -1,394 +0,0 @@ -/*global RuleResult, createExecutionContext, SupportError */ - -function Rule(spec, parentAudit) { - /*eslint complexity: ["error", 11] */ - 'use strict'; - - this._audit = parentAudit; - - /** - * The code, or string ID of the rule - * @type {String} - */ - this.id = spec.id; - - /** - * Selector that this rule applies to - * @type {String} - */ - this.selector = spec.selector || '*'; - - /** - * Whether to exclude hiddden elements form analysis. Defaults to true. - * @type {Boolean} - */ - this.excludeHidden = - typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; - - /** - * Flag to enable or disable rule - * @type {Boolean} - */ - this.enabled = typeof spec.enabled === 'boolean' ? spec.enabled : true; - - /** - * Denotes if the rule should be run if Context is not an entire page AND whether - * the Rule should be satisified regardless of Node - * @type {Boolean} - */ - this.pageLevel = typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; - - /** - * Checks that any may return true to satisfy rule - * @type {Array} - */ - this.any = spec.any || []; - - /** - * Checks that must all return true to satisfy rule - * @type {Array} - */ - this.all = spec.all || []; - - /** - * Checks that none may return true to satisfy rule - * @type {Array} - */ - this.none = spec.none || []; - - /** - * Tags associated to this rule - * @type {Array} - */ - this.tags = spec.tags || []; - - if (spec.matches) { - /** - * Optional function to test if rule should be run against a node, overrides Rule#matches - * @type {Function} - */ - this.matches = createExecutionContext(spec.matches); - } -} - -/** - * Optionally test each node against a `matches` function to determine if the rule should run against - * a given node. Defaults to `true`. - * @return {Boolean} Whether the rule should run - */ -Rule.prototype.matches = function() { - 'use strict'; - - return true; -}; - -/** - * Selects `HTMLElement`s based on configured selector - * @param {Context} context The resolved Context object - * @return {Array} All matching `HTMLElement`s - */ -Rule.prototype.gather = function(context) { - 'use strict'; - var elements = axe.utils.select(this.selector, context); - if (this.excludeHidden) { - return elements.filter(function(element) { - return !axe.utils.isHidden(element.actualNode); - }); - } - return elements; -}; - -<<<<<<< HEAD -Rule.prototype.runChecks = function (type, node, options, resolve, reject) { - const self = this; - const checkQueue = axe.utils.queue(); - - this[type] - .forEach((c) => { - const check = self._audit.checks[c.id || c]; - let option = axe.utils.getCheckOption(check, self.id, options); - checkQueue.defer(function (res, rej) { - check.run(node, option, res, rej); - }); -======= -Rule.prototype.runChecks = function(type, node, options, resolve, reject) { - 'use strict'; - - var self = this; - var checkQueue = axe.utils.queue(); - this[type].forEach(function(c) { - var check = self._audit.checks[c.id || c]; - var option = axe.utils.getCheckOption(check, self.id, options); - checkQueue.defer(function(res, rej) { - check.run(node, option, res, rej); ->>>>>>> develop - }); - - checkQueue -<<<<<<< HEAD - .then(function (results) { - results = results.filter(function (check) { -======= - .then(function(results) { - results = results.filter(function(check) { ->>>>>>> develop - return check; - }); - resolve({ type: type, results: results }); - }) - .catch(reject); -<<<<<<< HEAD - -======= ->>>>>>> develop -}; - -/** - * Runs the Rule's `evaluate` function - * @param {Context} context The resolved Context object - * @param {Mixed} options Options specific to this rule - * @param {Function} callback Function to call when evaluate is complete; receives a RuleResult instance - */ -Rule.prototype.run = function(context, options, resolve, reject) { - /*eslint max-statements: ["error",17] */ - const q = axe.utils.queue(); - const ruleResult = new RuleResult(this); - const markStart = 'mark_runchecks_start_' + this.id; - const markEnd = 'mark_runchecks_end_' + this.id; - let nodes; - - try { - // Matches throws an error when it lacks support for document methods - nodes = this.gather(context).filter(node => - this.matches(node.actualNode, node) - ); - } catch (error) { - // Exit the rule execution if matches fails - reject(new SupportError({ cause: error, ruleId: this.id })); - return; - } - - if (options.performanceTimer) { -<<<<<<< HEAD - axe.log('gather (', nodes.length, '):', axe.utils.performanceTimer.timeElapsed() + 'ms'); -======= - axe.log( - 'gather (', - nodes.length, - '):', - axe.utils.performanceTimer.timeElapsed() + 'ms' - ); ->>>>>>> develop - axe.utils.performanceTimer.mark(markStart); - } - - nodes.forEach(node => { - q.defer((resolveNode, rejectNode) => { - var checkQueue = axe.utils.queue(); - -<<<<<<< HEAD - ['any', 'all', 'none'] - .forEach((type) => { - checkQueue.defer((res, rej) => { - this.runChecks(type, node, options, res, rej); - }); - }); - - checkQueue - .then(function (results) { - if (results.length) { - var hasResults = false, result = {}; - results.forEach(function (r) { - var res = r.results.filter(function (result) { -======= - checkQueue - .then(function(results) { - if (results.length) { - var hasResults = false, - result = {}; - results.forEach(function(r) { - var res = r.results.filter(function(result) { ->>>>>>> develop - return result; - }); - result[r.type] = res; - if (res.length) { - hasResults = true; - } - }); - if (hasResults) { - result.node = new axe.utils.DqElement(node.actualNode, options); - ruleResult.nodes.push(result); - } - } - resolveNode(); - }) - .catch(err => rejectNode(err)); - }); - }); - - if (options.performanceTimer) { - axe.utils.performanceTimer.mark(markEnd); - axe.utils.performanceTimer.measure( - 'runchecks_' + this.id, - markStart, - markEnd - ); - } - - q.then(() => resolve(ruleResult)).catch(error => reject(error)); -}; - -/** - * Iterates the rule's Checks looking for ones that have an after function - * @private - * @param {Rule} rule The rule to check for after checks - * @return {Array} Checks that have an after function - */ -function findAfterChecks(rule) { - 'use strict'; - - return axe.utils - .getAllChecks(rule) - .map(function(c) { - var check = rule._audit.checks[c.id || c]; - return check && typeof check.after === 'function' ? check : null; - }) - .filter(Boolean); -} - -/** - * Finds and collates all results for a given Check on a specific Rule - * @private - * @param {Array} nodes RuleResult#nodes; array of 'detail' objects - * @param {String} checkID The ID of the Check to find - * @return {Array} Matching CheckResults - */ -function findCheckResults(nodes, checkID) { - 'use strict'; - - var checkResults = []; - nodes.forEach(function(nodeResult) { - var checks = axe.utils.getAllChecks(nodeResult); - checks.forEach(function(checkResult) { - if (checkResult.id === checkID) { - checkResults.push(checkResult); - } - }); - }); - return checkResults; -} - -function filterChecks(checks) { - 'use strict'; - - return checks.filter(function(check) { - return check.filtered !== true; - }); -} - -function sanitizeNodes(result) { - 'use strict'; - var checkTypes = ['any', 'all', 'none']; - - var nodes = result.nodes.filter(function(detail) { - var length = 0; - checkTypes.forEach(function(type) { - detail[type] = filterChecks(detail[type]); - length += detail[type].length; - }); - return length > 0; - }); - - if (result.pageLevel && nodes.length) { - nodes = [ - nodes.reduce(function(a, b) { - if (a) { - checkTypes.forEach(function(type) { - a[type].push.apply(a[type], b[type]); - }); - return a; - } - }) - ]; - } - return nodes; -} - -/** - * Runs all of the Rule's Check#after methods - * @param {RuleResult} result The "pre-after" RuleResult - * @param {Mixed} options Options specific to the rule - * @return {RuleResult} The RuleResult as filtered by after functions - */ -Rule.prototype.after = function(result, options) { - 'use strict'; - - var afterChecks = findAfterChecks(this); - var ruleID = this.id; - afterChecks.forEach(function(check) { - var beforeResults = findCheckResults(result.nodes, check.id); - var option = axe.utils.getCheckOption(check, ruleID, options); - - var afterResults = check.after(beforeResults, option); - beforeResults.forEach(function(item) { - if (afterResults.indexOf(item) === -1) { - item.filtered = true; - } - }); - }); - - result.nodes = sanitizeNodes(result); - return result; -}; - -/** - * Reconfigure a rule after it has been added - * @param {Object} spec - the attributes to be reconfigured - */ -Rule.prototype.configure = function(spec) { - /*eslint complexity:["error",14], max-statements:["error",22], no-eval:0 */ - 'use strict'; - - if (spec.hasOwnProperty('selector')) { - this.selector = spec.selector; - } - - if (spec.hasOwnProperty('excludeHidden')) { - this.excludeHidden = - typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; - } - - if (spec.hasOwnProperty('enabled')) { - this.enabled = typeof spec.enabled === 'boolean' ? spec.enabled : true; - } - - if (spec.hasOwnProperty('pageLevel')) { - this.pageLevel = - typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; - } - - if (spec.hasOwnProperty('any')) { - this.any = spec.any; - } - - if (spec.hasOwnProperty('all')) { - this.all = spec.all; - } - - if (spec.hasOwnProperty('none')) { - this.none = spec.none; - } - - if (spec.hasOwnProperty('tags')) { - this.tags = spec.tags; - } - - if (spec.hasOwnProperty('matches')) { - if (typeof spec.matches === 'string') { - this.matches = new Function('return ' + spec.matches + ';')(); - } else { - this.matches = spec.matches; - } - } -}; diff --git a/lib/core/public/configure.js.orig b/lib/core/public/configure.js.orig deleted file mode 100644 index 841cf46352..0000000000 --- a/lib/core/public/configure.js.orig +++ /dev/null @@ -1,60 +0,0 @@ -/* global reporters */ -function configureChecksRulesAndBranding(spec) { -<<<<<<< HEAD - /*eslint - max-statements: ["error",30] - */ -======= - /*eslint max-statements: ["error",20]*/ ->>>>>>> develop - 'use strict'; - var audit; - - audit = axe._audit; - - if (!audit) { - throw new Error('No audit configured'); - } - - if ( - spec.reporter && - (typeof spec.reporter === 'function' || reporters[spec.reporter]) - ) { - audit.reporter = spec.reporter; - } - - if (spec.checks) { - spec.checks.forEach(function(check) { - audit.addCheck(check); - }); - } - - const modifiedRules = []; - if (spec.rules) { - spec.rules.forEach(function(rule) { - modifiedRules.push(rule.id); - audit.addRule(rule); - }); - } - - if (spec.disableOtherRules) { - audit.rules.forEach(rule => { - if (modifiedRules.includes(rule.id) === false) { - rule.enabled = false; - } - }); - } - - if (typeof spec.branding !== 'undefined') { - audit.setBranding(spec.branding); - } else { - audit._constructHelpUrls(); - } - - if (spec.tagExclude) { - audit.tagExclude = spec.tagExclude; - } - -} - -axe.configure = configureChecksRulesAndBranding; diff --git a/lib/core/public/run-rules.js.orig b/lib/core/public/run-rules.js.orig deleted file mode 100644 index 61323ffdd2..0000000000 --- a/lib/core/public/run-rules.js.orig +++ /dev/null @@ -1,90 +0,0 @@ -/*global Context */ -/*exported runRules */ - -// Clean up after resolve / reject -function cleanup () { - axe._tree = undefined; - axe._selectorData = undefined; -} - -/** - * Starts analysis on the current document and its subframes - * @private - * @param {Object} context The `Context` specification object @see Context - * @param {Array} options Optional RuleOptions - * @param {Function} resolve Called when done running rules, receives ([results : Object], cleanup : Function) - * @param {Function} reject Called when execution failed, receives (err : Error) - */ -function runRules(context, options, resolve, reject) { - 'use strict'; - - try { - context = new Context(context); - axe._tree = context.flatTree; - axe._selectorData = axe.utils.getSelectorData(context.flatTree); - } catch (e) { - cleanup(); - return reject(e); - } - - var q = axe.utils.queue(); - var audit = axe._audit; - - if (options.performanceTimer) { - axe.utils.performanceTimer.auditStart(); - } - - if (context.frames.length && options.iframes !== false) { - q.defer(function (res, rej) { - axe.utils.collectResultsFromFrames(context, options, 'rules', null, res, rej); - }); - } - - let scrollState; - - q.defer(function (res, rej) { - if (options.restoreScroll) { - scrollState = axe.utils.getScrollState(); - } - audit.run(context, options, res, rej); - }); - - q.then(function (data) { - try { - if (scrollState) { - axe.utils.setScrollState(scrollState); - } - if (options.performanceTimer) { - axe.utils.performanceTimer.auditEnd(); - } - - // Add wrapper object so that we may use the same "merge" function for results from inside and outside frames - var results = axe.utils.mergeResults(data.map(function (results) { - return { results }; - })); - - // after should only run once, so ensure we are in the top level window - if (context.initiator) { - results = audit.after(results, options); - - results.forEach(axe.utils.publishMetaData); - results = results.map(axe.utils.finalizeRuleResult); - } - - try { - resolve(results, cleanup); - } catch(e) { - cleanup(); - axe.log(e); - } - } catch (e) { - cleanup(); - reject(e); - } - }).catch((e) => { - cleanup(); - reject(e); - }); -} - -axe._runRules = runRules; diff --git a/lib/core/public/run.js.orig b/lib/core/public/run.js.orig deleted file mode 100644 index 0dbdec4b3f..0000000000 --- a/lib/core/public/run.js.orig +++ /dev/null @@ -1,202 +0,0 @@ -/* global Promise */ -/*eslint -indent: 0, -complexity:["error", 12] -*/ -function isContext(potential) { - 'use strict'; - - switch (true) { - case typeof potential === 'string': - case Array.isArray(potential): - case Node && potential instanceof Node: - case NodeList && potential instanceof NodeList: - return true; - - case typeof potential !== 'object': - return false; - - case potential.include !== undefined: - case potential.exclude !== undefined: - case typeof potential.length === 'number': - return true; - - default: - return false; - } -} - -<<<<<<< HEAD -var noop = function () { }; -======= -var noop = function() {}; ->>>>>>> develop - -/** - * Normalize the optional params of axe.run() - * @param {object} context - * @param {object} options - * @param {Function} callback - * @return {object} With 3 keys: context, options, callback - */ -function normalizeRunParams(context, options, callback) { - 'use strict'; - - const typeErr = new TypeError('axe.run arguments are invalid'); - - // Determine the context - if (!isContext(context)) { - if (callback !== undefined) { - // Either context is invalid or there are too many params - throw typeErr; - } - // Set default and shift one over - callback = options; - options = context; - context = document; - } - - // Determine the options - if (typeof options !== 'object') { - if (callback !== undefined) { - // Either options is invalid or there are too many params - throw typeErr; - } - // Set default and shift one over - callback = options; - options = {}; - } - - // Set the callback or noop; - if (typeof callback !== 'function' && callback !== undefined) { - throw typeErr; - } - - return { - context: context, - options: options, - callback: callback || noop - }; -} - -/** - * Runs a number of rules against the provided HTML page and returns the - * resulting issue list - * - * @param {Object} context (optional) Defines the scope of the analysis - * @param {Object} options (optional) Set of options passed into rules or checks - * @param {Function} callback (optional) The callback when axe is done, given 2 params: - * - Error If any errors occured, otherwise null - * - Results The results object / array, or undefined on error - * @return {Promise} Resolves with the axe results. Only available when natively supported - */ -<<<<<<< HEAD -axe.run = function (context, options, callback) { - /*eslint - max-statements:["error",25] - */ -======= -axe.run = function(context, options, callback) { - /*eslint max-statements:["error",18] */ ->>>>>>> develop - 'use strict'; - - if (!axe._audit) { - throw new Error('No audit configured'); - } - - let args = normalizeRunParams(context, options, callback); - context = args.context; - options = args.options; - callback = args.callback; - - // set defaults: - options.reporter = options.reporter || axe._audit.reporter || 'v1'; - - if (options.performanceTimer) { - axe.utils.performanceTimer.start(); - } - - let p; - let reject = noop; - let resolve = noop; - - if (typeof Promise === 'function' && callback === noop) { - p = new Promise(function(_resolve, _reject) { - reject = _reject; - resolve = _resolve; - }); - } - -<<<<<<< HEAD - axe._runRules(context, options, function (rawResults, cleanup) { - let respond = function (results) { - cleanup(); - try { - callback(null, results); - } catch (e) { - axe.log(e); - } - resolve(results); - }; - if (options.performanceTimer) { - axe.utils.performanceTimer.end(); - } - - try { - let reporter = axe.getReporter(options.reporter); - let results = reporter(rawResults, options, respond); - if (results !== undefined) { - respond(results); - } - } catch (err) { - cleanup(); - callback(err); - reject(err); - } - }, function (err) { - callback(err); - reject(err); - }); - - return p; -}; -======= - axe._runRules( - context, - options, - function(rawResults, cleanup) { - let respond = function(results) { - cleanup(); - try { - callback(null, results); - } catch (e) { - axe.log(e); - } - resolve(results); - }; - if (options.performanceTimer) { - axe.utils.performanceTimer.end(); - } - - try { - let reporter = axe.getReporter(options.reporter); - let results = reporter(rawResults, options, respond); - if (results !== undefined) { - respond(results); - } - } catch (err) { - cleanup(); - callback(err); - reject(err); - } - }, - function(err) { - callback(err); - reject(err); - } - ); - - return p; -}; ->>>>>>> develop diff --git a/lib/core/utils/get-check-option.js.orig b/lib/core/utils/get-check-option.js.orig deleted file mode 100644 index 27bbee6a59..0000000000 --- a/lib/core/utils/get-check-option.js.orig +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint max-statements: ["error", 25], complexity: ["error", 15] */ - -/** - * Determines which CheckOption to use, either defined on the rule options, global check options or the check itself - * @param {Check} check The Check object - * @param {String} ruleID The ID of the rule - * @param {Object} options Options object as passed to main API - * @return {Object} The resolved object with `options` and `enabled` keys - */ -<<<<<<< HEAD -axe.utils.getCheckOption = function (check, ruleID, options) { -======= -axe.utils.getCheckOption = function(check, ruleID, options) { - var ruleCheckOption = (((options.rules && options.rules[ruleID]) || {}) - .checks || {})[check.id]; - var checkOption = (options.checks || {})[check.id]; ->>>>>>> develop - - const ruleCheckOption = ((options.rules && options.rules[ruleID] || {}).checks || {})[check.id]; - const checkOption = (options.checks || {})[check.id]; - const preloadConfig = axe.utils.preloadConfig(options); - - let enabled = check.enabled; - let opts = check.options || {}; - - if (checkOption) { - if (checkOption.hasOwnProperty('enabled')) { - enabled = checkOption.enabled; - } - if (checkOption.hasOwnProperty('options')) { - opts = checkOption.options; - } - } - - if (ruleCheckOption) { - if (ruleCheckOption.hasOwnProperty('enabled')) { - enabled = ruleCheckOption.enabled; - } - if (ruleCheckOption.hasOwnProperty('options')) { - opts = ruleCheckOption.options; - } - } - - if (preloadConfig.preload) { - opts = { - ...opts, - preloadedAssets: options.preloadedAssets - } - } - - return { - enabled: enabled, - options: opts, - absolutePaths: options.absolutePaths - }; -}; diff --git a/test/core/base/audit.js.orig b/test/core/base/audit.js.orig deleted file mode 100644 index 69f72b296e..0000000000 --- a/test/core/base/audit.js.orig +++ /dev/null @@ -1,972 +0,0 @@ -/*global Audit, Rule */ -describe('Audit', function() { - 'use strict'; - var a, getFlattenedTree; - var isNotCalled = function(err) { - throw err || new Error('Reject should not be called'); - }; -<<<<<<< HEAD - var noop = function () { }; -======= - var noop = function() {}; ->>>>>>> develop - - var mockChecks = [ - { - id: 'positive1-check1', - evaluate: function() { - return true; - } - }, - { - id: 'positive2-check1', - evaluate: function() { - return true; - } - }, - { - id: 'negative1-check1', - evaluate: function() { - return true; - } - }, - { - id: 'positive3-check1', - evaluate: function() { - return true; - } - } - ]; - - var mockRules = [ - { - id: 'positive1', - selector: 'input', - tags: ['positive'], - any: [ - { - id: 'positive1-check1' - } - ] - }, - { - id: 'positive2', - selector: '#monkeys', - tags: ['positive'], - any: ['positive2-check1'] - }, - { - id: 'negative1', - selector: 'div', - tags: ['negative'], - none: ['negative1-check1'] - }, - { - id: 'positive3', - selector: 'blink', - tags: ['positive'], - any: ['positive3-check1'] - } - ]; - - var fixture = document.getElementById('fixture'); - - beforeEach(function() { - a = new Audit(); - mockRules.forEach(function(r) { - a.addRule(r); - }); - mockChecks.forEach(function(c) { - a.addCheck(c); - }); - getFlattenedTree = axe.utils.getFlattenedTree; - }); - afterEach(function() { - fixture.innerHTML = ''; - axe._tree = undefined; - axe._selectCache = undefined; - axe.utils.getFlattenedTree = getFlattenedTree; - }); - - it('should be a function', function() { - assert.isFunction(Audit); - }); - - describe('Audit#_constructHelpUrls', function() { - it('should create default help URLS', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.data.rules.target, undefined); - audit._constructHelpUrls(); - assert.deepEqual(audit.data.rules.target, { - helpUrl: - 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' - }); - }); - it('should use changed branding', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.data.rules.target, undefined); - audit.brand = 'thing'; - audit._constructHelpUrls(); - assert.deepEqual(audit.data.rules.target, { - helpUrl: - 'https://dequeuniversity.com/rules/thing/x.y/target?application=axeAPI' - }); - }); - it('should use changed application', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.data.rules.target, undefined); - audit.application = 'thing'; - audit._constructHelpUrls(); - assert.deepEqual(audit.data.rules.target, { - helpUrl: - 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' - }); - }); - - it('does not override helpUrls of different products', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target1', - matches: 'function () {return "hello";}', - selector: 'bob', - metadata: { - helpUrl: - 'https://dequeuniversity.com/rules/myproject/x.y/target1?application=axeAPI' - } - }); - audit.addRule({ - id: 'target2', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - - assert.equal( - audit.data.rules.target1.helpUrl, - 'https://dequeuniversity.com/rules/myproject/x.y/target1?application=axeAPI' - ); - assert.isUndefined(audit.data.rules.target2); - - assert.lengthOf(audit.rules, 2); - audit.brand = 'thing'; - audit._constructHelpUrls(); - - assert.equal( - audit.data.rules.target1.helpUrl, - 'https://dequeuniversity.com/rules/myproject/x.y/target1?application=axeAPI' - ); - assert.equal( - audit.data.rules.target2.helpUrl, - 'https://dequeuniversity.com/rules/thing/x.y/target2?application=axeAPI' - ); - }); - it('understands prerelease type version numbers', function() { - var tempVersion = axe.version; - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - - axe.version = '3.2.1-alpha.0'; - audit._constructHelpUrls(); - - axe.version = tempVersion; - assert.equal( - audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/3.2/target?application=axeAPI' - ); - }); - it('sets x.y as version for invalid versions', function() { - var tempVersion = axe.version; - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - - axe.version = 'in-3.0-valid'; - audit._constructHelpUrls(); - - axe.version = tempVersion; - assert.equal( - audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' - ); - }); - it('matches major release versions', function() { - var tempVersion = axe.version; - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - - axe.version = '1.0.0'; - audit._constructHelpUrls(); - - axe.version = tempVersion; - assert.equal( - audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/1.0/target?application=axeAPI' - ); - }); - }); - - describe('Audit#setBranding', function() { - it('should change the brand', function() { - var audit = new Audit(); - assert.equal(audit.brand, 'axe'); - assert.equal(audit.application, 'axeAPI'); - audit.setBranding({ - brand: 'thing' - }); - assert.equal(audit.brand, 'thing'); - assert.equal(audit.application, 'axeAPI'); - }); - it('should change the application', function() { - var audit = new Audit(); - assert.equal(audit.brand, 'axe'); - assert.equal(audit.application, 'axeAPI'); - audit.setBranding({ - application: 'thing' - }); - assert.equal(audit.brand, 'axe'); - assert.equal(audit.application, 'thing'); - }); - it('should call _constructHelpUrls', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.data.rules.target, undefined); - audit.setBranding({ - application: 'thing' - }); - assert.deepEqual(audit.data.rules.target, { - helpUrl: - 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' - }); - }); - it('should call _constructHelpUrls even when nothing changed', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.data.rules.target, undefined); - audit.setBranding(undefined); - assert.deepEqual(audit.data.rules.target, { - helpUrl: - 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' - }); - }); - it('should not replace custom set branding', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob', - metadata: { - helpUrl: - 'https://dequeuniversity.com/rules/customer-x/x.y/target?application=axeAPI' - } - }); - audit.setBranding({ - application: 'thing', - brand: 'other' - }); - assert.equal( - audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/customer-x/x.y/target?application=axeAPI' - ); - }); - }); - - describe('Audit#addRule', function() { - it('should override existing rule', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - matches: 'function () {return "hello";}', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.rules[0].selector, 'bob'); - assert.equal(audit.rules[0].matches(), 'hello'); - - audit.addRule({ - id: 'target', - selector: 'fred' - }); - - assert.lengthOf(audit.rules, 1); - assert.equal(audit.rules[0].selector, 'fred'); - assert.equal(audit.rules[0].matches(), 'hello'); - }); - it('should otherwise push new rule', function() { - var audit = new Audit(); - audit.addRule({ - id: 'target', - selector: 'bob' - }); - assert.lengthOf(audit.rules, 1); - assert.equal(audit.rules[0].id, 'target'); - assert.equal(audit.rules[0].selector, 'bob'); - - audit.addRule({ - id: 'target2', - selector: 'fred' - }); - - assert.lengthOf(audit.rules, 2); - assert.equal(audit.rules[1].id, 'target2'); - assert.equal(audit.rules[1].selector, 'fred'); - }); - }); - - describe('Audit#resetRulesAndChecks', function() { - it('should override newly created check', function() { - var audit = new Audit(); - assert.equal(audit.checks.target, undefined); - audit.addCheck({ - id: 'target', - options: 'jane' - }); - assert.ok(audit.checks.target); - assert.equal(audit.checks.target.options, 'jane'); - audit.resetRulesAndChecks(); - assert.equal(audit.checks.target, undefined); - }); - }); - - describe('Audit#addCheck', function() { - it('should create a new check', function() { - var audit = new Audit(); - assert.equal(audit.checks.target, undefined); - audit.addCheck({ - id: 'target', - options: 'jane' - }); - assert.ok(audit.checks.target); - assert.equal(audit.checks.target.options, 'jane'); - }); - it('should configure the metadata, if passed', function() { - var audit = new Audit(); - assert.equal(audit.checks.target, undefined); - audit.addCheck({ - id: 'target', - metadata: { guy: 'bob' } - }); - assert.ok(audit.checks.target); - assert.equal(audit.data.checks.target.guy, 'bob'); - }); - it('should reconfigure existing check', function() { - var audit = new Audit(); -<<<<<<< HEAD - var myTest = function () { }; -======= - var myTest = function() {}; ->>>>>>> develop - audit.addCheck({ - id: 'target', - evaluate: myTest, - options: 'jane' - }); - - assert.equal(audit.checks.target.options, 'jane'); - - audit.addCheck({ - id: 'target', - options: 'fred' - }); - - assert.equal(audit.checks.target.evaluate, myTest); - assert.equal(audit.checks.target.options, 'fred'); - }); - it('should not turn messages into a function', function() { - var audit = new Audit(); - var spec = { - id: 'target', - evaluate: 'function () { return "blah";}', - metadata: { - messages: { - fail: 'it failed' - } - } - }; - audit.addCheck(spec); - - assert.equal(typeof audit.checks.target.evaluate, 'function'); - assert.equal(typeof audit.data.checks.target.messages.fail, 'string'); - assert.equal(audit.data.checks.target.messages.fail, 'it failed'); - }); - - it('should turn function strings into a function', function() { - var audit = new Audit(); - var spec = { - id: 'target', - evaluate: 'function () { return "blah";}', - metadata: { - messages: { - fail: 'function () {return "it failed";}' - } - } - }; - audit.addCheck(spec); - - assert.equal(typeof audit.checks.target.evaluate, 'function'); - assert.equal(typeof audit.data.checks.target.messages.fail, 'function'); - assert.equal(audit.data.checks.target.messages.fail(), 'it failed'); - }); - }); - - describe('Audit#run', function() { - it('should run all the rules', function(done) { - fixture.innerHTML = - '' + - '
bananas
' + - '' + - 'FAIL ME'; - - a.run( - { include: [axe.utils.getFlattenedTree(fixture)[0]] }, - {}, - function(results) { - var expected = [ - { - id: 'positive1', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, - { - id: 'positive2', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, - { - id: 'negative1', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, - { - id: 'positive3', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - } - ]; - - var out = results[0].nodes[0].node.source; - results.forEach(function(res) { - // attribute order is a pain in the lower back in IE, so we're not - // comparing nodes. Check.run and Rule.run do this. - res.nodes = '...other tests cover this...'; - }); - - assert.deepEqual(JSON.parse(JSON.stringify(results)), expected); - assert.match( - out, - /^/ - ); - done(); - }, - isNotCalled - ); - }); - it('should not run rules disabled by the options', function(done) { - a.run( - { include: [document] }, - { - rules: { - positive3: { - enabled: false - } - } - }, - function(results) { - assert.equal(results.length, 3); - done(); - }, - isNotCalled - ); - }); - it('should assign an empty array to axe._selectCache', function(done) { - var saved = axe.utils.ruleShouldRun; - axe.utils.ruleShouldRun = function() { - assert.equal(axe._selectCache.length, 0); - return false; - }; - a.run( - { include: [document] }, - {}, - function() { - axe.utils.ruleShouldRun = saved; - done(); - }, - isNotCalled - ); - }); - it('should clear axe._selectCache', function(done) { - a.run( - { include: [document] }, - { - rules: {} - }, - function() { - assert.isTrue(typeof axe._selectCache === 'undefined'); - done(); - }, - isNotCalled - ); - }); - it('should not run rules disabled by the configuration', function(done) { - var a = new Audit(); - var success = true; - a.rules.push( - new Rule({ - id: 'positive1', - selector: '*', - enabled: false, - any: [ - { - id: 'positive1-check1', - evaluate: function() { - success = false; - } - } - ] - }) - ); - a.run( - { include: [document] }, - {}, - function() { - assert.ok(success); - done(); - }, - isNotCalled - ); - }); - it("should call the rule's run function", function(done) { - var targetRule = mockRules[mockRules.length - 1], - rule = axe.utils.findBy(a.rules, 'id', targetRule.id), - called = false, - orig; - - fixture.innerHTML = 'link'; - orig = rule.run; - rule.run = function(node, options, callback) { - called = true; - callback({}); - }; - a.run( - { include: [document] }, - {}, - function() { - assert.isTrue(called); - rule.run = orig; - done(); - }, - isNotCalled - ); - }); - it('should pass the option to the run function', function(done) { - var targetRule = mockRules[mockRules.length - 1], - rule = axe.utils.findBy(a.rules, 'id', targetRule.id), - passed = false, - orig, - options; - - fixture.innerHTML = 'link'; - orig = rule.run; -<<<<<<< HEAD - rule.run = function (node, o, callback) { - assert.property(o, 'rules'); - passed = true; - callback({}); - }; - - options = { rules: {} }; - -======= - rule.run = function(node, o, callback) { - assert.deepEqual(o, options); - passed = true; - callback({}); - }; - options = { rules: {} }; ->>>>>>> develop - (options.rules[targetRule.id] = {}).data = 'monkeys'; - a.run( - { include: [document] }, - options, - function() { - assert.ok(passed); - rule.run = orig; - done(); - }, - isNotCalled - ); - }); - - it('should skip pageLevel rules if context is not set to entire page', function() { - var audit = new Audit(); - -<<<<<<< HEAD - audit.rules.push(new Rule({ - pageLevel: true, - enabled: true, - evaluate: function () { - assert.ok(false, 'Should not run'); - } - })); - - audit.run({ include: [document.body], page: false }, {}, function (results) { - assert.deepEqual(results, []); - }, isNotCalled); -======= - audit.rules.push( - new Rule({ - pageLevel: true, - enabled: true, - evaluate: function() { - assert.ok(false, 'Should not run'); - } - }) - ); ->>>>>>> develop - - audit.run( - { include: [document.body], page: false }, - {}, - function(results) { - assert.deepEqual(results, []); - }, - isNotCalled - ); - }); - - it('catches errors and passes them as a cantTell result', function(done) { - var err = new Error('Launch the super sheep!'); - a.addRule({ - id: 'throw1', - selector: '*', - any: [ - { - id: 'throw1-check1' - } - ] - }); - a.addCheck({ - id: 'throw1-check1', - evaluate: function() { - throw err; - } - }); - -<<<<<<< HEAD - a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, { - runOnly: { - 'type': 'rule', - 'values': ['throw1'] - } - }, function (results) { - assert.lengthOf(results, 1); - assert.equal(results[0].result, 'cantTell'); - assert.equal(results[0].message, err.message); - assert.equal(results[0].stack, err.stack); - assert.equal(results[0].error, err); - done(); - }, isNotCalled); -======= - a.run( - { include: [axe.utils.getFlattenedTree(fixture)[0]] }, - { - runOnly: { - type: 'rule', - values: ['throw1'] - } - }, - function(results) { - assert.lengthOf(results, 1); - assert.equal(results[0].result, 'cantTell'); - assert.equal(results[0].message, err.message); - assert.equal(results[0].stack, err.stack); - assert.equal(results[0].error, err); - done(); - }, - isNotCalled - ); ->>>>>>> develop - }); - - it('should not halt if errors occur', function(done) { - a.addRule({ - id: 'throw1', - selector: '*', - any: [ - { - id: 'throw1-check1' - } - ] - }); - a.addCheck({ - id: 'throw1-check1', - evaluate: function() { - throw new Error('Launch the super sheep!'); - } - }); - a.run( - { include: [axe.utils.getFlattenedTree(fixture)[0]] }, - { - runOnly: { - type: 'rule', - values: ['throw1', 'positive1'] - } - }, - function() { - done(); - }, - isNotCalled - ); - }); - - it('should run audit.normalizeOptions to ensure valid input', function() { - fixture.innerHTML = - '' + - '
bananas
' + - '' + - 'FAIL ME'; - var checked = 'options not validated'; - - a.normalizeOptions = function() { - checked = 'options validated'; - }; - - a.run({ include: [fixture] }, {}, noop, isNotCalled); - assert.equal(checked, 'options validated'); - }); - it('should halt if an error occurs when debug is set', function(done) { - a.addRule({ - id: 'throw1', - selector: '*', - any: [ - { - id: 'throw1-check1' - } - ] - }); - a.addCheck({ - id: 'throw1-check1', - evaluate: function() { - throw new Error('Launch the super sheep!'); - } - }); - a.run( - { include: [axe.utils.getFlattenedTree(fixture)[0]] }, - { - debug: true, - runOnly: { - type: 'rule', - values: ['throw1'] - } - }, - noop, - function(err) { - assert.equal(err.message, 'Launch the super sheep!'); - done(); - } - ); - }); - }); - describe('Audit#after', function() { - it('should run Rule#after on any rule whose result is passed in', function() { - /*eslint no-unused-vars:0*/ - var audit = new Audit(); - var success = false; - var options = [{ id: 'hehe', enabled: true, monkeys: 'bananas' }]; - var results = [ - { - id: 'hehe', - monkeys: 'bananas' - } - ]; - audit.rules.push( - new Rule({ - id: 'hehe', - pageLevel: false, - enabled: false - }) - ); - - audit.rules[0].after = function(res, opts) { - assert.equal(res, results[0]); - assert.deepEqual(opts, options); - success = true; - }; - - audit.after(results, options); - }); - }); - - describe('Audit#normalizeOptions', function() { - it('returns the options object when it is valid', function() { - var opt = { - runOnly: { - type: 'rule', - values: ['positive1', 'positive2'] - }, - rules: { - negative1: { enabled: false } - } - }; - assert(a.normalizeOptions(opt), opt); - }); - - it('allows `value` as alternative to `values`', function() { - var opt = { - runOnly: { - type: 'rule', - value: ['positive1', 'positive2'] - } - }; - var out = a.normalizeOptions(opt); - assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']); - assert.isUndefined(out.runOnly.value); - }); - - it('allows type: rules as an alternative to type: rule', function() { - var opt = { - runOnly: { - type: 'rules', - values: ['positive1', 'positive2'] - } - }; - assert(a.normalizeOptions(opt).runOnly.type, 'rule'); - }); - - it('allows type: tags as an alternative to type: tag', function() { - var opt = { - runOnly: { - type: 'tags', - values: ['positive'] - } - }; - assert(a.normalizeOptions(opt).runOnly.type, 'tag'); - }); - - it('allows type: undefined as an alternative to type: tag', function() { - var opt = { - runOnly: { - values: ['positive'] - } - }; - assert(a.normalizeOptions(opt).runOnly.type, 'tag'); - }); - - it('allows runOnly as an array as an alternative to type: tag', function() { - var opt = { runOnly: ['positive', 'negative'] }; - var out = a.normalizeOptions(opt); - assert(out.runOnly.type, 'tag'); - assert.deepEqual(out.runOnly.values, ['positive', 'negative']); - }); - - it('throws an error runOnly.values not an array', function() { - assert.throws(function() { - a.normalizeOptions({ - runOnly: { - type: 'rule', - values: { badProp: 'badValue' } - } - }); - }); - }); - - it('throws an error runOnly.values an empty', function() { - assert.throws(function() { - a.normalizeOptions({ - runOnly: { - type: 'rule', - values: [] - } - }); - }); - }); - - it('throws an error runOnly.type is unknown', function() { - assert.throws(function() { - a.normalizeOptions({ - runOnly: { - type: 'something-else', - values: ['wcag2aa'] - } - }); - }); - }); - - it('throws an error when option.runOnly has an unknown rule', function() { - assert.throws(function() { - a.normalizeOptions({ - runOnly: { - type: 'rule', - values: ['frakeRule'] - } - }); - }); - }); - - it('throws an error when option.runOnly has an unknown tag', function() { - assert.throws(function() { - a.normalizeOptions({ - runOnly: { - type: 'tags', - values: ['fakeTag'] - } - }); - }); - }); - - it('throws an error when option.rules has an unknown rule', function() { - assert.throws(function() { - a.normalizeOptions({ - rules: { - fakeRule: { enabled: false } - } - }); - }); - }); - }); -}); diff --git a/test/core/public/run.js.orig b/test/core/public/run.js.orig deleted file mode 100644 index d2766cda61..0000000000 --- a/test/core/public/run.js.orig +++ /dev/null @@ -1,494 +0,0 @@ -describe('axe.run', function () { - 'use strict'; - - var fixture = document.getElementById('fixture'); - var noop = function () { }; - var origRunRules = axe._runRules; - - beforeEach(function () { - axe._load({ - rules: [{ - id: 'test', - selector: '*', - none: ['fred'] - }], - checks: [{ - id: 'fred', - evaluate: function (node) { - this.relatedNodes([node]); - return true; - } - }] - }); - }); - - afterEach(function () { - fixture.innerHTML = ''; - axe._audit = null; - axe._runRules = origRunRules; - }); - - it('takes context, options and callback as parameters', function (done) { - fixture.innerHTML = '
'; - var options = { - runOnly: { - type: 'rule', - values: ['test'] - } - }; - - axe.run(['#t1'], options, function () { - assert.ok(true, 'test completed'); - done(); - }); - }); - - it('uses document as content if it is not specified', function (done) { - axe._runRules = function (ctxt) { - assert.equal(ctxt, document); - done(); - }; - - axe.run({ someOption: true }, noop); - }); - - it('uses an object as options if it is not specified', function (done) { - axe._runRules = function (ctxt, opt) { - assert.isObject(opt); - done(); - }; - axe.run(document, noop); - }); - - it('works with performance logging enabled', function (done) { - axe.run(document, { performanceTimer: true }, function (err, result) { - assert.isObject(result); - done(); - }); - }); - - it('treats objects with include or exclude as the context object', function (done) { - axe._runRules = function (ctxt) { - assert.deepEqual(ctxt, { include: '#BoggyB' }); - done(); - }; - - axe.run({ include: '#BoggyB' }, noop); - }); - - it('treats objects with neither include or exclude as the option object', function (done) { - axe._runRules = function (ctxt, opt) { - assert.deepEqual(opt.HHG, 'hallelujah'); - done(); - }; - - axe.run({ HHG: 'hallelujah' }, noop); - }); - - it('does not fail if no callback is specified', function (done) { - assert.doesNotThrow(function () { - axe.run(done); - }); - }); - - it('should clear axe._tree', function (done) { - var getFlattenedTree = axe.utils.getFlattenedTree; - var thing = 'honey badger'; - axe.utils.getFlattenedTree = function () { - return thing; - }; - axe._runRules = function () { - assert.isTrue(typeof axe._tree === 'undefined'); - axe.utils.getFlattenedTree = getFlattenedTree; - done(); - }; - - axe.run({ someOption: true }, noop); - }); - - describe('callback', function () { - it('gives errors to the first argument on the callback', function (done) { - axe._runRules = function (ctxt, opt, resolve, reject) { - axe._runRules = origRunRules; - reject('Ninja rope!'); - }; - - axe.run({ reporter: 'raw' }, function (err) { - assert.equal(err, 'Ninja rope!'); - done(); - }); - }); - - it('gives results to the second argument on the callback', function (done) { - axe._runRules = function (ctxt, opt, resolve) { - axe._runRules = origRunRules; - resolve('MB Bomb', noop); - }; - - axe.run({ reporter: 'raw' }, function (err, result) { - assert.equal(err, null); - assert.equal(result, 'MB Bomb'); - done(); - }); - }); - - it('does not run the callback twice if it throws', function (done) { - var calls = 0; - axe._runRules = function (ctxt, opt, resolve) { - resolve([], noop); - }; - - var log = axe.log; - axe.log = function (e) { - assert.equal(e.message, 'err'); - axe.log = log; - }; - axe.run(function () { - calls += 1; - if (calls === 1) { - setTimeout(function () { - assert.equal(calls, 1); - axe.log = log; - done(); - }, 20); - } - throw new Error('err'); - }); - }); - - it('is called after cleanup', function (done) { - var isClean = false; - axe._runRules = function (ctxt, opt, resolve) { - axe._runRules = origRunRules; - // Check that cleanup is called before the callback is executed - resolve('MB Bomb', function cleanup() { - isClean = true; - }); - }; - - axe.run({ reporter: 'raw' }, function () { - assert.isTrue(isClean, 'cleanup must be called first'); - done(); - }); - }); - }); - - - describe('promise result', function () { - /*eslint indent: 0*/ - var promiseIt = window.Promise ? it : it.skip; - - promiseIt('returns an error to catch if axe fails', function (done) { - axe._runRules = function (ctxt, opt, resolve, reject) { - axe._runRules = origRunRules; - reject('I surrender!'); - }; - - var p = axe.run({ reporter: 'raw' }); - p.then(noop) - .catch(function (err) { - assert.equal(err, 'I surrender!'); - done(); - }); - - assert.instanceOf(p, window.Promise); - }); - - promiseIt('returns a promise if no callback was given', function (done) { - axe._runRules = function (ctxt, opt, resolve) { - axe._runRules = origRunRules; - resolve('World party', noop); - }; - - var p = axe.run({ reporter: 'raw' }); - p.then(function (result) { - assert.equal(result, 'World party'); - done(); - }); - - assert.instanceOf(p, window.Promise); - }); - - promiseIt('does not error if then() throws', function (done) { - axe._runRules = function (ctxt, opt, resolve) { - resolve([], noop); - }; - - axe.run() - .then(function () { - throw new Error('err'); - }, function (e) { - assert.isNotOk(e, 'Caught callback error in the wrong place'); - done(); - - }).catch(function (e) { - assert.equal(e.message, 'err'); - done(); - }); - }); - - promiseIt('is called after cleanup', function (done) { - var isClean = false; - axe._runRules = function (ctxt, opt, resolve) { - axe._runRules = origRunRules; - // Check that cleanup is called before the callback is executed - resolve('MB Bomb', function cleanup() { - isClean = true; - }); - }; - - axe.run({ reporter: 'raw' }) - .then(function () { - assert(isClean, 'cleanup must be called first'); - done(); - }) - .catch(done); - }); - }); - - - describe('option reporter', function () { - it('sets v1 as the default reporter if audit.reporter is null', function (done) { - axe._runRules = function (ctxt, opt) { - assert.equal(opt.reporter, 'v1'); - axe._runRules = origRunRules; - done(); - }; - axe._audit.reporter = null; - axe.run(document, noop); - }); - - it('uses the audit.reporter if no reporter is set in options', function (done) { - axe._runRules = function (ctxt, opt) { - assert.equal(opt.reporter, 'raw'); - axe._runRules = origRunRules; - done(); - }; - axe._audit.reporter = 'raw'; - axe.run(document, noop); - }); - - it('does not override if another reporter is set', function (done) { - axe._runRules = function (ctxt, opt) { - assert.equal(opt.reporter, 'raw'); - axe._runRules = origRunRules; - done(); - }; - axe._audit.reporter = null; - axe.run(document, { reporter: 'raw' }, noop); - }); - }); - - - describe('option xpath', function () { - it('returns no xpath if the xpath option is not set', function (done) { - axe.run('#fixture', function (err, result) { - assert.isUndefined(result.violations[0].nodes[0].xpath); - done(); - }); - }); - - it('returns the xpath if the xpath option is true', function (done) { - axe.run('#fixture', { - xpath: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); - }); - - it('returns xpath on related nodes', function (done) { - axe.run('#fixture', { - xpath: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].none[0].relatedNodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); - }); - - it('returns the xpath on any reporter', function (done) { - axe.run('#fixture', { - xpath: true, - reporter: 'no-passes' - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); - }); - }); - - describe('option absolutePaths', function () { - - it('returns relative paths when falsy', function (done) { - axe.run('#fixture', { - absolutePaths: 0 - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].target, - ['#fixture'] - ); - done(); - }); - }); - - it('returns absolute paths when truthy', function (done) { - axe.run('#fixture', { - absolutePaths: 'yes please' - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].target, - ['html > body > #fixture'] - ); - done(); - }); - }); - - it('returns absolute paths on related nodes', function (done) { - axe.run('#fixture', { - absolutePaths: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].none[0].relatedNodes[0].target, - ['html > body > #fixture'] - ); - done(); - }); - }); - }); - - describe('option restoreScroll', function () { - it('does not change scroll when restoreScroll is not set', function (done) { - var calls = 0; - var _getSS = axe.utils.getScrollState; - var _setSS = axe.utils.setScrollState; - axe.utils.setScrollState = function () { - calls++; - }; - axe.utils.getScrollState = axe.utils.setScrollState; - - axe.run('#fixture', {}, function () { - assert.equal(calls, 0); - axe.utils.getScrollState = _getSS; - axe.utils.setScrollState = _setSS; - done(); - }); - }); - - it('resets scrolLState after running the audit', function (done) { - var scrollState = {}; - var calls = 0; - var _getSS = axe.utils.getScrollState; - var _setSS = axe.utils.setScrollState; - - axe.utils.setScrollState = function (arg) { - assert.equal(scrollState, arg); - calls++; - }; - axe.utils.getScrollState = function () { - return scrollState; - }; - - axe.run('#fixture', { - restoreScroll: true - }, function () { - assert.equal(calls, 1); - axe.utils.getScrollState = _getSS; - axe.utils.setScrollState = _setSS; - done(); - }); - }); - }); -}); - -describe('axe.run iframes', function () { - 'use strict'; - - var fixture = document.getElementById('fixture'); - var origRunRules = axe._runRules; - - beforeEach(function () { - fixture.innerHTML = '
Target in top frame
'; - axe._load({ - rules: [{ - id: 'html', - selector: '#target', - none: ['fred'] - }], - checks: [{ - id: 'fred', - evaluate: function () { - return true; - } - }] - }); - }); - - afterEach(function () { - fixture.innerHTML = ''; - axe._audit = null; - axe._runRules = origRunRules; - }); - - it('includes iframes by default', function (done) { - var frame = document.createElement('iframe'); - - frame.addEventListener('load', function () { - var safetyTimeout = window.setTimeout(function () { - done(); - }, 1000); - - axe.run('#fixture', {}, function (err, result) { - assert.isDefined(result); - assert.equal(result.violations.length, 1); - var violation = result.violations[0]; - assert.equal(violation.nodes.length, 2, - 'one node for top frame, one for iframe'); - assert.isTrue(violation.nodes.some(function (node) { - return node.target.length === 1 && node.target[0] === '#target'; - }), 'one result from top frame'); - assert.isTrue(violation.nodes.some(function (node) { - return node.target.length === 2 && - node.target[0] === '#fixture > iframe'; - }), 'one result from iframe'); - window.clearTimeout(safetyTimeout); - done(); - }); - }); - - frame.src = '../mock/frames/test.html'; - fixture.appendChild(frame); - }); - - it('excludes iframes if iframes is false', function (done) { - var frame = document.createElement('iframe'); - - frame.addEventListener('load', function () { - var safetyTimeout = setTimeout(function () { - done(); - }, 1000); - - axe.run('#fixture', { iframes: false }, function (err, result) { - assert.equal(result.violations.length, 1); - var violation = result.violations[0]; - assert.equal(violation.nodes.length, 1, - 'only top frame'); - assert.equal(violation.nodes[0].target.length, 1); - assert.equal(violation.nodes[0].target[0], '#target'); - window.clearTimeout(safetyTimeout); - done(); - }); - }); - - frame.src = '../mock/frames/test.html'; - fixture.appendChild(frame); - }); -}); From f18db7ffa8a38cf7450eae5d88de58058edbcae1 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 2 Jul 2018 12:42:40 +0100 Subject: [PATCH 24/43] refactor: revert preload changes to run/ audit/ checks --- lib/checks/aria/aria-hidden-body.json | 18 +- lib/checks/language/valid-lang.js | 23 +- lib/core/base/audit.js | 237 ++++----- lib/core/base/check.js | 37 +- lib/core/base/rule.js | 135 ++--- lib/core/public/configure.js | 15 +- lib/core/public/run-rules.js | 34 +- lib/core/public/run.js | 77 ++- lib/core/utils/get-check-option.js | 23 +- test/core/base/audit.js | 678 +++++++++++++++----------- test/core/public/run.js | 430 ++++++++-------- test/sandbox-preload.css | 4 - test/sandbox-preload.html | 52 -- 13 files changed, 923 insertions(+), 840 deletions(-) delete mode 100644 test/sandbox-preload.css delete mode 100644 test/sandbox-preload.html diff --git a/lib/checks/aria/aria-hidden-body.json b/lib/checks/aria/aria-hidden-body.json index 889fd68d2a..bad069615e 100644 --- a/lib/checks/aria/aria-hidden-body.json +++ b/lib/checks/aria/aria-hidden-body.json @@ -1,11 +1,11 @@ { - "id": "aria-hidden-body", - "evaluate": "aria-hidden-body.js", - "metadata": { - "impact": "critical", - "messages": { - "pass": "No aria-hidden attribute is present on document body", - "fail": "aria-hidden=true should not be present on the document body" - } - } + "id": "aria-hidden-body", + "evaluate": "aria-hidden-body.js", + "metadata": { + "impact": "critical", + "messages": { + "pass": "No aria-hidden attribute is present on document body", + "fail": "aria-hidden=true should not be present on the document body" + } + } } \ No newline at end of file diff --git a/lib/checks/language/valid-lang.js b/lib/checks/language/valid-lang.js index 3d7da69c96..0fe7945c0f 100644 --- a/lib/checks/language/valid-lang.js +++ b/lib/checks/language/valid-lang.js @@ -1,25 +1,15 @@ - function getBaseLang(lang) { - return lang.trim().split('-')[0].toLowerCase(); + return lang + .trim() + .split('-')[0] + .toLowerCase(); } var langs, invalid; -/** - * - * Note from Jey: - * the below is a hack as options is an array in certain checks, and an object is other checks. - * this was to get some integration/ full tests passing. - * TODO: - * need to come up with a more robust typing of args to prevent complex object mutation - */ -langs = ( - options && Array.isArray(options) - ? options - : axe.commons.utils.validLangs() -).map(getBaseLang); +langs = (options ? options : axe.commons.utils.validLangs()).map(getBaseLang); -invalid = ['lang', 'xml:lang'].reduce(function (invalid, langAttr) { +invalid = ['lang', 'xml:lang'].reduce(function(invalid, langAttr) { var langVal = node.getAttribute(langAttr); if (typeof langVal !== 'string') { return invalid; @@ -33,7 +23,6 @@ invalid = ['lang', 'xml:lang'].reduce(function (invalid, langAttr) { invalid.push(langAttr + '="' + node.getAttribute(langAttr) + '"'); } return invalid; - }, []); if (invalid.length) { diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 77571ea180..80d1a57a54 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -16,10 +16,13 @@ function getDefaultConfiguration(audit) { config.reporter = config.reporter || null; config.rules = config.rules || []; config.checks = config.checks || []; - config.data = Object.assign({ - checks: {}, - rules: {} - }, config.data); + config.data = Object.assign( + { + checks: {}, + rules: {} + }, + config.data + ); return config; } @@ -49,7 +52,7 @@ function Audit(audit) { /** * Initializes the rules and checks */ -Audit.prototype._init = function () { +Audit.prototype._init = function() { var audit = getDefaultConfiguration(this.defaultConfig); axe.commons = commons = audit.commons; @@ -65,8 +68,10 @@ Audit.prototype._init = function () { this.data = {}; this.data.checks = (audit.data && audit.data.checks) || {}; this.data.rules = (audit.data && audit.data.rules) || {}; - this.data.failureSummaries = (audit.data && audit.data.failureSummaries) || {}; - this.data.incompleteFallbackMessage = (audit.data && audit.data.incompleteFallbackMessage || ''); + this.data.failureSummaries = + (audit.data && audit.data.failureSummaries) || {}; + this.data.incompleteFallbackMessage = + (audit.data && audit.data.incompleteFallbackMessage) || ''; this._constructHelpUrls(); // create default helpUrls }; @@ -74,7 +79,8 @@ Audit.prototype._init = function () { /** * Adds a new command to the audit */ -Audit.prototype.registerCommand = function (command) { + +Audit.prototype.registerCommand = function(command) { 'use strict'; this.commands[command.id] = command.callback; }; @@ -83,7 +89,7 @@ Audit.prototype.registerCommand = function (command) { * Adds a new rule to the Audit. If a rule with specified ID already exists, it will be overridden * @param {Object} spec Rule specification object */ -Audit.prototype.addRule = function (spec) { +Audit.prototype.addRule = function(spec) { 'use strict'; if (spec.metadata) { @@ -104,7 +110,7 @@ Audit.prototype.addRule = function (spec) { * * @param {Object} spec Check specification object */ -Audit.prototype.addCheck = function (spec) { +Audit.prototype.addCheck = function(spec) { /*eslint no-eval: 0 */ 'use strict'; let metadata = spec.metadata; @@ -113,14 +119,17 @@ Audit.prototype.addCheck = function (spec) { this.data.checks[spec.id] = metadata; // Transform messages into functions: if (typeof metadata.messages === 'object') { - Object - .keys(metadata.messages) - .filter((prop) => { - return (metadata.messages.hasOwnProperty(prop) && typeof metadata.messages[prop] === 'string') - }) - .forEach((prop) => { + Object.keys(metadata.messages) + .filter( + prop => + metadata.messages.hasOwnProperty(prop) && + typeof metadata.messages[prop] === 'string' + ) + .forEach(prop => { if (metadata.messages[prop].indexOf('function') === 0) { - metadata.messages[prop] = (new Function('return ' + metadata.messages[prop] + ';'))(); + metadata.messages[prop] = new Function( + 'return ' + metadata.messages[prop] + ';' + )(); } }); } @@ -133,24 +142,6 @@ Audit.prototype.addCheck = function (spec) { } }; - -const performanceMarker = (id, shouldMark) => { - if (shouldMark) { - const marker = { - start: `mark_rule_start_${id}`, - end: `mark_rule_end_${id}` - } - axe.utils.performanceTimer.mark(marker.start); - return () => { - axe.utils.performanceTimer.mark(marker.end); - axe.utils.performanceTimer.measure(`rule_${id}`, marker.start, marker.end); - }; - } else { - return () => { }; - } -}; - - /** * Runs the Audit; which in turn should call `run` on each rule. * @async @@ -158,104 +149,101 @@ const performanceMarker = (id, shouldMark) => { * @param {Object} options Options object to pass into rules and/or disable rules or checks * @param {Function} fn Callback function to fire when audit is complete */ -Audit.prototype.run = function (context, options, resolve, reject) { +Audit.prototype.run = function(context, options, resolve, reject) { 'use strict'; - this.normalizeOptions(options); axe._selectCache = []; - - // preload assets if necessary - axe.utils.preload(options) - .then(([preloadedAssets]) => { // q - retuns array -> destructure/ pluck the first item, is an key value map of preloadedAssets - // mutate options with preloadedAssets - options = { - ...options, - preloadedAssets - }; - // construct a queue of rules to execute - this.rules - .reduce((out, rule) => { - if (axe.utils.ruleShouldRun(rule, context, options)) { - const marker = performanceMarker(rule.id, options.performanceTimer); - out.defer((resolve, reject) => { - rule.run( - context, - options, - (result) => { - marker(); - resolve(result); - }, - (err) => { - if (!options.debug) { - const errResult = Object.assign(new RuleResult(rule), { - result: axe.constants.CANTTELL, - description: 'An error occured while running this rule', - message: err.message, - stack: err.stack, - error: err - }); - resolve(errResult); - } else { - reject(err); - } - }); - }); + var q = axe.utils.queue(); + this.rules.forEach(function(rule) { + if (axe.utils.ruleShouldRun(rule, context, options)) { + if (options.performanceTimer) { + var markEnd = 'mark_rule_end_' + rule.id; + var markStart = 'mark_rule_start_' + rule.id; + axe.utils.performanceTimer.mark(markStart); + } + q.defer(function(res, rej) { + rule.run( + context, + options, + function(out) { + if (options.performanceTimer) { + axe.utils.performanceTimer.mark(markEnd); + axe.utils.performanceTimer.measure( + 'rule_' + rule.id, + markStart, + markEnd + ); + } + res(out); + }, + function(err) { + if (!options.debug) { + var errResult = Object.assign(new RuleResult(rule), { + result: axe.constants.CANTTELL, + description: 'An error occured while running this rule', + message: err.message, + stack: err.stack, + error: err + }); + res(errResult); + } else { + rej(err); + } } - return out; - }, axe.utils.queue()) - .then((results) => { - axe._selectCache = undefined; // remove the cache - resolve( - results.filter((result) => { - return !!result; - }) - ); - }).catch(reject); - }) - .catch(reject); + ); + }); + } + }); + q.then(function(results) { + axe._selectCache = undefined; // remove the cache + resolve( + results.filter(function(result) { + return !!result; + }) + ); + }).catch(reject); }; - /** * Runs Rule `after` post processing functions * @param {Array} results Array of RuleResults to postprocess * @param {Mixed} options Options object to pass into rules and/or disable rules or checks */ -Audit.prototype.after = function (results, options) { +Audit.prototype.after = function(results, options) { 'use strict'; var rules = this.rules; - return results.map(function (ruleResult) { + return results.map(function(ruleResult) { var rule = axe.utils.findBy(rules, 'id', ruleResult.id); if (!rule) { // If you see this, you're probably running the Mocha tests with the aXe extension installed - throw new Error('Result for unknown rule. You may be running mismatch aXe-core versions'); + throw new Error( + 'Result for unknown rule. You may be running mismatch aXe-core versions' + ); } return rule.after(ruleResult, options); }); }; - /** * Get the rule with a given ID * @param {string} * @return {Rule} */ -Audit.prototype.getRule = function (ruleId) { +Audit.prototype.getRule = function(ruleId) { return this.rules.find(rule => rule.id === ruleId); }; - /** * Ensure all rules that are expected to run exist * @throws {Error} If any tag or rule specified in options is unknown * @param {Object} options Options object * @return {Object} Validated options object */ -Audit.prototype.normalizeOptions = function (options) { +Audit.prototype.normalizeOptions = function(options) { /* eslint max-statements: ["error", 22] */ 'use strict'; var audit = this; @@ -281,7 +269,7 @@ Audit.prototype.normalizeOptions = function (options) { // Check if every value in options.runOnly is a known rule ID if (['rule', 'rules'].includes(only.type)) { only.type = 'rule'; - only.values.forEach(function (ruleId) { + only.values.forEach(function(ruleId) { if (!audit.getRule(ruleId)) { throw new Error('unknown rule `' + ruleId + '` in options.runOnly'); } @@ -291,14 +279,15 @@ Audit.prototype.normalizeOptions = function (options) { } else if (['tag', 'tags', undefined].includes(only.type)) { only.type = 'tag'; const unmatchedTags = audit.rules.reduce((unmatchedTags, rule) => { - return (unmatchedTags.length + return unmatchedTags.length ? unmatchedTags.filter(tag => !rule.tags.includes(tag)) - : unmatchedTags - ); + : unmatchedTags; }, only.values); if (unmatchedTags.length !== 0) { - throw new Error('Could not find tags `' + unmatchedTags.join('`, `') + '`'); + throw new Error( + 'Could not find tags `' + unmatchedTags.join('`, `') + '`' + ); } } else { throw new Error(`Unknown runOnly type '${only.type}'`); @@ -306,50 +295,63 @@ Audit.prototype.normalizeOptions = function (options) { } if (typeof options.rules === 'object') { - Object.keys(options.rules) - .forEach(function (ruleId) { - if (!audit.getRule(ruleId)) { - throw new Error('unknown rule `' + ruleId + '` in options.rules'); - } - }); + Object.keys(options.rules).forEach(function(ruleId) { + if (!audit.getRule(ruleId)) { + throw new Error('unknown rule `' + ruleId + '` in options.rules'); + } + }); } return options; }; - /* * Updates the default options and then applies them * @param {Mixed} options Options object */ -Audit.prototype.setBranding = function (branding) { + +Audit.prototype.setBranding = function(branding) { 'use strict'; let previous = { brand: this.brand, application: this.application }; - if (branding && branding.hasOwnProperty('brand') && - branding.brand && typeof branding.brand === 'string') { + if ( + branding && + branding.hasOwnProperty('brand') && + branding.brand && + typeof branding.brand === 'string' + ) { this.brand = branding.brand; } - if (branding && branding.hasOwnProperty('application') && - branding.application && typeof branding.application === 'string') { + if ( + branding && + branding.hasOwnProperty('application') && + branding.application && + typeof branding.application === 'string' + ) { this.application = branding.application; } this._constructHelpUrls(previous); }; - /** * For all the rules, create the helpUrl and add it to the data for that rule */ function getHelpUrl({ brand, application }, ruleId, version) { - return axe.constants.helpUrlBase + brand + - '/' + (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + - '/' + ruleId + '?application=' + application; + return ( + axe.constants.helpUrlBase + + brand + + '/' + + (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + + '/' + + ruleId + + '?application=' + + application + ); } -Audit.prototype._constructHelpUrls = function (previous = null) { +Audit.prototype._constructHelpUrls = function(previous = null) { var version = (axe.version.match(/^[1-9][0-9]*\.[0-9]+/) || ['x.y'])[0]; this.rules.forEach(rule => { if (!this.data.rules[rule.id]) { @@ -365,12 +367,11 @@ Audit.prototype._constructHelpUrls = function (previous = null) { }); }; - /** * Reset the default rules, checks and meta data */ -Audit.prototype.resetRulesAndChecks = function () { +Audit.prototype.resetRulesAndChecks = function() { 'use strict'; this._init(); }; diff --git a/lib/core/base/check.js b/lib/core/base/check.js index 94039888d0..0b52d9d293 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -50,7 +50,6 @@ function Check(spec) { */ Check.prototype.enabled = true; - /** * Run the check's evaluate function (call `this.evaluate(node, options)`) * @param {HTMLElement} node The node to test @@ -58,23 +57,31 @@ Check.prototype.enabled = true; * information for the check * @param {Function} callback Function to fire when check is complete */ -Check.prototype.run = function (node, options, resolve, reject) { - +Check.prototype.run = function(node, options, resolve, reject) { + 'use strict'; options = options || {}; - - const enabled = options.hasOwnProperty('enabled') - ? options.enabled - : this.enabled; - - const checkOptions = options.options || this.options; + var enabled = options.hasOwnProperty('enabled') + ? options.enabled + : this.enabled, + checkOptions = options.options || this.options; if (enabled) { var checkResult = new CheckResult(this); - var checkHelper = axe.utils.checkHelper(checkResult, options, resolve, reject); + var checkHelper = axe.utils.checkHelper( + checkResult, + options, + resolve, + reject + ); var result; try { - result = this.evaluate.call(checkHelper, node.actualNode, checkOptions, node); + result = this.evaluate.call( + checkHelper, + node.actualNode, + checkOptions, + node + ); } catch (e) { reject(e); return; @@ -82,7 +89,7 @@ Check.prototype.run = function (node, options, resolve, reject) { if (!checkHelper.isAsync) { checkResult.result = result; - setTimeout(function () { + setTimeout(function() { resolve(checkResult); }, 0); } @@ -98,12 +105,12 @@ Check.prototype.run = function (node, options, resolve, reject) { * @param {Object} spec - the specification of the attributes to be changed */ -Check.prototype.configure = function (spec) { +Check.prototype.configure = function(spec) { ['options', 'enabled'] .filter(prop => spec.hasOwnProperty(prop)) - .forEach(prop => this[prop] = spec[prop]); + .forEach(prop => (this[prop] = spec[prop])); ['evaluate', 'after'] .filter(prop => spec.hasOwnProperty(prop)) - .forEach(prop => this[prop] = createExecutionContext(spec[prop])); + .forEach(prop => (this[prop] = createExecutionContext(spec[prop]))); }; diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index bd0db0c5fa..93c6c8e32d 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -22,7 +22,8 @@ function Rule(spec, parentAudit) { * Whether to exclude hiddden elements form analysis. Defaults to true. * @type {Boolean} */ - this.excludeHidden = typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; + this.excludeHidden = + typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; /** * Flag to enable or disable rule @@ -75,7 +76,7 @@ function Rule(spec, parentAudit) { * a given node. Defaults to `true`. * @return {Boolean} Whether the rule should run */ -Rule.prototype.matches = function () { +Rule.prototype.matches = function() { 'use strict'; return true; @@ -86,39 +87,38 @@ Rule.prototype.matches = function () { * @param {Context} context The resolved Context object * @return {Array} All matching `HTMLElement`s */ -Rule.prototype.gather = function (context) { +Rule.prototype.gather = function(context) { 'use strict'; var elements = axe.utils.select(this.selector, context); if (this.excludeHidden) { - return elements.filter(function (element) { + return elements.filter(function(element) { return !axe.utils.isHidden(element.actualNode); }); } return elements; }; -Rule.prototype.runChecks = function (type, node, options, resolve, reject) { - const self = this; - const checkQueue = axe.utils.queue(); +Rule.prototype.runChecks = function(type, node, options, resolve, reject) { + 'use strict'; - this[type] - .forEach((c) => { - const check = self._audit.checks[c.id || c]; - let option = axe.utils.getCheckOption(check, self.id, options); - checkQueue.defer(function (res, rej) { - check.run(node, option, res, rej); - }); + var self = this; + var checkQueue = axe.utils.queue(); + this[type].forEach(function(c) { + var check = self._audit.checks[c.id || c]; + var option = axe.utils.getCheckOption(check, self.id, options); + checkQueue.defer(function(res, rej) { + check.run(node, option, res, rej); }); + }); checkQueue - .then(function (results) { - results = results.filter(function (check) { + .then(function(results) { + results = results.filter(function(check) { return check; }); resolve({ type: type, results: results }); }) .catch(reject); - }; /** @@ -127,7 +127,7 @@ Rule.prototype.runChecks = function (type, node, options, resolve, reject) { * @param {Mixed} options Options specific to this rule * @param {Function} callback Function to call when evaluate is complete; receives a RuleResult instance */ -Rule.prototype.run = function (context, options, resolve, reject) { +Rule.prototype.run = function(context, options, resolve, reject) { /*eslint max-statements: ["error",17] */ const q = axe.utils.queue(); const ruleResult = new RuleResult(this); @@ -137,8 +137,9 @@ Rule.prototype.run = function (context, options, resolve, reject) { try { // Matches throws an error when it lacks support for document methods - nodes = this.gather(context) - .filter(node => this.matches(node.actualNode, node)); + nodes = this.gather(context).filter(node => + this.matches(node.actualNode, node) + ); } catch (error) { // Exit the rule execution if matches fails reject(new SupportError({ cause: error, ruleId: this.id })); @@ -146,27 +147,35 @@ Rule.prototype.run = function (context, options, resolve, reject) { } if (options.performanceTimer) { - axe.log('gather (', nodes.length, '):', axe.utils.performanceTimer.timeElapsed() + 'ms'); + axe.log( + 'gather (', + nodes.length, + '):', + axe.utils.performanceTimer.timeElapsed() + 'ms' + ); axe.utils.performanceTimer.mark(markStart); } nodes.forEach(node => { q.defer((resolveNode, rejectNode) => { var checkQueue = axe.utils.queue(); - - ['any', 'all', 'none'] - .forEach((type) => { - checkQueue.defer((res, rej) => { - this.runChecks(type, node, options, res, rej); - }); - }); + checkQueue.defer((res, rej) => { + this.runChecks('any', node, options, res, rej); + }); + checkQueue.defer((res, rej) => { + this.runChecks('all', node, options, res, rej); + }); + checkQueue.defer((res, rej) => { + this.runChecks('none', node, options, res, rej); + }); checkQueue - .then(function (results) { + .then(function(results) { if (results.length) { - var hasResults = false, result = {}; - results.forEach(function (r) { - var res = r.results.filter(function (result) { + var hasResults = false, + result = {}; + results.forEach(function(r) { + var res = r.results.filter(function(result) { return result; }); result[r.type] = res; @@ -187,11 +196,14 @@ Rule.prototype.run = function (context, options, resolve, reject) { if (options.performanceTimer) { axe.utils.performanceTimer.mark(markEnd); - axe.utils.performanceTimer.measure('runchecks_' + this.id, markStart, markEnd); + axe.utils.performanceTimer.measure( + 'runchecks_' + this.id, + markStart, + markEnd + ); } - q.then(() => resolve(ruleResult)) - .catch(error => reject(error)); + q.then(() => resolve(ruleResult)).catch(error => reject(error)); }; /** @@ -203,10 +215,13 @@ Rule.prototype.run = function (context, options, resolve, reject) { function findAfterChecks(rule) { 'use strict'; - return axe.utils.getAllChecks(rule).map(function (c) { - var check = rule._audit.checks[c.id || c]; - return (check && typeof check.after === 'function') ? check : null; - }).filter(Boolean); + return axe.utils + .getAllChecks(rule) + .map(function(c) { + var check = rule._audit.checks[c.id || c]; + return check && typeof check.after === 'function' ? check : null; + }) + .filter(Boolean); } /** @@ -220,9 +235,9 @@ function findCheckResults(nodes, checkID) { 'use strict'; var checkResults = []; - nodes.forEach(function (nodeResult) { + nodes.forEach(function(nodeResult) { var checks = axe.utils.getAllChecks(nodeResult); - checks.forEach(function (checkResult) { + checks.forEach(function(checkResult) { if (checkResult.id === checkID) { checkResults.push(checkResult); } @@ -234,7 +249,7 @@ function findCheckResults(nodes, checkID) { function filterChecks(checks) { 'use strict'; - return checks.filter(function (check) { + return checks.filter(function(check) { return check.filtered !== true; }); } @@ -243,9 +258,9 @@ function sanitizeNodes(result) { 'use strict'; var checkTypes = ['any', 'all', 'none']; - var nodes = result.nodes.filter(function (detail) { + var nodes = result.nodes.filter(function(detail) { var length = 0; - checkTypes.forEach(function (type) { + checkTypes.forEach(function(type) { detail[type] = filterChecks(detail[type]); length += detail[type].length; }); @@ -253,14 +268,16 @@ function sanitizeNodes(result) { }); if (result.pageLevel && nodes.length) { - nodes = [nodes.reduce(function (a, b) { - if (a) { - checkTypes.forEach(function (type) { - a[type].push.apply(a[type], b[type]); - }); - return a; - } - })]; + nodes = [ + nodes.reduce(function(a, b) { + if (a) { + checkTypes.forEach(function(type) { + a[type].push.apply(a[type], b[type]); + }); + return a; + } + }) + ]; } return nodes; } @@ -271,17 +288,17 @@ function sanitizeNodes(result) { * @param {Mixed} options Options specific to the rule * @return {RuleResult} The RuleResult as filtered by after functions */ -Rule.prototype.after = function (result, options) { +Rule.prototype.after = function(result, options) { 'use strict'; var afterChecks = findAfterChecks(this); var ruleID = this.id; - afterChecks.forEach(function (check) { + afterChecks.forEach(function(check) { var beforeResults = findCheckResults(result.nodes, check.id); var option = axe.utils.getCheckOption(check, ruleID, options); var afterResults = check.after(beforeResults, option); - beforeResults.forEach(function (item) { + beforeResults.forEach(function(item) { if (afterResults.indexOf(item) === -1) { item.filtered = true; } @@ -296,7 +313,7 @@ Rule.prototype.after = function (result, options) { * Reconfigure a rule after it has been added * @param {Object} spec - the attributes to be reconfigured */ -Rule.prototype.configure = function (spec) { +Rule.prototype.configure = function(spec) { /*eslint complexity:["error",14], max-statements:["error",22], no-eval:0 */ 'use strict'; @@ -305,7 +322,8 @@ Rule.prototype.configure = function (spec) { } if (spec.hasOwnProperty('excludeHidden')) { - this.excludeHidden = typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; + this.excludeHidden = + typeof spec.excludeHidden === 'boolean' ? spec.excludeHidden : true; } if (spec.hasOwnProperty('enabled')) { @@ -313,7 +331,8 @@ Rule.prototype.configure = function (spec) { } if (spec.hasOwnProperty('pageLevel')) { - this.pageLevel = typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; + this.pageLevel = + typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; } if (spec.hasOwnProperty('any')) { diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index e230590bd0..e3eb753e69 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -1,30 +1,30 @@ /* global reporters */ function configureChecksRulesAndBranding(spec) { - /*eslint - max-statements: ["error",30] - */ + /*eslint max-statements: ["error",20]*/ 'use strict'; var audit; audit = axe._audit; - if (!audit) { throw new Error('No audit configured'); } - if (spec.reporter && (typeof spec.reporter === 'function' || reporters[spec.reporter])) { + if ( + spec.reporter && + (typeof spec.reporter === 'function' || reporters[spec.reporter]) + ) { audit.reporter = spec.reporter; } if (spec.checks) { - spec.checks.forEach(function (check) { + spec.checks.forEach(function(check) { audit.addCheck(check); }); } const modifiedRules = []; if (spec.rules) { - spec.rules.forEach(function (rule) { + spec.rules.forEach(function(rule) { modifiedRules.push(rule.id); audit.addRule(rule); }); @@ -47,7 +47,6 @@ function configureChecksRulesAndBranding(spec) { if (spec.tagExclude) { audit.tagExclude = spec.tagExclude; } - } axe.configure = configureChecksRulesAndBranding; diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index 61323ffdd2..173b8aaaef 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -2,7 +2,7 @@ /*exported runRules */ // Clean up after resolve / reject -function cleanup () { +function cleanup() { axe._tree = undefined; axe._selectorData = undefined; } @@ -17,7 +17,6 @@ function cleanup () { */ function runRules(context, options, resolve, reject) { 'use strict'; - try { context = new Context(context); axe._tree = context.flatTree; @@ -35,21 +34,25 @@ function runRules(context, options, resolve, reject) { } if (context.frames.length && options.iframes !== false) { - q.defer(function (res, rej) { - axe.utils.collectResultsFromFrames(context, options, 'rules', null, res, rej); + q.defer(function(res, rej) { + axe.utils.collectResultsFromFrames( + context, + options, + 'rules', + null, + res, + rej + ); }); } - let scrollState; - - q.defer(function (res, rej) { + q.defer(function(res, rej) { if (options.restoreScroll) { scrollState = axe.utils.getScrollState(); } audit.run(context, options, res, rej); }); - - q.then(function (data) { + q.then(function(data) { try { if (scrollState) { axe.utils.setScrollState(scrollState); @@ -59,9 +62,11 @@ function runRules(context, options, resolve, reject) { } // Add wrapper object so that we may use the same "merge" function for results from inside and outside frames - var results = axe.utils.mergeResults(data.map(function (results) { - return { results }; - })); + var results = axe.utils.mergeResults( + data.map(function(results) { + return { results }; + }) + ); // after should only run once, so ensure we are in the top level window if (context.initiator) { @@ -70,10 +75,9 @@ function runRules(context, options, resolve, reject) { results.forEach(axe.utils.publishMetaData); results = results.map(axe.utils.finalizeRuleResult); } - try { resolve(results, cleanup); - } catch(e) { + } catch (e) { cleanup(); axe.log(e); } @@ -81,7 +85,7 @@ function runRules(context, options, resolve, reject) { cleanup(); reject(e); } - }).catch((e) => { + }).catch(e => { cleanup(); reject(e); }); diff --git a/lib/core/public/run.js b/lib/core/public/run.js index f5094ed59b..bf83f27055 100644 --- a/lib/core/public/run.js +++ b/lib/core/public/run.js @@ -1,14 +1,10 @@ /* global Promise */ -/*eslint -indent: 0, -complexity:["error", 12] -*/ +/*eslint indent: 0, complexity:["error", 12]*/ function isContext(potential) { 'use strict'; - switch (true) { - case (typeof potential === 'string'): - case (Array.isArray(potential)): + case typeof potential === 'string': + case Array.isArray(potential): case Node && potential instanceof Node: case NodeList && potential instanceof NodeList: return true; @@ -26,7 +22,7 @@ function isContext(potential) { } } -var noop = function () { }; +var noop = function() {}; /** * Normalize the optional params of axe.run() @@ -37,8 +33,7 @@ var noop = function () { }; */ function normalizeRunParams(context, options, callback) { 'use strict'; - - const typeErr = new TypeError('axe.run arguments are invalid'); + let typeErr = new TypeError('axe.run arguments are invalid'); // Determine the context if (!isContext(context)) { @@ -75,7 +70,6 @@ function normalizeRunParams(context, options, callback) { }; } - /** * Runs a number of rules against the provided HTML page and returns the * resulting issue list @@ -87,12 +81,9 @@ function normalizeRunParams(context, options, callback) { * - Results The results object / array, or undefined on error * @return {Promise} Resolves with the axe results. Only available when natively supported */ -axe.run = function (context, options, callback) { - /*eslint - max-statements:["error",25] - */ +axe.run = function(context, options, callback) { + /*eslint max-statements:["error",18] */ 'use strict'; - if (!axe._audit) { throw new Error('No audit configured'); } @@ -108,47 +99,51 @@ axe.run = function (context, options, callback) { if (options.performanceTimer) { axe.utils.performanceTimer.start(); } - let p; let reject = noop; let resolve = noop; if (typeof Promise === 'function' && callback === noop) { - p = new Promise(function (_resolve, _reject) { + p = new Promise(function(_resolve, _reject) { reject = _reject; resolve = _resolve; }); } - axe._runRules(context, options, function (rawResults, cleanup) { - let respond = function (results) { - cleanup(); - try { - callback(null, results); - } catch (e) { - axe.log(e); + axe._runRules( + context, + options, + function(rawResults, cleanup) { + let respond = function(results) { + cleanup(); + try { + callback(null, results); + } catch (e) { + axe.log(e); + } + resolve(results); + }; + if (options.performanceTimer) { + axe.utils.performanceTimer.end(); } - resolve(results); - }; - if (options.performanceTimer) { - axe.utils.performanceTimer.end(); - } - try { - let reporter = axe.getReporter(options.reporter); - let results = reporter(rawResults, options, respond); - if (results !== undefined) { - respond(results); + try { + let reporter = axe.getReporter(options.reporter); + let results = reporter(rawResults, options, respond); + if (results !== undefined) { + respond(results); + } + } catch (err) { + cleanup(); + callback(err); + reject(err); } - } catch (err) { - cleanup(); + }, + function(err) { callback(err); reject(err); } - }, function (err) { - callback(err); - reject(err); - }); + ); return p; }; diff --git a/lib/core/utils/get-check-option.js b/lib/core/utils/get-check-option.js index c1d27f563e..f170996d6b 100644 --- a/lib/core/utils/get-check-option.js +++ b/lib/core/utils/get-check-option.js @@ -1,5 +1,4 @@ -/* eslint max-statements: ["error", 25], complexity: ["error", 15] */ - +/*eslint complexity: ["error", 12]*/ /** * Determines which CheckOption to use, either defined on the rule options, global check options or the check itself * @param {Check} check The Check object @@ -7,14 +6,13 @@ * @param {Object} options Options object as passed to main API * @return {Object} The resolved object with `options` and `enabled` keys */ -axe.utils.getCheckOption = function (check, ruleID, options) { - - const ruleCheckOption = ((options.rules && options.rules[ruleID] || {}).checks || {})[check.id]; - const checkOption = (options.checks || {})[check.id]; - const preloadConfig = axe.utils.preloadConfig(options); +axe.utils.getCheckOption = function(check, ruleID, options) { + var ruleCheckOption = (((options.rules && options.rules[ruleID]) || {}) + .checks || {})[check.id]; + var checkOption = (options.checks || {})[check.id]; - let enabled = check.enabled; - let opts = check.options || {}; + var enabled = check.enabled; + var opts = check.options; if (checkOption) { if (checkOption.hasOwnProperty('enabled')) { @@ -34,13 +32,6 @@ axe.utils.getCheckOption = function (check, ruleID, options) { } } - if (preloadConfig.preload) { - opts = { - ...opts, - preloadedAssets: options.preloadedAssets - } - } - return { enabled: enabled, options: opts, diff --git a/test/core/base/audit.js b/test/core/base/audit.js index e900ea0e29..6beedf1ce4 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -1,83 +1,95 @@ /*global Audit, Rule */ -describe('Audit', function () { +describe('Audit', function() { 'use strict'; var a, getFlattenedTree; - var isNotCalled = function (err) { + var isNotCalled = function(err) { throw err || new Error('Reject should not be called'); }; - var noop = function () { }; + var noop = function() {}; - var mockChecks = [{ - id: 'positive1-check1', - evaluate: function () { - return true; - } - }, { - id: 'positive2-check1', - evaluate: function () { - return true; - } - }, { - id: 'negative1-check1', - evaluate: function () { - return true; + var mockChecks = [ + { + id: 'positive1-check1', + evaluate: function() { + return true; + } + }, + { + id: 'positive2-check1', + evaluate: function() { + return true; + } + }, + { + id: 'negative1-check1', + evaluate: function() { + return true; + } + }, + { + id: 'positive3-check1', + evaluate: function() { + return true; + } } - }, { - id: 'positive3-check1', - evaluate: function () { - return true; + ]; + + var mockRules = [ + { + id: 'positive1', + selector: 'input', + tags: ['positive'], + any: [ + { + id: 'positive1-check1' + } + ] + }, + { + id: 'positive2', + selector: '#monkeys', + tags: ['positive'], + any: ['positive2-check1'] + }, + { + id: 'negative1', + selector: 'div', + tags: ['negative'], + none: ['negative1-check1'] + }, + { + id: 'positive3', + selector: 'blink', + tags: ['positive'], + any: ['positive3-check1'] } - }]; - - var mockRules = [{ - id: 'positive1', - selector: 'input', - tags: ['positive'], - any: [{ - id: 'positive1-check1', - }] - }, { - id: 'positive2', - selector: '#monkeys', - tags: ['positive'], - any: ['positive2-check1'] - }, { - id: 'negative1', - selector: 'div', - tags: ['negative'], - none: ['negative1-check1'] - }, { - id: 'positive3', - selector: 'blink', - tags: ['positive'], - any: ['positive3-check1'] - }]; + ]; var fixture = document.getElementById('fixture'); - beforeEach(function () { + beforeEach(function() { a = new Audit(); - mockRules.forEach(function (r) { + mockRules.forEach(function(r) { a.addRule(r); }); - mockChecks.forEach(function (c) { + mockChecks.forEach(function(c) { a.addCheck(c); }); getFlattenedTree = axe.utils.getFlattenedTree; }); - afterEach(function () { + afterEach(function() { fixture.innerHTML = ''; axe._tree = undefined; axe._selectCache = undefined; axe.utils.getFlattenedTree = getFlattenedTree; }); - it('should be a function', function () { + it('should be a function', function() { assert.isFunction(Audit); }); - describe('Audit#_constructHelpUrls', function () { - it('should create default help URLS', function () { + describe('Audit#_constructHelpUrls', function() { + it('should create default help URLS', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -88,10 +100,11 @@ describe('Audit', function () { assert.equal(audit.data.rules.target, undefined); audit._constructHelpUrls(); assert.deepEqual(audit.data.rules.target, { - helpUrl: 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' + helpUrl: + 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' }); }); - it('should use changed branding', function () { + it('should use changed branding', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -103,10 +116,11 @@ describe('Audit', function () { audit.brand = 'thing'; audit._constructHelpUrls(); assert.deepEqual(audit.data.rules.target, { - helpUrl: 'https://dequeuniversity.com/rules/thing/x.y/target?application=axeAPI' + helpUrl: + 'https://dequeuniversity.com/rules/thing/x.y/target?application=axeAPI' }); }); - it('should use changed application', function () { + it('should use changed application', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -118,18 +132,20 @@ describe('Audit', function () { audit.application = 'thing'; audit._constructHelpUrls(); assert.deepEqual(audit.data.rules.target, { - helpUrl: 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' + helpUrl: + 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' }); }); - it('does not override helpUrls of different products', function () { + it('does not override helpUrls of different products', function() { var audit = new Audit(); audit.addRule({ id: 'target1', matches: 'function () {return "hello";}', selector: 'bob', metadata: { - helpUrl: 'https://dequeuniversity.com/rules/myproject/x.y/target1?application=axeAPI' + helpUrl: + 'https://dequeuniversity.com/rules/myproject/x.y/target1?application=axeAPI' } }); audit.addRule({ @@ -157,7 +173,7 @@ describe('Audit', function () { 'https://dequeuniversity.com/rules/thing/x.y/target2?application=axeAPI' ); }); - it('understands prerelease type version numbers', function () { + it('understands prerelease type version numbers', function() { var tempVersion = axe.version; var audit = new Audit(); audit.addRule({ @@ -170,10 +186,12 @@ describe('Audit', function () { audit._constructHelpUrls(); axe.version = tempVersion; - assert.equal(audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/3.2/target?application=axeAPI'); + assert.equal( + audit.data.rules.target.helpUrl, + 'https://dequeuniversity.com/rules/axe/3.2/target?application=axeAPI' + ); }); - it('sets x.y as version for invalid versions', function () { + it('sets x.y as version for invalid versions', function() { var tempVersion = axe.version; var audit = new Audit(); audit.addRule({ @@ -186,10 +204,12 @@ describe('Audit', function () { audit._constructHelpUrls(); axe.version = tempVersion; - assert.equal(audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI'); + assert.equal( + audit.data.rules.target.helpUrl, + 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' + ); }); - it('matches major release versions', function () { + it('matches major release versions', function() { var tempVersion = axe.version; var audit = new Audit(); audit.addRule({ @@ -202,13 +222,15 @@ describe('Audit', function () { audit._constructHelpUrls(); axe.version = tempVersion; - assert.equal(audit.data.rules.target.helpUrl, - 'https://dequeuniversity.com/rules/axe/1.0/target?application=axeAPI'); + assert.equal( + audit.data.rules.target.helpUrl, + 'https://dequeuniversity.com/rules/axe/1.0/target?application=axeAPI' + ); }); }); - describe('Audit#setBranding', function () { - it('should change the brand', function () { + describe('Audit#setBranding', function() { + it('should change the brand', function() { var audit = new Audit(); assert.equal(audit.brand, 'axe'); assert.equal(audit.application, 'axeAPI'); @@ -218,7 +240,7 @@ describe('Audit', function () { assert.equal(audit.brand, 'thing'); assert.equal(audit.application, 'axeAPI'); }); - it('should change the application', function () { + it('should change the application', function() { var audit = new Audit(); assert.equal(audit.brand, 'axe'); assert.equal(audit.application, 'axeAPI'); @@ -228,7 +250,7 @@ describe('Audit', function () { assert.equal(audit.brand, 'axe'); assert.equal(audit.application, 'thing'); }); - it('should call _constructHelpUrls', function () { + it('should call _constructHelpUrls', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -241,10 +263,11 @@ describe('Audit', function () { application: 'thing' }); assert.deepEqual(audit.data.rules.target, { - helpUrl: 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' + helpUrl: + 'https://dequeuniversity.com/rules/axe/x.y/target?application=thing' }); }); - it('should call _constructHelpUrls even when nothing changed', function () { + it('should call _constructHelpUrls even when nothing changed', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -255,17 +278,19 @@ describe('Audit', function () { assert.equal(audit.data.rules.target, undefined); audit.setBranding(undefined); assert.deepEqual(audit.data.rules.target, { - helpUrl: 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' + helpUrl: + 'https://dequeuniversity.com/rules/axe/x.y/target?application=axeAPI' }); }); - it('should not replace custom set branding', function () { + it('should not replace custom set branding', function() { var audit = new Audit(); audit.addRule({ id: 'target', matches: 'function () {return "hello";}', selector: 'bob', metadata: { - helpUrl: 'https://dequeuniversity.com/rules/customer-x/x.y/target?application=axeAPI' + helpUrl: + 'https://dequeuniversity.com/rules/customer-x/x.y/target?application=axeAPI' } }); audit.setBranding({ @@ -276,13 +301,11 @@ describe('Audit', function () { audit.data.rules.target.helpUrl, 'https://dequeuniversity.com/rules/customer-x/x.y/target?application=axeAPI' ); - }); }); - - describe('Audit#addRule', function () { - it('should override existing rule', function () { + describe('Audit#addRule', function() { + it('should override existing rule', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -302,7 +325,7 @@ describe('Audit', function () { assert.equal(audit.rules[0].selector, 'fred'); assert.equal(audit.rules[0].matches(), 'hello'); }); - it('should otherwise push new rule', function () { + it('should otherwise push new rule', function() { var audit = new Audit(); audit.addRule({ id: 'target', @@ -321,11 +344,10 @@ describe('Audit', function () { assert.equal(audit.rules[1].id, 'target2'); assert.equal(audit.rules[1].selector, 'fred'); }); - }); - describe('Audit#resetRulesAndChecks', function () { - it('should override newly created check', function () { + describe('Audit#resetRulesAndChecks', function() { + it('should override newly created check', function() { var audit = new Audit(); assert.equal(audit.checks.target, undefined); audit.addCheck({ @@ -339,8 +361,8 @@ describe('Audit', function () { }); }); - describe('Audit#addCheck', function () { - it('should create a new check', function () { + describe('Audit#addCheck', function() { + it('should create a new check', function() { var audit = new Audit(); assert.equal(audit.checks.target, undefined); audit.addCheck({ @@ -350,7 +372,7 @@ describe('Audit', function () { assert.ok(audit.checks.target); assert.equal(audit.checks.target.options, 'jane'); }); - it('should configure the metadata, if passed', function () { + it('should configure the metadata, if passed', function() { var audit = new Audit(); assert.equal(audit.checks.target, undefined); audit.addCheck({ @@ -360,13 +382,13 @@ describe('Audit', function () { assert.ok(audit.checks.target); assert.equal(audit.data.checks.target.guy, 'bob'); }); - it('should reconfigure existing check', function () { + it('should reconfigure existing check', function() { var audit = new Audit(); - var myTest = function () { }; + var myTest = function() {}; audit.addCheck({ id: 'target', evaluate: myTest, - options: 'jane', + options: 'jane' }); assert.equal(audit.checks.target.options, 'jane'); @@ -379,7 +401,7 @@ describe('Audit', function () { assert.equal(audit.checks.target.evaluate, myTest); assert.equal(audit.checks.target.options, 'fred'); }); - it('should not turn messages into a function', function () { + it('should not turn messages into a function', function() { var audit = new Audit(); var spec = { id: 'target', @@ -397,7 +419,7 @@ describe('Audit', function () { assert.equal(audit.data.checks.target.messages.fail, 'it failed'); }); - it('should turn function strings into a function', function () { + it('should turn function strings into a function', function() { var audit = new Audit(); var spec = { id: 'target', @@ -414,106 +436,143 @@ describe('Audit', function () { assert.equal(typeof audit.data.checks.target.messages.fail, 'function'); assert.equal(audit.data.checks.target.messages.fail(), 'it failed'); }); - }); - describe('Audit#run', function () { - it('should run all the rules', function (done) { - fixture.innerHTML = '' + + describe('Audit#run', function() { + it('should run all the rules', function(done) { + fixture.innerHTML = + '' + '
bananas
' + '' + 'FAIL ME'; - a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, {}, function (results) { - var expected = [{ - id: 'positive1', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, { - id: 'positive2', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, { - id: 'negative1', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }, { - id: 'positive3', - result: 'inapplicable', - pageLevel: false, - impact: null, - nodes: '...other tests cover this...' - }]; - - var out = results[0].nodes[0].node.source; - results.forEach(function (res) { - // attribute order is a pain in the lower back in IE, so we're not - // comparing nodes. Check.run and Rule.run do this. - res.nodes = '...other tests cover this...'; - }); - - assert.deepEqual(JSON.parse(JSON.stringify(results)), expected); - assert.match(out, /^/); - done(); - }, isNotCalled); + a.run( + { include: [axe.utils.getFlattenedTree(fixture)[0]] }, + {}, + function(results) { + var expected = [ + { + id: 'positive1', + result: 'inapplicable', + pageLevel: false, + impact: null, + nodes: '...other tests cover this...' + }, + { + id: 'positive2', + result: 'inapplicable', + pageLevel: false, + impact: null, + nodes: '...other tests cover this...' + }, + { + id: 'negative1', + result: 'inapplicable', + pageLevel: false, + impact: null, + nodes: '...other tests cover this...' + }, + { + id: 'positive3', + result: 'inapplicable', + pageLevel: false, + impact: null, + nodes: '...other tests cover this...' + } + ]; + + var out = results[0].nodes[0].node.source; + results.forEach(function(res) { + // attribute order is a pain in the lower back in IE, so we're not + // comparing nodes. Check.run and Rule.run do this. + res.nodes = '...other tests cover this...'; + }); + + assert.deepEqual(JSON.parse(JSON.stringify(results)), expected); + assert.match( + out, + /^/ + ); + done(); + }, + isNotCalled + ); }); - it('should not run rules disabled by the options', function (done) { - a.run({ include: [document] }, { - rules: { - 'positive3': { - enabled: false + it('should not run rules disabled by the options', function(done) { + a.run( + { include: [document] }, + { + rules: { + positive3: { + enabled: false + } } - } - }, function (results) { - assert.equal(results.length, 3); - done(); - }, isNotCalled); + }, + function(results) { + assert.equal(results.length, 3); + done(); + }, + isNotCalled + ); }); - it('should assign an empty array to axe._selectCache', function (done) { + it('should assign an empty array to axe._selectCache', function(done) { var saved = axe.utils.ruleShouldRun; - axe.utils.ruleShouldRun = function () { + axe.utils.ruleShouldRun = function() { assert.equal(axe._selectCache.length, 0); return false; }; - a.run({ include: [document] }, {}, function () { - axe.utils.ruleShouldRun = saved; - done(); - }, isNotCalled); - }); - it('should clear axe._selectCache', function (done) { - a.run({ include: [document] }, { - rules: {} - }, function () { - assert.isTrue(typeof axe._selectCache === 'undefined'); - done(); - }, isNotCalled); - }); - it('should not run rules disabled by the configuration', function (done) { + a.run( + { include: [document] }, + {}, + function() { + axe.utils.ruleShouldRun = saved; + done(); + }, + isNotCalled + ); + }); + it('should clear axe._selectCache', function(done) { + a.run( + { include: [document] }, + { + rules: {} + }, + function() { + assert.isTrue(typeof axe._selectCache === 'undefined'); + done(); + }, + isNotCalled + ); + }); + it('should not run rules disabled by the configuration', function(done) { var a = new Audit(); var success = true; - a.rules.push(new Rule({ - id: 'positive1', - selector: '*', - enabled: false, - any: [{ - id: 'positive1-check1', - evaluate: function () { - success = false; - } - }] - })); - a.run({ include: [document] }, {}, function () { - assert.ok(success); - done(); - }, isNotCalled); - }); - it('should call the rule\'s run function', function (done) { + a.rules.push( + new Rule({ + id: 'positive1', + selector: '*', + enabled: false, + any: [ + { + id: 'positive1-check1', + evaluate: function() { + success = false; + } + } + ] + }) + ); + a.run( + { include: [document] }, + {}, + function() { + assert.ok(success); + done(); + }, + isNotCalled + ); + }); + it("should call the rule's run function", function(done) { var targetRule = mockRules[mockRules.length - 1], rule = axe.utils.findBy(a.rules, 'id', targetRule.id), called = false, @@ -521,17 +580,22 @@ describe('Audit', function () { fixture.innerHTML = 'link'; orig = rule.run; - rule.run = function (node, options, callback) { + rule.run = function(node, options, callback) { called = true; callback({}); }; - a.run({ include: [document] }, {}, function () { - assert.isTrue(called); - rule.run = orig; - done(); - }, isNotCalled); + a.run( + { include: [document] }, + {}, + function() { + assert.isTrue(called); + rule.run = orig; + done(); + }, + isNotCalled + ); }); - it('should pass the option to the run function', function (done) { + it('should pass the option to the run function', function(done) { var targetRule = mockRules[mockRules.length - 1], rule = axe.utils.findBy(a.rules, 'id', targetRule.id), passed = false, @@ -540,151 +604,186 @@ describe('Audit', function () { fixture.innerHTML = 'link'; orig = rule.run; - rule.run = function (node, o, callback) { - assert.property(o, 'rules'); + rule.run = function(node, o, callback) { + assert.deepEqual(o, options); passed = true; callback({}); }; - options = { rules: {} }; - (options.rules[targetRule.id] = {}).data = 'monkeys'; - a.run({ include: [document] }, options, function () { - assert.ok(passed); - rule.run = orig; - done(); - }, isNotCalled); + a.run( + { include: [document] }, + options, + function() { + assert.ok(passed); + rule.run = orig; + done(); + }, + isNotCalled + ); }); - it('should skip pageLevel rules if context is not set to entire page', function () { + it('should skip pageLevel rules if context is not set to entire page', function() { var audit = new Audit(); - audit.rules.push(new Rule({ - pageLevel: true, - enabled: true, - evaluate: function () { - assert.ok(false, 'Should not run'); - } - })); - - audit.run({ include: [document.body], page: false }, {}, function (results) { - assert.deepEqual(results, []); - }, isNotCalled); + audit.rules.push( + new Rule({ + pageLevel: true, + enabled: true, + evaluate: function() { + assert.ok(false, 'Should not run'); + } + }) + ); + audit.run( + { include: [document.body], page: false }, + {}, + function(results) { + assert.deepEqual(results, []); + }, + isNotCalled + ); }); - it('catches errors and passes them as a cantTell result', function (done) { + it('catches errors and passes them as a cantTell result', function(done) { var err = new Error('Launch the super sheep!'); a.addRule({ id: 'throw1', selector: '*', - any: [{ - id: 'throw1-check1', - }] + any: [ + { + id: 'throw1-check1' + } + ] }); a.addCheck({ id: 'throw1-check1', - evaluate: function () { + evaluate: function() { throw err; } }); - a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, { - runOnly: { - 'type': 'rule', - 'values': ['throw1'] - } - }, function (results) { - assert.lengthOf(results, 1); - assert.equal(results[0].result, 'cantTell'); - assert.equal(results[0].message, err.message); - assert.equal(results[0].stack, err.stack); - assert.equal(results[0].error, err); - done(); - }, isNotCalled); + a.run( + { include: [axe.utils.getFlattenedTree(fixture)[0]] }, + { + runOnly: { + type: 'rule', + values: ['throw1'] + } + }, + function(results) { + assert.lengthOf(results, 1); + assert.equal(results[0].result, 'cantTell'); + assert.equal(results[0].message, err.message); + assert.equal(results[0].stack, err.stack); + assert.equal(results[0].error, err); + done(); + }, + isNotCalled + ); }); - it('should not halt if errors occur', function (done) { + it('should not halt if errors occur', function(done) { a.addRule({ id: 'throw1', selector: '*', - any: [{ - id: 'throw1-check1', - }] + any: [ + { + id: 'throw1-check1' + } + ] }); a.addCheck({ id: 'throw1-check1', - evaluate: function () { + evaluate: function() { throw new Error('Launch the super sheep!'); } }); - a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, { - runOnly: { - 'type': 'rule', - 'values': ['throw1', 'positive1'] - } - }, function () { - done(); - }, isNotCalled); + a.run( + { include: [axe.utils.getFlattenedTree(fixture)[0]] }, + { + runOnly: { + type: 'rule', + values: ['throw1', 'positive1'] + } + }, + function() { + done(); + }, + isNotCalled + ); }); - it('should run audit.normalizeOptions to ensure valid input', function () { - fixture.innerHTML = '' + + it('should run audit.normalizeOptions to ensure valid input', function() { + fixture.innerHTML = + '' + '
bananas
' + '' + 'FAIL ME'; var checked = 'options not validated'; - a.normalizeOptions = function () { + a.normalizeOptions = function() { checked = 'options validated'; }; a.run({ include: [fixture] }, {}, noop, isNotCalled); assert.equal(checked, 'options validated'); }); - it('should halt if an error occurs when debug is set', function (done) { + it('should halt if an error occurs when debug is set', function(done) { a.addRule({ id: 'throw1', selector: '*', - any: [{ - id: 'throw1-check1', - }] + any: [ + { + id: 'throw1-check1' + } + ] }); a.addCheck({ id: 'throw1-check1', - evaluate: function () { + evaluate: function() { throw new Error('Launch the super sheep!'); } }); - a.run({ include: [axe.utils.getFlattenedTree(fixture)[0]] }, { - debug: true, - runOnly: { - 'type': 'rule', - 'values': ['throw1'] + a.run( + { include: [axe.utils.getFlattenedTree(fixture)[0]] }, + { + debug: true, + runOnly: { + type: 'rule', + values: ['throw1'] + } + }, + noop, + function(err) { + assert.equal(err.message, 'Launch the super sheep!'); + done(); } - }, noop, function (err) { - assert.equal(err.message, 'Launch the super sheep!'); - done(); - }); + ); }); }); - describe('Audit#after', function () { - it('should run Rule#after on any rule whose result is passed in', function () { + describe('Audit#after', function() { + it('should run Rule#after on any rule whose result is passed in', function() { /*eslint no-unused-vars:0*/ var audit = new Audit(); var success = false; var options = [{ id: 'hehe', enabled: true, monkeys: 'bananas' }]; - var results = [{ - id: 'hehe', - monkeys: 'bananas' - }]; - audit.rules.push(new Rule({ - id: 'hehe', - pageLevel: false, - enabled: false - })); - - audit.rules[0].after = function (res, opts) { + var results = [ + { + id: 'hehe', + monkeys: 'bananas' + } + ]; + audit.rules.push( + new Rule({ + id: 'hehe', + pageLevel: false, + enabled: false + }) + ); + + audit.rules[0].after = function(res, opts) { assert.equal(res, results[0]); assert.deepEqual(opts, options); success = true; @@ -694,9 +793,8 @@ describe('Audit', function () { }); }); - describe('Audit#normalizeOptions', function () { - - it('returns the options object when it is valid', function () { + describe('Audit#normalizeOptions', function() { + it('returns the options object when it is valid', function() { var opt = { runOnly: { type: 'rule', @@ -709,19 +807,19 @@ describe('Audit', function () { assert(a.normalizeOptions(opt), opt); }); - it('allows `value` as alternative to `values`', function () { + it('allows `value` as alternative to `values`', function() { var opt = { runOnly: { type: 'rule', value: ['positive1', 'positive2'] } }; - var out = a.normalizeOptions(opt) + var out = a.normalizeOptions(opt); assert.deepEqual(out.runOnly.values, ['positive1', 'positive2']); assert.isUndefined(out.runOnly.value); }); - it('allows type: rules as an alternative to type: rule', function () { + it('allows type: rules as an alternative to type: rule', function() { var opt = { runOnly: { type: 'rules', @@ -731,7 +829,7 @@ describe('Audit', function () { assert(a.normalizeOptions(opt).runOnly.type, 'rule'); }); - it('allows type: tags as an alternative to type: tag', function () { + it('allows type: tags as an alternative to type: tag', function() { var opt = { runOnly: { type: 'tags', @@ -741,7 +839,7 @@ describe('Audit', function () { assert(a.normalizeOptions(opt).runOnly.type, 'tag'); }); - it('allows type: undefined as an alternative to type: tag', function () { + it('allows type: undefined as an alternative to type: tag', function() { var opt = { runOnly: { values: ['positive'] @@ -750,15 +848,15 @@ describe('Audit', function () { assert(a.normalizeOptions(opt).runOnly.type, 'tag'); }); - it('allows runOnly as an array as an alternative to type: tag', function () { + it('allows runOnly as an array as an alternative to type: tag', function() { var opt = { runOnly: ['positive', 'negative'] }; var out = a.normalizeOptions(opt); assert(out.runOnly.type, 'tag'); assert.deepEqual(out.runOnly.values, ['positive', 'negative']); }); - it('throws an error runOnly.values not an array', function () { - assert.throws(function () { + it('throws an error runOnly.values not an array', function() { + assert.throws(function() { a.normalizeOptions({ runOnly: { type: 'rule', @@ -768,8 +866,8 @@ describe('Audit', function () { }); }); - it('throws an error runOnly.values an empty', function () { - assert.throws(function () { + it('throws an error runOnly.values an empty', function() { + assert.throws(function() { a.normalizeOptions({ runOnly: { type: 'rule', @@ -779,8 +877,8 @@ describe('Audit', function () { }); }); - it('throws an error runOnly.type is unknown', function () { - assert.throws(function () { + it('throws an error runOnly.type is unknown', function() { + assert.throws(function() { a.normalizeOptions({ runOnly: { type: 'something-else', @@ -790,8 +888,8 @@ describe('Audit', function () { }); }); - it('throws an error when option.runOnly has an unknown rule', function () { - assert.throws(function () { + it('throws an error when option.runOnly has an unknown rule', function() { + assert.throws(function() { a.normalizeOptions({ runOnly: { type: 'rule', @@ -801,8 +899,8 @@ describe('Audit', function () { }); }); - it('throws an error when option.runOnly has an unknown tag', function () { - assert.throws(function () { + it('throws an error when option.runOnly has an unknown tag', function() { + assert.throws(function() { a.normalizeOptions({ runOnly: { type: 'tags', @@ -812,8 +910,8 @@ describe('Audit', function () { }); }); - it('throws an error when option.rules has an unknown rule', function () { - assert.throws(function () { + it('throws an error when option.rules has an unknown rule', function() { + assert.throws(function() { a.normalizeOptions({ rules: { fakeRule: { enabled: false } @@ -821,7 +919,5 @@ describe('Audit', function () { }); }); }); - }); - }); diff --git a/test/core/public/run.js b/test/core/public/run.js index d2766cda61..49140cffd1 100644 --- a/test/core/public/run.js +++ b/test/core/public/run.js @@ -1,34 +1,38 @@ -describe('axe.run', function () { +describe('axe.run', function() { 'use strict'; var fixture = document.getElementById('fixture'); - var noop = function () { }; + var noop = function() {}; var origRunRules = axe._runRules; - beforeEach(function () { + beforeEach(function() { axe._load({ - rules: [{ - id: 'test', - selector: '*', - none: ['fred'] - }], - checks: [{ - id: 'fred', - evaluate: function (node) { - this.relatedNodes([node]); - return true; + rules: [ + { + id: 'test', + selector: '*', + none: ['fred'] } - }] + ], + checks: [ + { + id: 'fred', + evaluate: function(node) { + this.relatedNodes([node]); + return true; + } + } + ] }); }); - afterEach(function () { + afterEach(function() { fixture.innerHTML = ''; axe._audit = null; axe._runRules = origRunRules; }); - it('takes context, options and callback as parameters', function (done) { + it('takes context, options and callback as parameters', function(done) { fixture.innerHTML = '
'; var options = { runOnly: { @@ -37,14 +41,14 @@ describe('axe.run', function () { } }; - axe.run(['#t1'], options, function () { + axe.run(['#t1'], options, function() { assert.ok(true, 'test completed'); done(); }); }); - it('uses document as content if it is not specified', function (done) { - axe._runRules = function (ctxt) { + it('uses document as content if it is not specified', function(done) { + axe._runRules = function(ctxt) { assert.equal(ctxt, document); done(); }; @@ -52,23 +56,23 @@ describe('axe.run', function () { axe.run({ someOption: true }, noop); }); - it('uses an object as options if it is not specified', function (done) { - axe._runRules = function (ctxt, opt) { + it('uses an object as options if it is not specified', function(done) { + axe._runRules = function(ctxt, opt) { assert.isObject(opt); done(); }; axe.run(document, noop); }); - it('works with performance logging enabled', function (done) { - axe.run(document, { performanceTimer: true }, function (err, result) { + it('works with performance logging enabled', function(done) { + axe.run(document, { performanceTimer: true }, function(err, result) { assert.isObject(result); done(); }); }); - it('treats objects with include or exclude as the context object', function (done) { - axe._runRules = function (ctxt) { + it('treats objects with include or exclude as the context object', function(done) { + axe._runRules = function(ctxt) { assert.deepEqual(ctxt, { include: '#BoggyB' }); done(); }; @@ -76,8 +80,8 @@ describe('axe.run', function () { axe.run({ include: '#BoggyB' }, noop); }); - it('treats objects with neither include or exclude as the option object', function (done) { - axe._runRules = function (ctxt, opt) { + it('treats objects with neither include or exclude as the option object', function(done) { + axe._runRules = function(ctxt, opt) { assert.deepEqual(opt.HHG, 'hallelujah'); done(); }; @@ -85,19 +89,19 @@ describe('axe.run', function () { axe.run({ HHG: 'hallelujah' }, noop); }); - it('does not fail if no callback is specified', function (done) { - assert.doesNotThrow(function () { + it('does not fail if no callback is specified', function(done) { + assert.doesNotThrow(function() { axe.run(done); }); }); - it('should clear axe._tree', function (done) { + it('should clear axe._tree', function(done) { var getFlattenedTree = axe.utils.getFlattenedTree; var thing = 'honey badger'; - axe.utils.getFlattenedTree = function () { + axe.utils.getFlattenedTree = function() { return thing; }; - axe._runRules = function () { + axe._runRules = function() { assert.isTrue(typeof axe._tree === 'undefined'); axe.utils.getFlattenedTree = getFlattenedTree; done(); @@ -106,47 +110,47 @@ describe('axe.run', function () { axe.run({ someOption: true }, noop); }); - describe('callback', function () { - it('gives errors to the first argument on the callback', function (done) { - axe._runRules = function (ctxt, opt, resolve, reject) { + describe('callback', function() { + it('gives errors to the first argument on the callback', function(done) { + axe._runRules = function(ctxt, opt, resolve, reject) { axe._runRules = origRunRules; reject('Ninja rope!'); }; - axe.run({ reporter: 'raw' }, function (err) { + axe.run({ reporter: 'raw' }, function(err) { assert.equal(err, 'Ninja rope!'); done(); }); }); - it('gives results to the second argument on the callback', function (done) { - axe._runRules = function (ctxt, opt, resolve) { + it('gives results to the second argument on the callback', function(done) { + axe._runRules = function(ctxt, opt, resolve) { axe._runRules = origRunRules; resolve('MB Bomb', noop); }; - axe.run({ reporter: 'raw' }, function (err, result) { + axe.run({ reporter: 'raw' }, function(err, result) { assert.equal(err, null); assert.equal(result, 'MB Bomb'); done(); }); }); - it('does not run the callback twice if it throws', function (done) { + it('does not run the callback twice if it throws', function(done) { var calls = 0; - axe._runRules = function (ctxt, opt, resolve) { + axe._runRules = function(ctxt, opt, resolve) { resolve([], noop); }; var log = axe.log; - axe.log = function (e) { + axe.log = function(e) { assert.equal(e.message, 'err'); axe.log = log; }; - axe.run(function () { + axe.run(function() { calls += 1; if (calls === 1) { - setTimeout(function () { + setTimeout(function() { assert.equal(calls, 1); axe.log = log; done(); @@ -156,9 +160,9 @@ describe('axe.run', function () { }); }); - it('is called after cleanup', function (done) { + it('is called after cleanup', function(done) { var isClean = false; - axe._runRules = function (ctxt, opt, resolve) { + axe._runRules = function(ctxt, opt, resolve) { axe._runRules = origRunRules; // Check that cleanup is called before the callback is executed resolve('MB Bomb', function cleanup() { @@ -166,42 +170,40 @@ describe('axe.run', function () { }); }; - axe.run({ reporter: 'raw' }, function () { + axe.run({ reporter: 'raw' }, function() { assert.isTrue(isClean, 'cleanup must be called first'); done(); }); }); }); - - describe('promise result', function () { + describe('promise result', function() { /*eslint indent: 0*/ var promiseIt = window.Promise ? it : it.skip; - promiseIt('returns an error to catch if axe fails', function (done) { - axe._runRules = function (ctxt, opt, resolve, reject) { + promiseIt('returns an error to catch if axe fails', function(done) { + axe._runRules = function(ctxt, opt, resolve, reject) { axe._runRules = origRunRules; reject('I surrender!'); }; var p = axe.run({ reporter: 'raw' }); - p.then(noop) - .catch(function (err) { - assert.equal(err, 'I surrender!'); - done(); - }); + p.then(noop).catch(function(err) { + assert.equal(err, 'I surrender!'); + done(); + }); assert.instanceOf(p, window.Promise); }); - promiseIt('returns a promise if no callback was given', function (done) { - axe._runRules = function (ctxt, opt, resolve) { + promiseIt('returns a promise if no callback was given', function(done) { + axe._runRules = function(ctxt, opt, resolve) { axe._runRules = origRunRules; resolve('World party', noop); }; var p = axe.run({ reporter: 'raw' }); - p.then(function (result) { + p.then(function(result) { assert.equal(result, 'World party'); done(); }); @@ -209,27 +211,31 @@ describe('axe.run', function () { assert.instanceOf(p, window.Promise); }); - promiseIt('does not error if then() throws', function (done) { - axe._runRules = function (ctxt, opt, resolve) { + promiseIt('does not error if then() throws', function(done) { + axe._runRules = function(ctxt, opt, resolve) { resolve([], noop); }; - axe.run() - .then(function () { - throw new Error('err'); - }, function (e) { - assert.isNotOk(e, 'Caught callback error in the wrong place'); - done(); - - }).catch(function (e) { + axe + .run() + .then( + function() { + throw new Error('err'); + }, + function(e) { + assert.isNotOk(e, 'Caught callback error in the wrong place'); + done(); + } + ) + .catch(function(e) { assert.equal(e.message, 'err'); done(); }); }); - promiseIt('is called after cleanup', function (done) { + promiseIt('is called after cleanup', function(done) { var isClean = false; - axe._runRules = function (ctxt, opt, resolve) { + axe._runRules = function(ctxt, opt, resolve) { axe._runRules = origRunRules; // Check that cleanup is called before the callback is executed resolve('MB Bomb', function cleanup() { @@ -237,8 +243,9 @@ describe('axe.run', function () { }); }; - axe.run({ reporter: 'raw' }) - .then(function () { + axe + .run({ reporter: 'raw' }) + .then(function() { assert(isClean, 'cleanup must be called first'); done(); }) @@ -246,10 +253,9 @@ describe('axe.run', function () { }); }); - - describe('option reporter', function () { - it('sets v1 as the default reporter if audit.reporter is null', function (done) { - axe._runRules = function (ctxt, opt) { + describe('option reporter', function() { + it('sets v1 as the default reporter if audit.reporter is null', function(done) { + axe._runRules = function(ctxt, opt) { assert.equal(opt.reporter, 'v1'); axe._runRules = origRunRules; done(); @@ -258,8 +264,8 @@ describe('axe.run', function () { axe.run(document, noop); }); - it('uses the audit.reporter if no reporter is set in options', function (done) { - axe._runRules = function (ctxt, opt) { + it('uses the audit.reporter if no reporter is set in options', function(done) { + axe._runRules = function(ctxt, opt) { assert.equal(opt.reporter, 'raw'); axe._runRules = origRunRules; done(); @@ -268,8 +274,8 @@ describe('axe.run', function () { axe.run(document, noop); }); - it('does not override if another reporter is set', function (done) { - axe._runRules = function (ctxt, opt) { + it('does not override if another reporter is set', function(done) { + axe._runRules = function(ctxt, opt) { assert.equal(opt.reporter, 'raw'); axe._runRules = origRunRules; done(); @@ -279,103 +285,119 @@ describe('axe.run', function () { }); }); - - describe('option xpath', function () { - it('returns no xpath if the xpath option is not set', function (done) { - axe.run('#fixture', function (err, result) { + describe('option xpath', function() { + it('returns no xpath if the xpath option is not set', function(done) { + axe.run('#fixture', function(err, result) { assert.isUndefined(result.violations[0].nodes[0].xpath); done(); }); }); - it('returns the xpath if the xpath option is true', function (done) { - axe.run('#fixture', { - xpath: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); + it('returns the xpath if the xpath option is true', function(done) { + axe.run( + '#fixture', + { + xpath: true + }, + function(err, result) { + assert.deepEqual(result.violations[0].nodes[0].xpath, [ + "/div[@id='fixture']" + ]); + done(); + } + ); }); - it('returns xpath on related nodes', function (done) { - axe.run('#fixture', { - xpath: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].none[0].relatedNodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); + it('returns xpath on related nodes', function(done) { + axe.run( + '#fixture', + { + xpath: true + }, + function(err, result) { + assert.deepEqual( + result.violations[0].nodes[0].none[0].relatedNodes[0].xpath, + ["/div[@id='fixture']"] + ); + done(); + } + ); }); - it('returns the xpath on any reporter', function (done) { - axe.run('#fixture', { - xpath: true, - reporter: 'no-passes' - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].xpath, - ['/div[@id=\'fixture\']'] - ); - done(); - }); + it('returns the xpath on any reporter', function(done) { + axe.run( + '#fixture', + { + xpath: true, + reporter: 'no-passes' + }, + function(err, result) { + assert.deepEqual(result.violations[0].nodes[0].xpath, [ + "/div[@id='fixture']" + ]); + done(); + } + ); }); }); - describe('option absolutePaths', function () { - - it('returns relative paths when falsy', function (done) { - axe.run('#fixture', { - absolutePaths: 0 - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].target, - ['#fixture'] - ); - done(); - }); + describe('option absolutePaths', function() { + it('returns relative paths when falsy', function(done) { + axe.run( + '#fixture', + { + absolutePaths: 0 + }, + function(err, result) { + assert.deepEqual(result.violations[0].nodes[0].target, ['#fixture']); + done(); + } + ); }); - it('returns absolute paths when truthy', function (done) { - axe.run('#fixture', { - absolutePaths: 'yes please' - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].target, - ['html > body > #fixture'] - ); - done(); - }); + it('returns absolute paths when truthy', function(done) { + axe.run( + '#fixture', + { + absolutePaths: 'yes please' + }, + function(err, result) { + assert.deepEqual(result.violations[0].nodes[0].target, [ + 'html > body > #fixture' + ]); + done(); + } + ); }); - it('returns absolute paths on related nodes', function (done) { - axe.run('#fixture', { - absolutePaths: true - }, function (err, result) { - assert.deepEqual( - result.violations[0].nodes[0].none[0].relatedNodes[0].target, - ['html > body > #fixture'] - ); - done(); - }); + it('returns absolute paths on related nodes', function(done) { + axe.run( + '#fixture', + { + absolutePaths: true + }, + function(err, result) { + assert.deepEqual( + result.violations[0].nodes[0].none[0].relatedNodes[0].target, + ['html > body > #fixture'] + ); + done(); + } + ); }); }); - describe('option restoreScroll', function () { - it('does not change scroll when restoreScroll is not set', function (done) { + describe('option restoreScroll', function() { + it('does not change scroll when restoreScroll is not set', function(done) { var calls = 0; var _getSS = axe.utils.getScrollState; var _setSS = axe.utils.setScrollState; - axe.utils.setScrollState = function () { + axe.utils.setScrollState = function() { calls++; }; axe.utils.getScrollState = axe.utils.setScrollState; - axe.run('#fixture', {}, function () { + axe.run('#fixture', {}, function() { assert.equal(calls, 0); axe.utils.getScrollState = _getSS; axe.utils.setScrollState = _setSS; @@ -383,82 +405,99 @@ describe('axe.run', function () { }); }); - it('resets scrolLState after running the audit', function (done) { + it('resets scrolLState after running the audit', function(done) { var scrollState = {}; var calls = 0; var _getSS = axe.utils.getScrollState; var _setSS = axe.utils.setScrollState; - axe.utils.setScrollState = function (arg) { + axe.utils.setScrollState = function(arg) { assert.equal(scrollState, arg); calls++; }; - axe.utils.getScrollState = function () { + axe.utils.getScrollState = function() { return scrollState; }; - axe.run('#fixture', { - restoreScroll: true - }, function () { - assert.equal(calls, 1); - axe.utils.getScrollState = _getSS; - axe.utils.setScrollState = _setSS; - done(); - }); + axe.run( + '#fixture', + { + restoreScroll: true + }, + function() { + assert.equal(calls, 1); + axe.utils.getScrollState = _getSS; + axe.utils.setScrollState = _setSS; + done(); + } + ); }); }); }); -describe('axe.run iframes', function () { +describe('axe.run iframes', function() { 'use strict'; var fixture = document.getElementById('fixture'); var origRunRules = axe._runRules; - beforeEach(function () { + beforeEach(function() { fixture.innerHTML = '
Target in top frame
'; axe._load({ - rules: [{ - id: 'html', - selector: '#target', - none: ['fred'] - }], - checks: [{ - id: 'fred', - evaluate: function () { - return true; + rules: [ + { + id: 'html', + selector: '#target', + none: ['fred'] + } + ], + checks: [ + { + id: 'fred', + evaluate: function() { + return true; + } } - }] + ] }); }); - afterEach(function () { + afterEach(function() { fixture.innerHTML = ''; axe._audit = null; axe._runRules = origRunRules; }); - it('includes iframes by default', function (done) { + it('includes iframes by default', function(done) { var frame = document.createElement('iframe'); - frame.addEventListener('load', function () { - var safetyTimeout = window.setTimeout(function () { + frame.addEventListener('load', function() { + var safetyTimeout = window.setTimeout(function() { done(); }, 1000); - axe.run('#fixture', {}, function (err, result) { - assert.isDefined(result); + axe.run('#fixture', {}, function(err, result) { assert.equal(result.violations.length, 1); var violation = result.violations[0]; - assert.equal(violation.nodes.length, 2, - 'one node for top frame, one for iframe'); - assert.isTrue(violation.nodes.some(function (node) { - return node.target.length === 1 && node.target[0] === '#target'; - }), 'one result from top frame'); - assert.isTrue(violation.nodes.some(function (node) { - return node.target.length === 2 && - node.target[0] === '#fixture > iframe'; - }), 'one result from iframe'); + assert.equal( + violation.nodes.length, + 2, + 'one node for top frame, one for iframe' + ); + assert.isTrue( + violation.nodes.some(function(node) { + return node.target.length === 1 && node.target[0] === '#target'; + }), + 'one result from top frame' + ); + assert.isTrue( + violation.nodes.some(function(node) { + return ( + node.target.length === 2 && node.target[0] === '#fixture > iframe' + ); + }), + 'one result from iframe' + ); window.clearTimeout(safetyTimeout); done(); }); @@ -468,19 +507,18 @@ describe('axe.run iframes', function () { fixture.appendChild(frame); }); - it('excludes iframes if iframes is false', function (done) { + it('excludes iframes if iframes is false', function(done) { var frame = document.createElement('iframe'); - frame.addEventListener('load', function () { - var safetyTimeout = setTimeout(function () { + frame.addEventListener('load', function() { + var safetyTimeout = setTimeout(function() { done(); }, 1000); - axe.run('#fixture', { iframes: false }, function (err, result) { + axe.run('#fixture', { iframes: false }, function(err, result) { assert.equal(result.violations.length, 1); var violation = result.violations[0]; - assert.equal(violation.nodes.length, 1, - 'only top frame'); + assert.equal(violation.nodes.length, 1, 'only top frame'); assert.equal(violation.nodes[0].target.length, 1); assert.equal(violation.nodes[0].target[0], '#target'); window.clearTimeout(safetyTimeout); diff --git a/test/sandbox-preload.css b/test/sandbox-preload.css deleted file mode 100644 index ae684b0e37..0000000000 --- a/test/sandbox-preload.css +++ /dev/null @@ -1,4 +0,0 @@ -html, -body { - font-size: inherit; -} \ No newline at end of file diff --git a/test/sandbox-preload.html b/test/sandbox-preload.html deleted file mode 100644 index 4522c08b41..0000000000 --- a/test/sandbox-preload.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - Sandbox::Preload - - - - - - - - - - - - - - - - \ No newline at end of file From 1434751766e31d9e6b59eb07292666b4d6d2542e Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 2 Jul 2018 12:45:07 +0100 Subject: [PATCH 25/43] fix: preload changes based on review --- lib/core/constants.js | 2 +- lib/core/utils/preload-config.js | 63 ---------- lib/core/utils/preload-cssom.js | 172 ++++++++++++++------------ lib/core/utils/preload.js | 147 ++++++++++++++++++---- test/core/utils/preload-config.js | 80 ------------ test/core/utils/preload-cssom.js | 189 ++++++++++++++++++----------- test/core/utils/preload.js | 130 +++++++++++++++++--- test/core/utils/xhr-q.js | 84 ++++++------- test/integration/rules/runner.tmpl | 1 - test/runner.tmpl | 1 - 10 files changed, 494 insertions(+), 375 deletions(-) delete mode 100644 lib/core/utils/preload-config.js delete mode 100644 test/core/utils/preload-config.js diff --git a/lib/core/constants.js b/lib/core/constants.js index 7243caa745..e31a210a1b 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -33,7 +33,7 @@ resultGroupMap: {}, impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']), preloadAssets: Object.freeze(['cssom']), - preloadAssetsTimeout: 30000 + preloadAssetsTimeout: 10000 }; definitions.forEach(function(definition) { diff --git a/lib/core/utils/preload-config.js b/lib/core/utils/preload-config.js deleted file mode 100644 index 6935702e2f..0000000000 --- a/lib/core/utils/preload-config.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Construct a configuration object representing the preload requested assets - * @param {Object} options run configuration options (or defaults) passed via axe.run - * @return {Object} - */ -axe.utils.preloadConfig = (options) => { - - /** - * Possible values for `preload`: - * true | false (default) | { assets: ['cssom'], timeout: 30000 (optional) } - */ - const p = options && options.preload - ? options.preload - : false; - - // default fallback configuration - let out = { - preload: false, - assets: axe.constants.preloadAssets, - timeout: p && p.timeout - ? Number(p.timeout) - : axe.constants.preloadAssetsTimeout - }; - - if (p) { - // by type is boolean - if (typeof (p) === typeof (true)) { - out.preload = p; - } else { - // if type is object - ensure an array of assets to load is specified - if (p.hasOwnProperty('assets') && - Array.isArray(p.assets) && - p.assets.length) { - - out.preload = true; - out.assets = p.assets - .map((a) => { - if (axe.constants.preloadAssets.includes(a)) { - return a; - } else { - const e = `Requested asset: ${a}, not supported by aXe.` + - `Supported assets are: ${axe.constants.preloadAssets.map(_ => _).join(', ')}.`; - console.error(e); - throw new Error(e); - } - }) - .reduce((out, asset) => { - const a = asset.toLowerCase(); - if (!out.includes(a)) { // unique assets to load, incase user had requested same asset type many times. - out.push(a); - } - return out; - }, []); - } else { - const e = 'No assets configured for preload in aXe run configuration'; - console.error(e); - throw new Error(e); - } - } - } - - return out; -} \ No newline at end of file diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 66b23f4bcb..0a87e9fdbe 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -1,104 +1,120 @@ /** - * Returns a CSSStyleSheet object for a given style sheet content/ text - * @param {string} cssText content for the stylesheet - * @return {CSSStyleSheet} + * Returns a then(able) queue of CSSStyleSheet(s) + * @param {Object} ownerDocument document object to be inspected for stylesheets + * @param {number} timeout on network request for stylesheet that need to be externally fetched + * @param {Function} getSheetFromTextFn a utility function to generate a style sheet from text + * @return {Object} queue * @private */ -const getCssSheet = (cssText) => { - let htmlHead = document.implementation.createHTMLDocument().head; - - // create style node with css text - let style = document.createElement('style'); - style.type = 'text/css'; - style.appendChild(document.createTextNode(cssText)); +function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { + const q = axe.utils.queue(); - // added style to temporary document - htmlHead.appendChild(style); + Array.from(ownerDocument.styleSheets).forEach(sheet => { + if (sheet.disabled) { + return; + } - // cleanup style and temporary document after return - setTimeout(() => { - htmlHead.removeChild(style); - htmlHead = undefined; + try { + // attempt to resolve if sheet is relative/ same domain + sheet.cssRules && q.defer(resolve => resolve(sheet)); + } catch (e) { + const deferredSheet = (resolve, reject) => { + axe.utils + .xhrQ({ + url: sheet.href, + timeout + }) + .then(xhrResponse => { + if ( + xhrResponse && + Array.isArray(xhrResponse) && + xhrResponse.length + ) { + xhrResponse.forEach(r => { + const text = r.responseText ? r.responseText : r.response; + const href = r.responseURL; + const sheet = getSheetFromTextFn(text, href); + resolve(sheet); + }); + } + }) + .catch(reject); + }; + // external sheet -> make an xhr and q the response + q.defer(deferredSheet); + } }); - return style.sheet; + return q; } /** - * Returns a then(able) queue of CSSStyleSheet(s) - * @param {Object} ownerDocument document object to be inspected for all stylesheets - * @param {number} timeout for stylesheet that are external and needs to be fetched, a timeout on the network call - * @return {Object} + * Returns an array of documents with a given root node/ tree + * @param {Object} treeRoot - the DOM tree to be inspected + * @return {Array} documents * @private */ -const loadCssom = (ownerDocument, timeout) => { - return [...ownerDocument.styleSheets] - .reduce((out, sheet) => { - if (!sheet.disabled) { - try { - // attempt to resolve if sheet is relative/ same domain - sheet.cssRules && out.defer((res) => res(sheet)); - } - catch (e) { - // external sheet -> make an xhr and q the response - out.defer((res, rej) => { - axe.utils - .xhrQ({ - url: sheet.href, - timeout - }) - .then((xhrResponse) => { - if (xhrResponse && Array.isArray(xhrResponse) && xhrResponse.length) { - xhrResponse.forEach((r) => { - const sheet = getCssSheet(r.responseText ? r.responseText : r.response); - res(sheet) - }) - } - }) - .catch(rej); - }) - } +function getDocumentsFromTreeRoot(treeRoot) { + let ids = []; + const documents = axe.utils + .querySelectorAllFilter(treeRoot, '*', node => { + if (ids.includes(node.shadowId)) { + return false; } - return out; - }, axe.utils.queue()); + ids.push(node.shadowId); + return true; + }) + .map(node => { + return node.actualNode.ownerDocument; + }); + return documents; } /** * @method preloadCssom * @memberof axe.utils * @instance - * @param {Object} object with attributes asset, timeout, treeRoot(optional) + * @param {Object} object argument which is a composite object, with attributes asset, timeout, treeRoot(optional), resolve & reject * asset - type of asset being loaded, in this case cssom * timeout - timeout for any network calls made * treeRoot - the DOM tree to be inspected + * resolve/ reject - promise chainable methods * @return {Object} */ -axe.utils.preloadCssom = ({ - asset, - timeout, - treeRoot = axe._tree[0] -}) => { - let ids = []; - const documents = axe.utils - .querySelectorAllFilter(treeRoot, '*', (node) => { - if (ids.includes(node.shadowId)) { - return false; - } - ids.push(node.shadowId) - return true; - }) - .map((node) => { - return node.actualNode.ownerDocument; +function preloadCssom({ asset, timeout, treeRoot = axe._tree[0] }) { + const documents = getDocumentsFromTreeRoot(treeRoot); + const q = axe.utils.queue(); + + if (!documents.length) { + return q; + } + + const getSheetFromTextFn = (function() { + let htmlHead = document.implementation.createHTMLDocument().head; + return (cssText, href) => { + // create style node with css text + let style = document.createElement('style'); + style.type = 'text/css'; + style.href = href; + style.appendChild(document.createTextNode(cssText)); + // added style to temporary document + htmlHead.appendChild(style); + return style.sheet; + }; + })(); // invoke immediately + + documents.forEach(doc => { + q.defer((resolve, reject) => { + loadCssom(doc, timeout, getSheetFromTextFn) + .then(sheets => + resolve({ + [asset]: sheets + }) + ) + .catch(reject); }); - ids = undefined; + }); - return documents - .reduce((out, ownerDocument) => { - out.defer((res, rej) => { - loadCssom(ownerDocument, timeout) - .then((sheets) => res({ [asset]: sheets })) - .catch(rej) - }); - return out; - }, axe.utils.queue()); -}; \ No newline at end of file + return q; +} +axe.utils.preloadCssom = preloadCssom; diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index b76a217a9b..ebbb6d600d 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -1,37 +1,136 @@ /** - * Returns a then(able) queue with results of all requested preload(able) assets. Eg: ['cssom']. - * If preload is set to false, returns an empty queue. + * Validated the preload object + * @param {Object | boolean} preload configuration object or boolean passed via the options parameter to axe.run + * @return {boolean} + * @private + */ +function isPreloadValidObject(preload) { + return ( + typeof preload === 'object' && + preload.hasOwnProperty('assets') && + Array.isArray(preload.assets) && + preload.assets.length + ); +} + +/** + * Returns a boolean which decides if preload is configured + * @param {Object} options run configuration options (or defaults) passed via axe.run + * @return {boolean} + */ +function shouldPreload(options) { + if (!options) { + return false; + } + + if (!options.preload) { + return false; + } + + if (typeof options.preload === typeof true) { + return options.preload; + } + + if (isPreloadValidObject(options.preload)) { + return true; + } + + return false; +} +axe.utils.shouldPreload = shouldPreload; + +/** + * Constructs a configuration object representing the preload requested assets & timeout * @param {Object} options run configuration options (or defaults) passed via axe.run * @return {Object} */ -axe.utils.preload = (options) => { +function getPreloadConfig(options) { + // default fallback configuration + let out = { + assets: axe.constants.preloadAssets, + timeout: axe.constants.preloadAssetsTimeout + }; + + // if type is boolean + if (typeof options.preload === typeof true) { + return out; + } + + // if type is object - ensure an array of assets to load is specified + if (isPreloadValidObject(options.preload)) { + const requestedAssets = []; + options.preload.assets.forEach(asset => { + const a = asset.toLowerCase(); + if (axe.constants.preloadAssets.includes(a)) { + // unique assets to load, in case user had requested same asset type many times. + if (!requestedAssets.includes(a)) { + requestedAssets.push(a); + } + return a; + } else { + const e = + `Requested asset: ${a}, not supported by aXe.` + + `Supported assets are: ${axe.constants.preloadAssets + .map(_ => _) + .join(', ')}.`; + throw new Error(e); + } + }); + out.assets = requestedAssets; + if (options.preload.timeout) { + if ( + typeof options.preload.timeout === 'number' && + !Number.isNaN(options.preload.timeout) + ) { + out.timeout = options.preload.timeout; + } else { + throw new Error(`preload timeout specified is not of type number`); + } + } + + return out; + } else { + throw new Error( + 'No assets configured for preload in aXe run configuration' + ); + } +} +axe.utils.getPreloadConfig = getPreloadConfig; + +/** + * Returns a then(able) queue with results of all requested preload(able) assets. Eg: ['cssom']. + * If preload is set to false, returns an empty queue. + * @param {Object} options run configuration options (or defaults) passed via axe.run + * @return {Object} queue + */ +function preload(options) { const preloadFunctionsMap = { - 'cssom': axe.utils.preloadCssom + cssom: axe.utils.preloadCssom }; const q = axe.utils.queue(); - const preloadConfig = axe.utils.preloadConfig(options); - - if (preloadConfig.preload) { - preloadConfig.assets.forEach((asset) => { - q.defer((res, rej) => { - preloadFunctionsMap[asset]({ asset, timeout: preloadConfig.timeout }) - .then((result) => { - res( - result.reduce((out, asset) => { - return { - ...out, - ...asset - }; - }, {}) - ); - }) - .catch(rej) - }); - }); + const shouldPreload = axe.utils.shouldPreload(options); + if (!shouldPreload) { + return q; } + const preloadConfig = axe.utils.getPreloadConfig(options); + + preloadConfig.assets.forEach(asset => { + q.defer((resolve, reject) => { + preloadFunctionsMap[asset]({ + asset, + timeout: preloadConfig.timeout + }) + .then(results => { + resolve(results[0]); + }) + .catch(reject); + }); + }); + return q; -} \ No newline at end of file +} +axe.utils.preload = preload; diff --git a/test/core/utils/preload-config.js b/test/core/utils/preload-config.js deleted file mode 100644 index 2617774d89..0000000000 --- a/test/core/utils/preload-config.js +++ /dev/null @@ -1,80 +0,0 @@ -describe('axe.utils.preloadConfig', function () { - 'use strict'; - - it('should be a function', function () { - assert.isFunction(axe.utils.preloadConfig); - }); - - it('should return default preload configuration if no preload options', function () { - var actual = axe.utils.preloadConfig({}); - var expected = { - preload: false, - assets: ['cssom'], - timeout: 30000 - }; - assert.deepEqual(actual, expected); - }); - - it('should return default preload value as false', function () { - var actual = axe.utils.preloadConfig({}).preload; - var expected = false; - assert.strictEqual(actual, expected); - }); - - it('should return default assets if preload options is set to true', function () { - var options = { - preload: true - }; - var actual = axe.utils.preloadConfig(options).assets; - var expected = ['cssom']; - assert.deepEqual(actual, expected); - }); - - it('should return default timeout value if not configured', function () { - var options = { - preload: true, - }; - var actual = axe.utils.preloadConfig(options).timeout; - var expected = 30000; - assert.equal(actual, expected); - }); - - it('should throw error if requested asset type is not supported', function () { - var options = { - preload: { - assets: ['aom'] - } - }; - var actual = function () { axe.utils.preloadConfig(options); } - var expected = Error; - assert.throws(actual, expected); - }); - - it('should throw error if assets array is empty in options for preload', function () { - var options = { - preload: { - assets: [] - } - }; - var actual = function () { axe.utils.preloadConfig(options); } - var expected = Error; - assert.throws(actual, expected); - }); - - it('should unique assets requested if repeated assets are passed via options', function () { - var options = { - preload: { - assets: ['cssom', 'cssom'], - timeout: 15000 - } - }; - var actual = axe.utils.preloadConfig(options); - var expected = { - preload: true, - assets: ['cssom'], - timeout: 15000 - }; - assert.deepEqual(actual, expected); - }); - -}); diff --git a/test/core/utils/preload-cssom.js b/test/core/utils/preload-cssom.js index 70ffc727b8..e4bf662784 100644 --- a/test/core/utils/preload-cssom.js +++ b/test/core/utils/preload-cssom.js @@ -1,120 +1,167 @@ -describe('axe.utils.preloadCssom', function () { +describe('axe.utils.preloadCssom', function() { 'use strict'; var fixture = document.getElementById('fixture'); - var pageTpl = ''; + var pageTpl = + ''; + var args; - afterEach(function () { + beforeEach(function() { + args = { + asset: 'cssom', + timeout: 10000 + }; + }); + + afterEach(function() { fixture.innerHTML = ''; - axe._tree = undefined; }); - it('should be a function', function () { + it('should be a function', function() { assert.isFunction(axe.utils.preloadCssom); }); - it('should return a queue', function () { + it('should return a queue', function() { fixture.innerHTML = pageTpl; - var tree = axe._tree = axe.utils.getFlattenedTree(fixture); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); assert.isObject(actual); + assert.containsAllKeys(actual, ['then', 'defer', 'catch']); }); - it('should ensure queue is defer(able)', function (done) { + it('should ensure result has cssom property', function(done) { fixture.innerHTML = pageTpl; - var tree = axe._tree = axe.utils.getFlattenedTree(fixture); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); actual - .defer((function (res, rej) { - res(true); - assert.isFunction(rej); - assert.isOk(true); + .then(function(results) { + var r = results[0]; + assert.property(r, 'cssom'); done(); - })); + }) + .catch(function(error) { + done(error); + }); }); - it('should ensure queue is then(able)', function (done) { + it('should ensure result of cssom returned is an array of sheets', function(done) { fixture.innerHTML = pageTpl; - var tree = axe._tree = axe.utils.getFlattenedTree(fixture); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); actual - .then((function (results) { - assert.isDefined(results); - assert.isOk(true); + .then(function(results) { + var sheets = results[0].cssom; + assert.isTrue(Array.isArray(sheets)); + assert.lengthOf(sheets, 2); done(); - })); + }) + .catch(function(error) { + done(error); + }); }); - it('should ensure result has cssom property', function (done) { + it('should ensure all returned stylesheet is defined and has readable sheet/ cssrules', function(done) { fixture.innerHTML = pageTpl; - var tree = axe._tree = axe.utils.getFlattenedTree(fixture); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); actual - .then((function (results) { - var r = results[0]; - assert.property(r, 'cssom'); + .then(function(results) { + var sheets = results[0].cssom; + sheets.forEach(function(s) { + assert.isDefined(s); + assert.property(s, 'cssRules'); + }); done(); - })); + }) + .catch(function(error) { + done(error); + }); }); - it('should ensure result is an array', function (done) { - fixture.innerHTML = pageTpl; - var target = fixture.children[0]; - var tree = axe._tree = axe.utils.getFlattenedTree(target); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + it('should ignore disabled stylesheets', function(done) { + var page = + ' '; + fixture.innerHTML = page; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); actual - .then((function (results) { + .then(function(results) { var sheets = results[0].cssom; - assert.isTrue(Array.isArray(sheets)); - assert.lengthOf(sheets, 2); + assert.lengthOf(sheets, 1); // the 1 stylesheet fetched is of the parent test runner page done(); - })); + }) + .catch(function(error) { + done(error); + }); }); - it('should ensure all returned stylesheet is defined and has readable sheet/ cssrules', function (done) { - fixture.innerHTML = pageTpl; - var target = fixture.children[0]; - var tree = axe._tree = axe.utils.getFlattenedTree(target); - var args = { - asset: ['cssom'], - timeout: 30000, - treeRoot: tree - }; + var shadowSupported = axe.testUtils.shadowSupport.v1; + (shadowSupported ? it : xit)( + 'should fetch all shadow DOM stylesheets', + function(done) { + var content = + ''; + var shadow = fixture.attachShadow({ mode: 'open' }); + shadow.innerHTML = content; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; + var actual = axe.utils.preloadCssom(args); + actual + .then(function(results) { + var sheets = results[0].cssom; + assert.lengthOf(sheets, 1); + done(); + }) + .catch(function(error) { + done(error); + }); + } + ); + + it('should make xhr for external stylesheet', function(done) { + var page = + ' '; + fixture.innerHTML = page; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); actual - .then(function (results) { + .then(function(results) { var sheets = results[0].cssom; - sheets.forEach(function (s) { + assert.lengthOf(sheets, 1); + sheets.forEach(function(s) { assert.isDefined(s); + assert.property(s, 'href'); + assert.isDefined(s.href); assert.property(s, 'cssRules'); }); done(); + }) + .catch(function(error) { + done(error); }); }); -}); \ No newline at end of file + it('should return 2 stylesheets', function(done) { + var page = + ' '; + fixture.innerHTML = page; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); + args.treeRoot = tree; + var actual = axe.utils.preloadCssom(args); + actual + .then(function(results) { + var sheets = results[0].cssom; + assert.lengthOf(sheets, 2); + done(); + }) + .catch(function(error) { + done(error); + }); + }); +}); diff --git a/test/core/utils/preload.js b/test/core/utils/preload.js index 31b7c2ea93..57de425366 100644 --- a/test/core/utils/preload.js +++ b/test/core/utils/preload.js @@ -1,13 +1,11 @@ - - -describe('axe.utils.preload', function () { +describe('axe.utils.preload', function() { 'use strict'; - it('should be a function', function () { + it('should be a function', function() { assert.isFunction(axe.utils.preload); }); - it('should return a queue', function () { + it('should return a queue', function() { var options = { preload: true }; @@ -15,31 +13,135 @@ describe('axe.utils.preload', function () { assert.isObject(actual); }); - it('should ensure queue is defer(able)', function (done) { + it('should ensure queue is defer(able)', function(done) { var options = { preload: false }; var actual = axe.utils.preload(options); actual - .defer((function (res, rej) { + .defer(function(res, rej) { assert.isFunction(rej); res(true); - assert.isOk(true); done(); - })); + }) + .catch(function(error) { + done(error); + }); }); - it('should ensure queue is then(able)', function (done) { + it('should ensure queue is then(able)', function(done) { var options = { preload: false }; var actual = axe.utils.preload(options); actual - .then((function (results) { + .then(function(results) { + assert.isDefined(results); + done(); + }) + .catch(function(error) { + done(error); + }); + }); + + it('should return an object with property cssom', function(done) { + var options = { + preload: { + assets: ['cssom'] + } + }; + var actual = axe.utils.preload(options); + actual + .then(function(results) { assert.isDefined(results); - assert.isOk(true); + assert.isArray(results); + assert.property(results[0], 'cssom'); done(); - })); + }) + .catch(function(error) { + done(error); + }); }); -}); \ No newline at end of file + it('should return true if preload configuration is valid', function() { + var options = { + preload: true + }; + var actual = axe.utils.shouldPreload(options); + assert.isTrue(actual); + }); + + it('should return false if preload configuration is invalid', function() { + var options = { + preload: { + errorProperty: ['cssom'] + } + }; + var actual = axe.utils.shouldPreload(options); + assert.isFalse(actual); + }); + + it('should throw error if preload configuration is invalid', function() { + var actual = function() { + axe.utils.getPreloadConfig({}); + }; + var expected = Error; + assert.throws(actual, expected); + }); + + it('should return default assets if preload options is set to true', function() { + var options = { + preload: true + }; + var actual = axe.utils.getPreloadConfig(options).assets; + var expected = ['cssom']; + assert.deepEqual(actual, expected); + }); + + it('should return default timeout value if not configured', function() { + var options = { + preload: true + }; + var actual = axe.utils.getPreloadConfig(options).timeout; + var expected = 10000; + assert.equal(actual, expected); + }); + + it('should throw error if requested asset type is not supported', function() { + var options = { + preload: { + assets: ['aom'] + } + }; + var actual = function() { + axe.utils.getPreloadConfig(options); + }; + var expected = Error; + assert.throws(actual, expected); + }); + + it('should throw error if assets array is empty in options for preload', function() { + var options = { + preload: { + assets: [] + } + }; + var actual = function() { + axe.utils.getPreloadConfig(options); + }; + var expected = Error; + assert.throws(actual, expected); + }); + + it('should unique assets requested if repeated assets are passed via options', function() { + var options = { + preload: { + assets: ['cssom', 'cssom'] + } + }; + var actual = axe.utils.getPreloadConfig(options); + assert.property(actual, 'assets'); + assert.containsAllKeys(actual, ['assets', 'timeout']); + assert.lengthOf(actual.assets, 1); + }); +}); diff --git a/test/core/utils/xhr-q.js b/test/core/utils/xhr-q.js index c344f179ce..533e26ca9b 100644 --- a/test/core/utils/xhr-q.js +++ b/test/core/utils/xhr-q.js @@ -1,21 +1,21 @@ /*global sinon */ -describe('axe.utils.xhrQ', function () { +describe('axe.utils.xhrQ', function() { 'use strict'; - beforeEach(function () { + beforeEach(function() { this.xhr = sinon.useFakeXMLHttpRequest(); this.requests = []; - this.xhr.onCreate = function (xhr) { + this.xhr.onCreate = function(xhr) { this.requests.push(xhr); }.bind(this); }); - afterEach(function () { + afterEach(function() { this.xhr.restore(); }); - it('should reject queue on 500 error', function (done) { + it('should reject queue on 500 error', function(done) { var config = { url: '/kaBoom' }; @@ -25,25 +25,25 @@ describe('axe.utils.xhrQ', function () { dataJson: { message: 'some dummy data.' } }; - axe.utils.xhrQ(config) - .then(function () { + axe.utils + .xhrQ(config) + .then(function() { assert.fail('should not have resolved the queue'); done(); }) - .catch(function (err) { + .catch(function(err) { assert.equal(err.status, 500); done(); }); - this.requests[0] - .respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); + this.requests[0].respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); }); - it('should resolve queue for status 200', function (done) { + it('should resolve queue for status 200', function(done) { var config = { url: '/gotMe' }; @@ -53,26 +53,25 @@ describe('axe.utils.xhrQ', function () { dataJson: { message: 'some dummy data.' } }; - axe.utils.xhrQ(config) - .then(function (results) { + axe.utils + .xhrQ(config) + .then(function(results) { var response = results[0]; assert.equal(response.status, 200); done(); }) - .catch(function () { - assert.fail('should not have rejected the queue.') - done(); + .catch(function(error) { + done(error); }); - this.requests[0] - .respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); + this.requests[0].respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); }); - it('should populate response', function (done) { + it('should populate response', function(done) { var config = { url: '/gotMe' }; @@ -81,28 +80,29 @@ describe('axe.utils.xhrQ', function () { contentType: { 'Content-Type': 'text/json' }, dataJson: { status: 200, - responseText: 'My Expected Data!', + responseText: 'My Expected Data!' } }; - axe.utils.xhrQ(config) - .then(function (results) { + axe.utils + .xhrQ(config) + .then(function(results) { var response = results[0]; assert.isObject(response); - assert.deepEqual(JSON.parse(response.responseText), fakeResponse.dataJson); + assert.deepEqual( + JSON.parse(response.responseText), + fakeResponse.dataJson + ); done(); }) - .catch(function () { - assert.fail('should not have rejected the queue.'); - done(); + .catch(function(error) { + done(error); }); - this.requests[0] - .respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); + this.requests[0].respond( + fakeResponse.status, + fakeResponse.contentType, + JSON.stringify(fakeResponse.dataJson) + ); }); - -}); \ No newline at end of file +}); diff --git a/test/integration/rules/runner.tmpl b/test/integration/rules/runner.tmpl index 85009a643e..2b9e7b79c3 100644 --- a/test/integration/rules/runner.tmpl +++ b/test/integration/rules/runner.tmpl @@ -14,7 +14,6 @@ ui: 'bdd' }); var assert = chai.assert; - var sinon = sinon; var global = {}; <% files.forEach(function (file) { %> diff --git a/test/runner.tmpl b/test/runner.tmpl index 301677d7e4..d4d3666368 100644 --- a/test/runner.tmpl +++ b/test/runner.tmpl @@ -14,7 +14,6 @@ ui: 'bdd' }); var assert = chai.assert; - var sinon = sinon; var global = {}; <% files.forEach(function (file) { %> From b64309cecdaa322c7b3b69ca39b1ca62826fd095 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 2 Jul 2018 13:17:17 +0100 Subject: [PATCH 26/43] refactor: revert formatting changes --- lib/checks/aria/aria-hidden-body.js | 2 +- lib/checks/aria/aria-hidden-body.json | 2 +- lib/rules/aria-hidden-body.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/checks/aria/aria-hidden-body.js b/lib/checks/aria/aria-hidden-body.js index b46bd1095a..2cb98c9513 100644 --- a/lib/checks/aria/aria-hidden-body.js +++ b/lib/checks/aria/aria-hidden-body.js @@ -1 +1 @@ -return node.getAttribute('aria-hidden') !== 'true'; \ No newline at end of file +return node.getAttribute('aria-hidden') !== 'true'; diff --git a/lib/checks/aria/aria-hidden-body.json b/lib/checks/aria/aria-hidden-body.json index bad069615e..37012e3f1d 100644 --- a/lib/checks/aria/aria-hidden-body.json +++ b/lib/checks/aria/aria-hidden-body.json @@ -8,4 +8,4 @@ "fail": "aria-hidden=true should not be present on the document body" } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/lib/rules/aria-hidden-body.json b/lib/rules/aria-hidden-body.json index 6b7ee29476..bc9eeee34e 100644 --- a/lib/rules/aria-hidden-body.json +++ b/lib/rules/aria-hidden-body.json @@ -16,4 +16,4 @@ "aria-hidden-body" ], "none": [] -} \ No newline at end of file +} \ No newline at end of file From 5ea88d5a0108eb434765c1d8b33dcbe98c4ef977 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 2 Jul 2018 15:00:55 +0100 Subject: [PATCH 27/43] fix: update shadown dom test to not pollute fixture --- test/core/utils/preload-cssom.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/core/utils/preload-cssom.js b/test/core/utils/preload-cssom.js index e4bf662784..1f38aad48c 100644 --- a/test/core/utils/preload-cssom.js +++ b/test/core/utils/preload-cssom.js @@ -104,10 +104,12 @@ describe('axe.utils.preloadCssom', function() { (shadowSupported ? it : xit)( 'should fetch all shadow DOM stylesheets', function(done) { - var content = + fixture.innerHTML = '
'; + var target = document.querySelector('#target'); + var shadowRoot = target.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = ''; - var shadow = fixture.attachShadow({ mode: 'open' }); - shadow.innerHTML = content; + var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); From 28bdcdd04c4c90a85e322c6eabcf92def02134b4 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 2 Jul 2018 15:08:01 +0100 Subject: [PATCH 28/43] fix: markdown lint --- doc/API.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/API.md b/doc/API.md index bbc6eb1e94..e86ff80d7c 100644 --- a/doc/API.md +++ b/doc/API.md @@ -469,10 +469,7 @@ preload: true 2. Specifying an `object` ```js -preload: { - assets: ['cssom'], - timeout: 50000 -} +preload: { assets: ['cssom'], timeout: 50000 } ``` The `assets` attribute expects an array of preload(able) constraints to be fetched. From ae59586628f6fd40a0a6c76899582ee4482bda6f Mon Sep 17 00:00:00 2001 From: Jey Date: Wed, 4 Jul 2018 14:43:56 +0200 Subject: [PATCH 29/43] fix: refactor based on review --- lib/core/utils/preload-cssom.js | 10 ++--- lib/core/utils/preload.js | 72 +++++++++++++++++---------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 0a87e9fdbe..2536d5d931 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -15,7 +15,7 @@ function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { } try { - // attempt to resolve if sheet is relative/ same domain + // cannot use if/else here as the catch block won't be triggered (if passes for both relative and external sheets) sheet.cssRules && q.defer(resolve => resolve(sheet)); } catch (e) { const deferredSheet = (resolve, reject) => { @@ -90,15 +90,15 @@ function preloadCssom({ asset, timeout, treeRoot = axe._tree[0] }) { } const getSheetFromTextFn = (function() { - let htmlHead = document.implementation.createHTMLDocument().head; + const dynamicDoc = document.implementation.createHTMLDocument(); return (cssText, href) => { // create style node with css text - let style = document.createElement('style'); + const style = dynamicDoc.createElement('style'); style.type = 'text/css'; style.href = href; - style.appendChild(document.createTextNode(cssText)); + style.appendChild(dynamicDoc.createTextNode(cssText)); // added style to temporary document - htmlHead.appendChild(style); + dynamicDoc.head.appendChild(style); return style.sheet; }; })(); // invoke immediately diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index ebbb6d600d..55edac61fb 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -46,55 +46,59 @@ axe.utils.shouldPreload = shouldPreload; */ function getPreloadConfig(options) { // default fallback configuration - let out = { + const out = { assets: axe.constants.preloadAssets, timeout: axe.constants.preloadAssetsTimeout }; // if type is boolean - if (typeof options.preload === typeof true) { + if (typeof options.preload === 'boolean') { return out; } // if type is object - ensure an array of assets to load is specified - if (isPreloadValidObject(options.preload)) { - const requestedAssets = []; - options.preload.assets.forEach(asset => { + if (!isPreloadValidObject(options.preload)) { + throw new Error( + 'No assets configured for preload in aXe run configuration' + ); + } + + // check if assets are valid items + const areRequestedAssetsValid = options.preload.assets.reduce( + (out, asset) => { const a = asset.toLowerCase(); - if (axe.constants.preloadAssets.includes(a)) { - // unique assets to load, in case user had requested same asset type many times. - if (!requestedAssets.includes(a)) { - requestedAssets.push(a); - } - return a; - } else { - const e = - `Requested asset: ${a}, not supported by aXe.` + - `Supported assets are: ${axe.constants.preloadAssets - .map(_ => _) - .join(', ')}.`; - throw new Error(e); - } - }); - out.assets = requestedAssets; - - if (options.preload.timeout) { - if ( - typeof options.preload.timeout === 'number' && - !Number.isNaN(options.preload.timeout) - ) { - out.timeout = options.preload.timeout; - } else { - throw new Error(`preload timeout specified is not of type number`); + if (!axe.constants.preloadAssets.includes(a)) { + out = false; } - } + return out; + }, + true + ); - return out; - } else { + if (!areRequestedAssetsValid) { throw new Error( - 'No assets configured for preload in aXe run configuration' + `Requested assets, not supported by aXe.` + + `Supported assets are: ${axe.constants.preloadAssets.join(', ')}.` ); } + + out.assets = options.preload.assets.reduce((out, asset) => { + const a = asset.toLowerCase(); + // unique assets to load, in case user had requested same asset type many times. + if (!out.includes(a)) { + out.push(a); + } + return out; + }, []); + + if ( + options.preload.timeout && + typeof options.preload.timeout === 'number' && + !Number.isNaN(options.preload.timeout) + ) { + out.timeout = options.preload.timeout; + } + return out; } axe.utils.getPreloadConfig = getPreloadConfig; From 222f05b4b1d3c633d64d5f56968661ef4e6f9dcd Mon Sep 17 00:00:00 2001 From: Jey Date: Thu, 12 Jul 2018 15:14:05 +0100 Subject: [PATCH 30/43] fix: implement axios against xhrQ --- lib/core/utils/preload-cssom.js | 24 +++---- lib/core/utils/xhr-q.js | 73 --------------------- test/core/utils/xhr-q.js | 108 -------------------------------- 3 files changed, 7 insertions(+), 198 deletions(-) delete mode 100644 lib/core/utils/xhr-q.js delete mode 100644 test/core/utils/xhr-q.js diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 2536d5d931..747a5b9326 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -19,24 +19,15 @@ function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { sheet.cssRules && q.defer(resolve => resolve(sheet)); } catch (e) { const deferredSheet = (resolve, reject) => { - axe.utils - .xhrQ({ + axe.imports + .axios({ + method: 'get', url: sheet.href, timeout }) - .then(xhrResponse => { - if ( - xhrResponse && - Array.isArray(xhrResponse) && - xhrResponse.length - ) { - xhrResponse.forEach(r => { - const text = r.responseText ? r.responseText : r.response; - const href = r.responseURL; - const sheet = getSheetFromTextFn(text, href); - resolve(sheet); - }); - } + .then(({ data }) => { + const sheet = getSheetFromTextFn(data); + resolve(sheet); }) .catch(reject); }; @@ -91,11 +82,10 @@ function preloadCssom({ asset, timeout, treeRoot = axe._tree[0] }) { const getSheetFromTextFn = (function() { const dynamicDoc = document.implementation.createHTMLDocument(); - return (cssText, href) => { + return cssText => { // create style node with css text const style = dynamicDoc.createElement('style'); style.type = 'text/css'; - style.href = href; style.appendChild(dynamicDoc.createTextNode(cssText)); // added style to temporary document dynamicDoc.head.appendChild(style); diff --git a/lib/core/utils/xhr-q.js b/lib/core/utils/xhr-q.js deleted file mode 100644 index 4261dcb34d..0000000000 --- a/lib/core/utils/xhr-q.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Returns a then(able) queue of XHR's - * @param {Object} config configuration for XMLHttpRequest - * @return {Object} - */ -axe.utils.xhrQ = (config) => { - 'use strict'; - - const request = new XMLHttpRequest(); // IE7+ friendly - - const q = axe.utils.queue(); - - q.defer((resolve, reject) => { - // wire up timeout - request.timeout = config.timeout; - - // listen for timeout - request.ontimeout = () => { - reject({ - status: request.status, - statusText: request.statusText - }); - } - - // monitor ready state - request.onreadystatechange = () => { - // request is not complete. - if (request.readyState !== 4) { - return; - } - // process the response - if (request.status >= 200 && request.status <= 300) { - // success - resolve(request); - } else { - // failure - reject({ - status: request.status, - statusText: request.statusText - }); - } - }; - - // setup request - request.open(config.method || 'GET', config.url, true); - - // add headers if any - if (config.headers) { - Object - .keys(config.headers) - .forEach((k) => { - request - .setRequestHeader(k, config.headers[k]); - }); - } - - // enumerate and construct params - let params = config.params; - if (params && - typeof params === 'object') { - params = Object.keys(params) - .map((k) => { - return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`; - }) - .join('&'); - } - - // send - request.send(params); - }); - - return q; -} \ No newline at end of file diff --git a/test/core/utils/xhr-q.js b/test/core/utils/xhr-q.js deleted file mode 100644 index 533e26ca9b..0000000000 --- a/test/core/utils/xhr-q.js +++ /dev/null @@ -1,108 +0,0 @@ -/*global sinon */ - -describe('axe.utils.xhrQ', function() { - 'use strict'; - - beforeEach(function() { - this.xhr = sinon.useFakeXMLHttpRequest(); - this.requests = []; - this.xhr.onCreate = function(xhr) { - this.requests.push(xhr); - }.bind(this); - }); - - afterEach(function() { - this.xhr.restore(); - }); - - it('should reject queue on 500 error', function(done) { - var config = { - url: '/kaBoom' - }; - var fakeResponse = { - status: 500, - contentType: { 'Content-Type': 'text/json' }, - dataJson: { message: 'some dummy data.' } - }; - - axe.utils - .xhrQ(config) - .then(function() { - assert.fail('should not have resolved the queue'); - done(); - }) - .catch(function(err) { - assert.equal(err.status, 500); - done(); - }); - - this.requests[0].respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); - }); - - it('should resolve queue for status 200', function(done) { - var config = { - url: '/gotMe' - }; - var fakeResponse = { - status: 200, - contentType: { 'Content-Type': 'text/json' }, - dataJson: { message: 'some dummy data.' } - }; - - axe.utils - .xhrQ(config) - .then(function(results) { - var response = results[0]; - assert.equal(response.status, 200); - done(); - }) - .catch(function(error) { - done(error); - }); - - this.requests[0].respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); - }); - - it('should populate response', function(done) { - var config = { - url: '/gotMe' - }; - var fakeResponse = { - status: 200, - contentType: { 'Content-Type': 'text/json' }, - dataJson: { - status: 200, - responseText: 'My Expected Data!' - } - }; - - axe.utils - .xhrQ(config) - .then(function(results) { - var response = results[0]; - assert.isObject(response); - assert.deepEqual( - JSON.parse(response.responseText), - fakeResponse.dataJson - ); - done(); - }) - .catch(function(error) { - done(error); - }); - - this.requests[0].respond( - fakeResponse.status, - fakeResponse.contentType, - JSON.stringify(fakeResponse.dataJson) - ); - }); -}); From 7f061aeb2f220e458360aa397fc44b45b355324c Mon Sep 17 00:00:00 2001 From: Jey Date: Thu, 12 Jul 2018 16:43:48 +0100 Subject: [PATCH 31/43] fix: refactor based on comments/ review --- lib/core/constants.js | 2 +- lib/core/utils/preload.js | 28 +++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/lib/core/constants.js b/lib/core/constants.js index e31a210a1b..af9897ba86 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -32,7 +32,7 @@ resultGroups: [], resultGroupMap: {}, impact: Object.freeze(['minor', 'moderate', 'serious', 'critical']), - preloadAssets: Object.freeze(['cssom']), + preloadAssets: Object.freeze(['cssom']), // overtime this array will grow with other preload asset types, this constant is to verify if a requested preload type by the user via the configuration is supported by axe. preloadAssetsTimeout: 10000 }; diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index 55edac61fb..3968a59223 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -27,7 +27,7 @@ function shouldPreload(options) { return false; } - if (typeof options.preload === typeof true) { + if (typeof options.preload === 'boolean') { return options.preload; } @@ -63,16 +63,9 @@ function getPreloadConfig(options) { ); } - // check if assets are valid items - const areRequestedAssetsValid = options.preload.assets.reduce( - (out, asset) => { - const a = asset.toLowerCase(); - if (!axe.constants.preloadAssets.includes(a)) { - out = false; - } - return out; - }, - true + // check if requested assets to preload are valid items + const areRequestedAssetsValid = options.preload.assets.every(a => + axe.constants.preloadAssets.includes(a.toLowerCase()) ); if (!areRequestedAssetsValid) { @@ -82,14 +75,11 @@ function getPreloadConfig(options) { ); } - out.assets = options.preload.assets.reduce((out, asset) => { - const a = asset.toLowerCase(); - // unique assets to load, in case user had requested same asset type many times. - if (!out.includes(a)) { - out.push(a); - } - return out; - }, []); + // unique assets to load, in case user had requested same asset type many times. + out.assets = axe.utils.uniqueArray( + options.preload.assets.map(a => a.toLowerCase()), + [] + ); if ( options.preload.timeout && From 0fca2d12ea8fbc6787a676f36916919a62c384ab Mon Sep 17 00:00:00 2001 From: Jey Date: Thu, 12 Jul 2018 16:56:55 +0100 Subject: [PATCH 32/43] refactor: try catch block for stylesheet cssRules --- lib/core/utils/preload-cssom.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 747a5b9326..b52335d945 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -15,8 +15,10 @@ function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { } try { - // cannot use if/else here as the catch block won't be triggered (if passes for both relative and external sheets) - sheet.cssRules && q.defer(resolve => resolve(sheet)); + if (sheet.cssRules) { + // accessing .cssRules throws for external (cross-domain) sheets, which is handled in the catch + q.defer(resolve => resolve(sheet)); + } } catch (e) { const deferredSheet = (resolve, reject) => { axe.imports From 2155df8274106a939f708072a720ce04d735a6f5 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 17 Jul 2018 23:38:55 +0100 Subject: [PATCH 33/43] refactor: changes based on code review --- lib/core/utils/preload-cssom.js | 42 +++-- lib/core/utils/preload.js | 18 ++- test/core/utils/preload-cssom.js | 143 ++++------------- test/core/utils/preload.js | 253 +++++++++++++++---------------- 4 files changed, 184 insertions(+), 272 deletions(-) diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index b52335d945..ef0ad21c29 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -17,10 +17,12 @@ function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { try { if (sheet.cssRules) { // accessing .cssRules throws for external (cross-domain) sheets, which is handled in the catch + sheet.isExternal = false; // isExternal is a flag to ascertain if a network request is necessary for cssom building q.defer(resolve => resolve(sheet)); } } catch (e) { - const deferredSheet = (resolve, reject) => { + // external sheet -> make an xhr and q the response + q.defer((resolve, reject) => { axe.imports .axios({ method: 'get', @@ -28,13 +30,11 @@ function loadCssom(ownerDocument, timeout, getSheetFromTextFn) { timeout }) .then(({ data }) => { - const sheet = getSheetFromTextFn(data); + const sheet = getSheetFromTextFn(data, true); //second argument acts as > isExternal - true resolve(sheet); }) .catch(reject); - }; - // external sheet -> make an xhr and q the response - q.defer(deferredSheet); + }); } }); @@ -74,7 +74,7 @@ function getDocumentsFromTreeRoot(treeRoot) { * resolve/ reject - promise chainable methods * @return {Object} */ -function preloadCssom({ asset, timeout, treeRoot = axe._tree[0] }) { +function preloadCssom({ timeout, treeRoot = axe._tree[0] }) { const documents = getDocumentsFromTreeRoot(treeRoot); const q = axe.utils.queue(); @@ -82,27 +82,23 @@ function preloadCssom({ asset, timeout, treeRoot = axe._tree[0] }) { return q; } - const getSheetFromTextFn = (function() { - const dynamicDoc = document.implementation.createHTMLDocument(); - return cssText => { - // create style node with css text - const style = dynamicDoc.createElement('style'); - style.type = 'text/css'; - style.appendChild(dynamicDoc.createTextNode(cssText)); - // added style to temporary document - dynamicDoc.head.appendChild(style); - return style.sheet; - }; - })(); // invoke immediately + const dynamicDoc = document.implementation.createHTMLDocument(); + function getSheetFromTextFn(cssText, isExternal) { + // create style node with css text + const style = dynamicDoc.createElement('style'); + style.type = 'text/css'; + style.appendChild(dynamicDoc.createTextNode(cssText)); + // added style to temporary document + dynamicDoc.head.appendChild(style); + // add attribute to differentiate if it is a fetched sheet + style.sheet.isExternal = isExternal; + return style.sheet; + } documents.forEach(doc => { q.defer((resolve, reject) => { loadCssom(doc, timeout, getSheetFromTextFn) - .then(sheets => - resolve({ - [asset]: sheets - }) - ) + .then(resolve) .catch(reject); }); }); diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index 3968a59223..d42519ae29 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -8,8 +8,7 @@ function isPreloadValidObject(preload) { return ( typeof preload === 'object' && preload.hasOwnProperty('assets') && - Array.isArray(preload.assets) && - preload.assets.length + Array.isArray(preload.assets) ); } @@ -46,14 +45,14 @@ axe.utils.shouldPreload = shouldPreload; */ function getPreloadConfig(options) { // default fallback configuration - const out = { + const config = { assets: axe.constants.preloadAssets, timeout: axe.constants.preloadAssetsTimeout }; // if type is boolean if (typeof options.preload === 'boolean') { - return out; + return config; } // if type is object - ensure an array of assets to load is specified @@ -76,7 +75,7 @@ function getPreloadConfig(options) { } // unique assets to load, in case user had requested same asset type many times. - out.assets = axe.utils.uniqueArray( + config.assets = axe.utils.uniqueArray( options.preload.assets.map(a => a.toLowerCase()), [] ); @@ -86,9 +85,9 @@ function getPreloadConfig(options) { typeof options.preload.timeout === 'number' && !Number.isNaN(options.preload.timeout) ) { - out.timeout = options.preload.timeout; + config.timeout = options.preload.timeout; } - return out; + return config; } axe.utils.getPreloadConfig = getPreloadConfig; @@ -119,7 +118,10 @@ function preload(options) { timeout: preloadConfig.timeout }) .then(results => { - resolve(results[0]); + const sheets = results[0]; + resolve({ + [asset]: sheets + }); }) .catch(reject); }); diff --git a/test/core/utils/preload-cssom.js b/test/core/utils/preload-cssom.js index 1f38aad48c..bab9ac5bff 100644 --- a/test/core/utils/preload-cssom.js +++ b/test/core/utils/preload-cssom.js @@ -1,20 +1,36 @@ -describe('axe.utils.preloadCssom', function() { +describe('axe.utils.preloadCssom unit tests', function() { 'use strict'; - var fixture = document.getElementById('fixture'); - var pageTpl = - ''; var args; + function addStyleToHead() { + var css = 'html {font-size: inherit;}'; + var head = document.head || document.getElementsByTagName('head')[0]; + var style = document.createElement('style'); + style.id = 'preloadCssomTestHeadSheet'; + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + + function removeStyleFromHead() { + var s = document.getElementById('preloadCssomTestHeadSheet'); + if (s) { + s.parentNode.removeChild(s); + } + } + beforeEach(function() { + addStyleToHead(); args = { asset: 'cssom', - timeout: 10000 + timeout: 10000, + treeRoot: (axe._tree = axe.utils.getFlattenedTree(document)) }; }); afterEach(function() { - fixture.innerHTML = ''; + removeStyleFromHead(); }); it('should be a function', function() { @@ -22,59 +38,16 @@ describe('axe.utils.preloadCssom', function() { }); it('should return a queue', function() { - fixture.innerHTML = pageTpl; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; var actual = axe.utils.preloadCssom(args); assert.isObject(actual); assert.containsAllKeys(actual, ['then', 'defer', 'catch']); }); - it('should ensure result has cssom property', function(done) { - fixture.innerHTML = pageTpl; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; - var actual = axe.utils.preloadCssom(args); - actual - .then(function(results) { - var r = results[0]; - assert.property(r, 'cssom'); - done(); - }) - .catch(function(error) { - done(error); - }); - }); - - it('should ensure result of cssom returned is an array of sheets', function(done) { - fixture.innerHTML = pageTpl; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; - var actual = axe.utils.preloadCssom(args); - actual - .then(function(results) { - var sheets = results[0].cssom; - assert.isTrue(Array.isArray(sheets)); - assert.lengthOf(sheets, 2); - done(); - }) - .catch(function(error) { - done(error); - }); - }); - - it('should ensure all returned stylesheet is defined and has readable sheet/ cssrules', function(done) { - fixture.innerHTML = pageTpl; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; + it('should ensure result of cssom is an array of sheets', function(done) { var actual = axe.utils.preloadCssom(args); actual .then(function(results) { - var sheets = results[0].cssom; - sheets.forEach(function(s) { - assert.isDefined(s); - assert.property(s, 'cssRules'); - }); + assert.lengthOf(results[0], 2); // returned from queue, hence the index look up done(); }) .catch(function(error) { @@ -82,17 +55,11 @@ describe('axe.utils.preloadCssom', function() { }); }); - it('should ignore disabled stylesheets', function(done) { - var page = - ' '; - fixture.innerHTML = page; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; + it('should fail if number of sheets returned does not match stylesheets defined in document', function(done) { var actual = axe.utils.preloadCssom(args); actual .then(function(results) { - var sheets = results[0].cssom; - assert.lengthOf(sheets, 1); // the 1 stylesheet fetched is of the parent test runner page + assert.isFalse(results[0].length <= 1); // returned from queue, hence the index look up done(); }) .catch(function(error) { @@ -100,46 +67,13 @@ describe('axe.utils.preloadCssom', function() { }); }); - var shadowSupported = axe.testUtils.shadowSupport.v1; - (shadowSupported ? it : xit)( - 'should fetch all shadow DOM stylesheets', - function(done) { - fixture.innerHTML = '
'; - var target = document.querySelector('#target'); - var shadowRoot = target.attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = - ''; - - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; - var actual = axe.utils.preloadCssom(args); - actual - .then(function(results) { - var sheets = results[0].cssom; - assert.lengthOf(sheets, 1); - done(); - }) - .catch(function(error) { - done(error); - }); - } - ); - - it('should make xhr for external stylesheet', function(done) { - var page = - ' '; - fixture.innerHTML = page; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; + it('should ensure all returned stylesheet is defined and has property cssRules', function(done) { var actual = axe.utils.preloadCssom(args); actual .then(function(results) { - var sheets = results[0].cssom; - assert.lengthOf(sheets, 1); + var sheets = results[0]; sheets.forEach(function(s) { assert.isDefined(s); - assert.property(s, 'href'); - assert.isDefined(s.href); assert.property(s, 'cssRules'); }); done(); @@ -149,21 +83,8 @@ describe('axe.utils.preloadCssom', function() { }); }); - it('should return 2 stylesheets', function(done) { - var page = - ' '; - fixture.innerHTML = page; - var tree = (axe._tree = axe.utils.getFlattenedTree(fixture)); - args.treeRoot = tree; - var actual = axe.utils.preloadCssom(args); - actual - .then(function(results) { - var sheets = results[0].cssom; - assert.lengthOf(sheets, 2); - done(); - }) - .catch(function(error) { - done(error); - }); - }); + /** + * NOTE: document.styleSheets does not recognise dynamically injected stylesheets after load via beforeEach/ before, so tests for disabled and external stylesheets are done in integration + * Refer Directory: ./test/full/preload-cssom/**.* + */ }); diff --git a/test/core/utils/preload.js b/test/core/utils/preload.js index 57de425366..61221cfbff 100644 --- a/test/core/utils/preload.js +++ b/test/core/utils/preload.js @@ -1,147 +1,140 @@ -describe('axe.utils.preload', function() { +describe('axe.utils.preload unit tests', function() { 'use strict'; - it('should be a function', function() { - assert.isFunction(axe.utils.preload); - }); + describe('axe.utils.preload', function() { + it('should be a function', function() { + assert.isFunction(axe.utils.preload); + }); - it('should return a queue', function() { - var options = { - preload: true - }; - var actual = axe.utils.preload(options); - assert.isObject(actual); - }); + it('should return a queue', function() { + var options = { + preload: true + }; + var actual = axe.utils.preload(options); + assert.isObject(actual); + }); - it('should ensure queue is defer(able)', function(done) { - var options = { - preload: false - }; - var actual = axe.utils.preload(options); - actual - .defer(function(res, rej) { - assert.isFunction(rej); - res(true); - done(); - }) - .catch(function(error) { - done(error); - }); - }); + it('should ensure queue is defer(able)', function(done) { + var options = { + preload: false + }; + var actual = axe.utils.preload(options); + actual + .defer(function(res, rej) { + assert.isFunction(rej); + res(true); + done(); + }) + .catch(function(error) { + done(error); + }); + }); - it('should ensure queue is then(able)', function(done) { - var options = { - preload: false - }; - var actual = axe.utils.preload(options); - actual - .then(function(results) { - assert.isDefined(results); - done(); - }) - .catch(function(error) { - done(error); - }); - }); + it('should ensure queue is then(able)', function(done) { + var options = { + preload: false + }; + var actual = axe.utils.preload(options); + actual + .then(function(results) { + assert.isDefined(results); + done(); + }) + .catch(function(error) { + done(error); + }); + }); - it('should return an object with property cssom', function(done) { - var options = { - preload: { - assets: ['cssom'] - } - }; - var actual = axe.utils.preload(options); - actual - .then(function(results) { - assert.isDefined(results); - assert.isArray(results); - assert.property(results[0], 'cssom'); - done(); - }) - .catch(function(error) { - done(error); - }); + it('should return an object with property cssom', function(done) { + var options = { + preload: { + assets: ['cssom'] + } + }; + var actual = axe.utils.preload(options); + actual + .then(function(results) { + assert.isDefined(results); + assert.isArray(results); + assert.property(results[0], 'cssom'); + done(); + }) + .catch(function(error) { + done(error); + }); + }); }); - it('should return true if preload configuration is valid', function() { - var options = { - preload: true - }; - var actual = axe.utils.shouldPreload(options); - assert.isTrue(actual); - }); + describe('axe.utils.shouldPreload', function() { + it('should return true if preload configuration is valid', function() { + var options = { + preload: true + }; + var actual = axe.utils.shouldPreload(options); + assert.isTrue(actual); + }); - it('should return false if preload configuration is invalid', function() { - var options = { - preload: { - errorProperty: ['cssom'] - } - }; - var actual = axe.utils.shouldPreload(options); - assert.isFalse(actual); + it('should return false if preload configuration is invalid', function() { + var options = { + preload: { + errorProperty: ['cssom'] + } + }; + var actual = axe.utils.shouldPreload(options); + assert.isFalse(actual); + }); }); - it('should throw error if preload configuration is invalid', function() { - var actual = function() { - axe.utils.getPreloadConfig({}); - }; - var expected = Error; - assert.throws(actual, expected); - }); + describe('axe.utils.getPreloadConfig', function() { + it('should throw error if preload configuration is invalid', function() { + var actual = function() { + axe.utils.getPreloadConfig({}); + }; + var expected = Error; + assert.throws(actual, expected); + }); - it('should return default assets if preload options is set to true', function() { - var options = { - preload: true - }; - var actual = axe.utils.getPreloadConfig(options).assets; - var expected = ['cssom']; - assert.deepEqual(actual, expected); - }); + it('should return default assets if preload options is set to true', function() { + var options = { + preload: true + }; + var actual = axe.utils.getPreloadConfig(options).assets; + var expected = ['cssom']; + assert.deepEqual(actual, expected); + }); - it('should return default timeout value if not configured', function() { - var options = { - preload: true - }; - var actual = axe.utils.getPreloadConfig(options).timeout; - var expected = 10000; - assert.equal(actual, expected); - }); + it('should return default timeout value if not configured', function() { + var options = { + preload: true + }; + var actual = axe.utils.getPreloadConfig(options).timeout; + var expected = 10000; + assert.equal(actual, expected); + }); - it('should throw error if requested asset type is not supported', function() { - var options = { - preload: { - assets: ['aom'] - } - }; - var actual = function() { - axe.utils.getPreloadConfig(options); - }; - var expected = Error; - assert.throws(actual, expected); - }); - - it('should throw error if assets array is empty in options for preload', function() { - var options = { - preload: { - assets: [] - } - }; - var actual = function() { - axe.utils.getPreloadConfig(options); - }; - var expected = Error; - assert.throws(actual, expected); - }); + it('should throw error if requested asset type is not supported', function() { + var options = { + preload: { + assets: ['some-unsupported-asset'] + } + }; + var actual = function() { + axe.utils.getPreloadConfig(options); + }; + var expected = Error; + assert.throws(actual, expected); + }); - it('should unique assets requested if repeated assets are passed via options', function() { - var options = { - preload: { - assets: ['cssom', 'cssom'] - } - }; - var actual = axe.utils.getPreloadConfig(options); - assert.property(actual, 'assets'); - assert.containsAllKeys(actual, ['assets', 'timeout']); - assert.lengthOf(actual.assets, 1); + it('should remove any duplicate assets passed via preload configuration', function() { + var options = { + preload: { + assets: ['cssom', 'cssom'] + } + }; + var actual = axe.utils.getPreloadConfig(options); + assert.property(actual, 'assets'); + assert.containsAllKeys(actual, ['assets', 'timeout']); + assert.lengthOf(actual.assets, 1); + }); }); }); From 0923c365363857126fed5c0ec38d74c7a7bf4413 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 17 Jul 2018 23:39:43 +0100 Subject: [PATCH 34/43] style: revert formatting changes --- lib/checks/aria/aria-hidden-body.json | 2 +- lib/rules/aria-hidden-body.json | 36 +++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/checks/aria/aria-hidden-body.json b/lib/checks/aria/aria-hidden-body.json index 37012e3f1d..bad069615e 100644 --- a/lib/checks/aria/aria-hidden-body.json +++ b/lib/checks/aria/aria-hidden-body.json @@ -8,4 +8,4 @@ "fail": "aria-hidden=true should not be present on the document body" } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/lib/rules/aria-hidden-body.json b/lib/rules/aria-hidden-body.json index bc9eeee34e..ae1cefe268 100644 --- a/lib/rules/aria-hidden-body.json +++ b/lib/rules/aria-hidden-body.json @@ -1,19 +1,19 @@ { - "id": "aria-hidden-body", - "selector": "body", - "excludeHidden": false, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412" - ], - "metadata": { - "description": "Ensures aria-hidden='true' is not present on the document body.", - "help": "aria-hidden='true' must not be present on the document body" - }, - "all": [], - "any": [ - "aria-hidden-body" - ], - "none": [] -} \ No newline at end of file + "id": "aria-hidden-body", + "selector": "body", + "excludeHidden": false, + "tags": [ + "cat.aria", + "wcag2a", + "wcag412" + ], + "metadata": { + "description": "Ensures aria-hidden='true' is not present on the document body.", + "help": "aria-hidden='true' must not be present on the document body" + }, + "all": [], + "any": [ + "aria-hidden-body" + ], + "none": [] +} \ No newline at end of file From 29e3c61636baabf9cddbcfbf43df693aa97d2080 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 17 Jul 2018 23:40:06 +0100 Subject: [PATCH 35/43] docs: update documentation for preload configuration --- doc/API.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/API.md b/doc/API.md index e86ff80d7c..6a74391c42 100644 --- a/doc/API.md +++ b/doc/API.md @@ -471,9 +471,14 @@ preload: true ```js preload: { assets: ['cssom'], timeout: 50000 } ``` -The `assets` attribute expects an array of preload(able) constraints to be fetched. +The `assets` attribute expects an array of preload(able) constraints to be fetched. The current set of values supported for `assets` is listed in the following table: + +| Asset Type | Description | +|:-----------|:------------| +| `cssom` | This asset type preloads all CSS Stylesheets rulesets specified in the page. The stylessheets can be an external cross-domain resource, a relative stylesheet or an inline style with in the head tag of the document. If the stylesheet is an external cross-domain a network request is made. An object representing the CSS Rules from each stylesheet is made available to the checks evaluate function as `preloadedAssets` at run-time | + +The `timeout` attribute in the object configuration is `optional` and has a fallback default value (10000ms). The `timeout` is essential for any network dependent assets that are preloaded, where-in if a given request takes longer than the specified/ default value, the operation is aborted. -The `timeout` attribute in the object configuration is `optional` and has a fallback default value. The `timeout` is essential for any network dependent assets that are preloaded. ##### Callback Parameter From 9ecba9e2e3349b88051e7239a546bb93d4709b54 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 17 Jul 2018 23:40:28 +0100 Subject: [PATCH 36/43] test: add integration tests for preload-cssom --- .../full/preload-cssom/frames/level1.html | 8 ++ .../full/preload-cssom/preload-cssom.css | 3 + .../full/preload-cssom/preload-cssom.html | 26 ++++ .../full/preload-cssom/preload-cssom.js | 130 ++++++++++++++++++ test/integration/rules/runner.tmpl | 1 - test/runner.tmpl | 1 - 6 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 test/integration/full/preload-cssom/frames/level1.html create mode 100644 test/integration/full/preload-cssom/preload-cssom.css create mode 100644 test/integration/full/preload-cssom/preload-cssom.html create mode 100644 test/integration/full/preload-cssom/preload-cssom.js diff --git a/test/integration/full/preload-cssom/frames/level1.html b/test/integration/full/preload-cssom/frames/level1.html new file mode 100644 index 0000000000..b28010392f --- /dev/null +++ b/test/integration/full/preload-cssom/frames/level1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/integration/full/preload-cssom/preload-cssom.css b/test/integration/full/preload-cssom/preload-cssom.css new file mode 100644 index 0000000000..7567efd837 --- /dev/null +++ b/test/integration/full/preload-cssom/preload-cssom.css @@ -0,0 +1,3 @@ +html { + font-weight: inherit; +} \ No newline at end of file diff --git a/test/integration/full/preload-cssom/preload-cssom.html b/test/integration/full/preload-cssom/preload-cssom.html new file mode 100644 index 0000000000..4aab699450 --- /dev/null +++ b/test/integration/full/preload-cssom/preload-cssom.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + +
+ + + + diff --git a/test/integration/full/preload-cssom/preload-cssom.js b/test/integration/full/preload-cssom/preload-cssom.js new file mode 100644 index 0000000000..b603aaf6a2 --- /dev/null +++ b/test/integration/full/preload-cssom/preload-cssom.js @@ -0,0 +1,130 @@ +/* global axe, sinon, Promise */ +describe('preload cssom integration test pass', function() { + 'use strict'; + + before(function(done) { + function start() { + // Stop messing with my tests Mocha! + document.querySelector('#mocha h1').outerHTML = + '

preload cssom integration test

'; + axe.run({ preload: true }, function(err, r) { + assert.isNull(err); + console.log(r); + done(); + }); + } + if (document.readyState !== 'complete') { + window.addEventListener('load', start); + } else { + start(); + } + }); + + function getStub(fail) { + var axiosStub = sinon + .stub(axe.imports, 'axios') + .callsFake(function axiosFakerInsideAxeImports() { + return new Promise(function(resolve, reject) { + if (fail) { + reject(new Error('Fake Error')); + } + resolve({ + data: 'body { overflow: auto; }' + }); + }); + }); + return axiosStub; + } + + var args; + var stub; + beforeEach(function() { + stub = getStub(); + args = { + asset: 'cssom', + timeout: 10000, + treeRoot: (axe._tree = axe.utils.getFlattenedTree(document)) + }; + }); + + afterEach(function() { + stub.restore(); + }); + + it('should ignore disabled stylesheets on top level document', function(done) { + var actual = axe.utils.preloadCssom(args); + actual.then(function(results) { + if (stub.callCount > 0) { + // This is a hack to ignore assertion on webdriver phantomjs mode, as sion stub is not hit + var sheets = results[0]; + assert.lengthOf(sheets, 2); + } + done(); + }); + }); + + it('should ensure external stylesheet is fetched on top level document', function(done) { + var actual = axe.utils.preloadCssom(args); + actual.then(function(results) { + if (stub.callCount > 0) { + // This is a hack to ignore assertion on webdriver phantomjs mode, as sinon stub is not hit + var sheets = results[0]; + var externalSheet = sheets.filter(function(s) { + return s.isExternal; + })[0]; + assert.isDefined(externalSheet); + assert.property(externalSheet, 'cssRules'); + } + done(); + }); + }); + + it('should return correct number of sheets for targeted iframe', function(done) { + var fixture = document.getElementById('frame1').contentDocument; + args.treeRoot = axe._tree = axe.utils.getFlattenedTree(fixture); + var actual = axe.utils.preloadCssom(args); + actual.then(function(results) { + if (stub.callCount > 0) { + // This is a hack to ignore assertion on webdriver phantomjs mode, as sinon stub is not hit + var sheets = results[0]; + assert.lengthOf(sheets, 1); + } + done(); + }); + }); + + it('should ensure the fetched stylesheet in iframe is external resource', function(done) { + var fixture = document.getElementById('frame1').contentDocument; + args.treeRoot = axe._tree = axe.utils.getFlattenedTree(fixture); + var actual = axe.utils.preloadCssom(args); + actual.then(function(results) { + if (stub.callCount > 0) { + // This is a hack to ignore assertion on webdriver phantomjs mode, as sinon stub is not hit + var externalSheet = results[0][0]; + assert.isDefined(externalSheet); + assert.property(externalSheet, 'isExternal'); + assert.property(externalSheet, 'cssRules'); + } + done(); + }); + }); + + it('should reject external stylesheet in iframe', function(done) { + stub.restore(); + var failStub = getStub(true); + var fixture = document.getElementById('frame1').contentDocument; + args.treeRoot = axe._tree = axe.utils.getFlattenedTree(fixture); + var actual = axe.utils.preloadCssom(args); + actual + .then(function() { + done(); // This is a hack to ignore assertion on webdriver phantomjs mode, as sinon stub is not hit + }) + .catch(function(error) { + if (failStub.callCount > 0) { + // This is a hack to ignore assertion on webdriver phantomjs mode, as sinon stub is not hit + assert.equal(error.message, 'Fake Error'); + } + done(); + }); + }); +}); diff --git a/test/integration/rules/runner.tmpl b/test/integration/rules/runner.tmpl index 2b9e7b79c3..8b714221e4 100644 --- a/test/integration/rules/runner.tmpl +++ b/test/integration/rules/runner.tmpl @@ -7,7 +7,6 @@ - - diff --git a/test/integration/full/preload-cssom/preload-cssom.html b/test/integration/full/preload-cssom/preload-cssom.html index 4aab699450..7455f257ac 100644 --- a/test/integration/full/preload-cssom/preload-cssom.html +++ b/test/integration/full/preload-cssom/preload-cssom.html @@ -2,12 +2,26 @@ + + + + + + + + + + + - diff --git a/test/integration/full/preload-cssom/preload-cssom.js b/test/integration/full/preload-cssom/preload-cssom.js index 572629bfaf..5788124fee 100644 --- a/test/integration/full/preload-cssom/preload-cssom.js +++ b/test/integration/full/preload-cssom/preload-cssom.js @@ -3,6 +3,7 @@ describe('preload cssom integration test pass', function() { 'use strict'; var origAxios; + var shadowSupported = axe.testUtils.shadowSupport.v1; before(function(done) { function start() { @@ -80,7 +81,7 @@ describe('preload cssom integration test pass', function() { var sheets = results[0]; var externalSheet = sheets.filter(function(s) { return s.isExternal; - })[0]; + })[0].sheet; assertStylesheet(externalSheet, 'body', 'body{overflow:auto;}'); done(); }) @@ -126,8 +127,8 @@ describe('preload cssom integration test pass', function() { }); assert.lengthOf(nonExternalsheets, 2); var inlineStylesheet = nonExternalsheets.filter(function(s) { - return s.rules.length === 1; - })[0]; + return s.sheet.rules.length === 1; + })[0].sheet; assertStylesheet( inlineStylesheet, '.inline-css-test', @@ -150,8 +151,8 @@ describe('preload cssom integration test pass', function() { }); assert.lengthOf(relativeSheets, 2); var relativeSheet = relativeSheets.filter(function(s) { - return s.rules.length > 1; - })[0]; + return s.sheet.rules.length > 1; + })[0].sheet; assertStylesheet(relativeSheet, 'body', 'body{margin:0px;}'); done(); }) @@ -167,7 +168,7 @@ describe('preload cssom integration test pass', function() { var externalSheets = sheets.filter(function(s) { return s.isExternal; }); - assert.lengthOf(externalSheets, 2); + assert.lengthOf(externalSheets, 3); done(); }) .catch(done); @@ -180,13 +181,53 @@ describe('preload cssom integration test pass', function() { getPreload() .then(function(results) { var sheets = results[0]; - assert.lengthOf(sheets, 4); + assert.lengthOf(sheets, 5); done(); }) .catch(done); } ); + if (!window.PHANTOMJS) { + (shadowSupported ? it : xit)( + 'should return styles from shadow dom', + function(done) { + var fixture = document.getElementById('shadow-fixture'); + var shadow = fixture.attachShadow({ mode: 'open' }); + shadow.innerHTML = + '' + + '
Some text
' + + '
green
' + + '
red
' + + '' + + '

Heading

'; + getPreload(fixture) + .then(function(results) { + var sheets = results[0]; + // verify count + assert.lengthOf(sheets, 8); + // verify that the last non external sheet with shadowId has green selector + var nonExternalsheetsWithShadowId = sheets + .filter(function(s) { + return !s.isExternal; + }) + .filter(function(s) { + return s.shadowId; + }); + assertStylesheet( + nonExternalsheetsWithShadowId[ + nonExternalsheetsWithShadowId.length - 1 + ].sheet, + '.green', + '.green{background-color:green;}' + ); + done(); + }) + .catch(done); + } + ); + } + commonTestsForRootAndFrame(); }); @@ -221,8 +262,8 @@ describe('preload cssom integration test pass', function() { }); assert.lengthOf(nonExternalsheets, 1); var inlineStylesheet = nonExternalsheets.filter(function(s) { - return s.rules.length === 1; - })[0]; + return s.sheet.rules.length === 1; + })[0].sheet; assertStylesheet( inlineStylesheet, '.inline-frame-css-test', From 567ef3c4cc4c067f0109814b8a65a8eda54ed4a2 Mon Sep 17 00:00:00 2001 From: Jey Date: Mon, 6 Aug 2018 16:50:10 +0100 Subject: [PATCH 42/43] fix: updates based on review --- lib/commons/dom/get-root-node.js | 10 +- lib/core/utils/get-root-node.js | 18 ++++ lib/core/utils/preload-cssom.js | 102 +++++++----------- lib/core/utils/preload.js | 15 +-- .../dom => core/utils}/get-root-node.js | 10 +- .../full/preload-cssom/preload-cssom.js | 7 +- 6 files changed, 68 insertions(+), 94 deletions(-) create mode 100644 lib/core/utils/get-root-node.js rename test/{commons/dom => core/utils}/get-root-node.js (77%) diff --git a/lib/commons/dom/get-root-node.js b/lib/commons/dom/get-root-node.js index 59fbd86556..3aa7209ef1 100644 --- a/lib/commons/dom/get-root-node.js +++ b/lib/commons/dom/get-root-node.js @@ -7,12 +7,6 @@ * @instance * @param {Element} node * @returns {DocumentFragment|Document} + * @deprecated use axe.utils.getRootNode */ -dom.getRootNode = function(node) { - var doc = (node.getRootNode && node.getRootNode()) || document; // this is for backwards compatibility - if (doc === node) { - // disconnected node - doc = document; - } - return doc; -}; +dom.getRootNode = axe.utils.getRootNode; diff --git a/lib/core/utils/get-root-node.js b/lib/core/utils/get-root-node.js new file mode 100644 index 0000000000..43c13a046c --- /dev/null +++ b/lib/core/utils/get-root-node.js @@ -0,0 +1,18 @@ +/* global axe */ + +/** + * Return the document or document fragment (shadow DOM) + * @method getRootNode + * @memberof axe.utils + * @instance + * @param {Element} node + * @returns {DocumentFragment|Document} + */ +axe.utils.getRootNode = function getRootNode(node) { + var doc = (node.getRootNode && node.getRootNode()) || document; // this is for backwards compatibility + if (doc === node) { + // disconnected node + doc = document; + } + return doc; +}; diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 7ece000efa..b3388dba38 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -1,19 +1,3 @@ -/** - * Construct an extended object of cssom stylesheet with added attribtues for isExternal and shadowId - * @method getCssomSheet - * @private - * @param {CSSStyleSheet} sheet stylesheet - * @param {Boolean} isExternal flag to specify if the stylesheet was fetched via xhr - * @param {String} shadowId (optional) string representing shadowId if style/ sheet was constructed from shadowDOM assets - */ -function getCssomSheet(sheet, isExternal, shadowId) { - const out = { isExternal, sheet }; - if (shadowId) { - out.shadowId = shadowId; - } - return out; -} - /** * Returns a then(able) queue of CSSStyleSheet(s) * @param {Object} ownerDocument document object to be inspected for stylesheets @@ -52,6 +36,7 @@ function loadCssom({ root, shadowId }, timeout, convertTextToStylesheetFn) { } const q = axe.utils.queue(); + // iterate to decipher multi-level nested sheets if any (this is essential to retrieve styles from shadowDOM) Array.from(root.styleSheets).forEach(sheet => { // ignore disabled sheets @@ -64,43 +49,53 @@ function loadCssom({ root, shadowId }, timeout, convertTextToStylesheetFn) { const cssRules = sheet.cssRules; // read all css rules in the sheet const rules = Array.from(cssRules); - // filter rules to assess if any external rules by way of @import or link exists on a nested level - const externalRules = rules.filter(r => r.href); - // if no external rules then return an expanded version of - if (!externalRules.length) { - q.defer(resolve => resolve(getCssomSheet(sheet, false, shadowId))); + + // filter rules that are included by way of @import or nested link + const importRules = rules.filter(r => r.href); + + // if no import or nested link rules, with in these cssRules + // return current sheet + if (!importRules.length) { + q.defer(resolve => + resolve({ + sheet, + isExternal: false, + shadowId + }) + ); return; } - // there are external rules, best to filter non-external rules and create a new stylesheet to avoid duplication - const nonExternalRules = rules.filter(rule => !rule.href); - // concat all cssText into a string for non external rules - const nonExternalRulesText = nonExternalRules + // if any import rules exists, fetch via `href` which eventually constructs a sheet with results from resource + importRules.forEach(rule => { + q.defer((resolve, reject) => { + getExternalStylesheet({ resolve, reject, url: rule.href }); + }); + }); + + // in the same sheet - get inline rules in