diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index 17fdfcbde9..240dfe8a74 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -144,7 +144,6 @@ Audit.prototype.run = function (context, options, resolve, reject) { 'use strict'; this.validateOptions(options); - axe._tree = axe.utils.getFlattenedTree(document.documentElement); //cache the flattened tree axe._selectCache = []; var q = axe.utils.queue(); this.rules.forEach(function (rule) { @@ -197,6 +196,10 @@ Audit.prototype.after = function (results, options) { 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); }); diff --git a/lib/core/base/context.js b/lib/core/base/context.js index 2f85e9da02..f6ce1c429e 100644 --- a/lib/core/base/context.js +++ b/lib/core/base/context.js @@ -75,7 +75,6 @@ function normalizeContext(context) { // typeof NodeList.length in PhantomJS === function if (context && typeof context === 'object' || context instanceof NodeList) { - if (context instanceof Node) { return { include: [context], @@ -130,7 +129,7 @@ function parseSelectorArray(context, type) { nodeList = Array.from(document.querySelectorAll(item)); //eslint no-loop-func:0 result = result.concat(nodeList.map((node) => { - return axe.utils.getFlattenedTree(node)[0]; + return axe.utils.getNodeFromTree(axe._tree[0], node); })); break; } else if (item && item.length && !(item instanceof Node)) { @@ -141,12 +140,15 @@ function parseSelectorArray(context, type) { nodeList = Array.from(document.querySelectorAll(item[0])); //eslint no-loop-func:0 result = result.concat(nodeList.map((node) => { - return axe.utils.getFlattenedTree(node)[0]; + return axe.utils.getNodeFromTree(axe._tree[0], node); })); } } else if (item instanceof Node) { - - result.push(axe.utils.getFlattenedTree(item)[0]); + if (item.documentElement instanceof Node) { + result.push(axe._tree[0]); + } else { + result.push(axe.utils.getNodeFromTree(axe._tree[0], item)); + } } } @@ -180,6 +182,25 @@ function validateContext(context) { } +/** + * For a context-like object, find its shared root node + */ +function getRootNode ({ include, exclude }) { + const selectors = Array.from(include).concat(Array.from(exclude)); + // Find the first Element.ownerDocument or Document + const localDocument = selectors.reduce((result, item) => { + if (result) { + return result; + } else if (item instanceof Element) { + return item.ownerDocument + } else if (item instanceof Document) { + return item + } + }, null); + + return (localDocument || document).documentElement; +} + /** * Holds context of includes, excludes and frames for analysis. * @@ -201,24 +222,26 @@ function validateContext(context) { * @param {Object} spec Configuration or "specification" object */ function Context(spec) { - /* eslint max-statements:["error",19], no-unused-vars:0 */ + /* eslint max-statements:["error",22], no-unused-vars:0 */ 'use strict'; - var self = this; this.frames = []; this.initiator = (spec && typeof spec.initiator === 'boolean') ? spec.initiator : true; this.page = false; spec = normalizeContext(spec); + + //cache the flattened tree + axe._tree = axe.utils.getFlattenedTree(getRootNode(spec)); this.exclude = spec.exclude; this.include = spec.include; this.include = parseSelectorArray(this, 'include'); this.exclude = parseSelectorArray(this, 'exclude'); - axe.utils.select('frame, iframe', this).forEach(function (frame) { - if (isNodeInContext(frame, self)) { - pushUniqueFrame(self.frames, frame.actualNode); + axe.utils.select('frame, iframe', this).forEach((frame) => { + if (isNodeInContext(frame, this)) { + pushUniqueFrame(this.frames, frame.actualNode); } }); diff --git a/test/core/base/audit.js b/test/core/base/audit.js index 518102e470..8e924f200f 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -471,34 +471,6 @@ describe('Audit', function () { done(); }, isNotCalled); }); - it('should call axe.utils.getFlattenedTree', function (done) { - var called = false; - axe.utils.getFlattenedTree = function () { - called = true; - }; - a.run({ include: [document] }, { - rules: {} - }, function () { - assert.isTrue(called); - axe.utils.getFlattenedTree = getFlattenedTree; - done(); - }, isNotCalled); - }); - it('should assign the result of getFlattenedTree to axe._tree', function (done) { - var thing = 'honey badger'; - var saved = axe.utils.ruleShouldRun; - axe.utils.ruleShouldRun = function () { - assert.equal(axe._tree, thing); - return false; - }; - axe.utils.getFlattenedTree = function () { - return thing; - }; - a.run({ include: [document] }, {}, function () { - axe.utils.ruleShouldRun = saved; - done(); - }, isNotCalled); - }); it('should assign an empty array to axe._selectCache', function (done) { var saved = axe.utils.ruleShouldRun; axe.utils.ruleShouldRun = function () { diff --git a/test/core/base/context.js b/test/core/base/context.js index 5a94227d96..96f66f629e 100644 --- a/test/core/base/context.js +++ b/test/core/base/context.js @@ -327,6 +327,13 @@ describe('Context', function() { }); }); + it('should assign the result of getFlattenedTree to axe._tree', function () { + /* eslint no-new:0 */ + new Context({ include: [document] }); + // WARNING: This only works because there is now Shadow DOM on this page + assert.deepEqual(axe._tree, axe.utils.getFlattenedTree(document)); + }); + it('should throw when frame could not be found', function (done) { fixture.innerHTML = '
'; iframeReady('../mock/frames/context.html', $id('outer'), 'target', function() { @@ -429,6 +436,7 @@ describe('Context', function() { it('should not throw given really weird circumstances when hasOwnProperty is deleted from a document node?', function() { var spec = document.implementation.createHTMLDocument('ie is dumb'); spec.hasOwnProperty = undefined; + var result = new Context(spec); assert.lengthOf(result.include, 1); diff --git a/test/core/public/run-rules.js b/test/core/public/run-rules.js index 0bedda2830..822caf6989 100644 --- a/test/core/public/run-rules.js +++ b/test/core/public/run-rules.js @@ -53,6 +53,7 @@ describe('runRules', function () { afterEach(function () { fixture.innerHTML = ''; axe._audit = null; + axe._tree = undefined; }); it('should work', function (done) { @@ -72,7 +73,6 @@ describe('runRules', function () { frame.addEventListener('load', function () { setTimeout(function () { - axe._tree = axe.utils.getFlattenedTree(document); runRules(document, {}, function (r) { assert.lengthOf(r[0].passes, 3); done(); @@ -97,7 +97,6 @@ describe('runRules', function () { var frame = document.createElement('iframe'); frame.addEventListener('load', function () { setTimeout(function () { - axe._tree = axe.utils.getFlattenedTree(document); runRules(document, {}, function (r) { var nodes = r[0].passes.map(function (detail) { return detail.node.selector; @@ -156,7 +155,6 @@ describe('runRules', function () { var div = document.createElement('div'); fixture.appendChild(div); - axe._tree = axe.utils.getFlattenedTree(document); runRules('#fixture', {}, function (results) { assert.deepEqual(JSON.parse(JSON.stringify(results)), [{ id: 'div#target', @@ -232,7 +230,6 @@ describe('runRules', function () { }); iframeReady('../mock/frames/context.html', fixture, 'context-test', function () { - axe._tree = axe.utils.getFlattenedTree(document); runRules('#not-happening', {}, function () { assert.fail('This selector should not exist.'); }, function (error) { diff --git a/test/integration/full/context/context.html b/test/integration/full/context/context.html index 5b420716e4..f9c9cb3df7 100644 --- a/test/integration/full/context/context.html +++ b/test/integration/full/context/context.html @@ -28,21 +28,23 @@
+ -