Skip to content

Commit

Permalink
fix: Allow exclusion of Shadow DOM content
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Mar 9, 2018
1 parent 10086fc commit cc66eb2
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 56 deletions.
5 changes: 4 additions & 1 deletion lib/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
});
Expand Down
43 changes: 33 additions & 10 deletions lib/core/base/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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)) {
Expand All @@ -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));
}
}
}

Expand Down Expand Up @@ -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.
*
Expand All @@ -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);
}
});

Expand Down
28 changes: 0 additions & 28 deletions test/core/base/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
8 changes: 8 additions & 0 deletions test/core/base/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div id="outer"></div>';
iframeReady('../mock/frames/context.html', $id('outer'), 'target', function() {
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 1 addition & 4 deletions test/core/public/run-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('runRules', function () {
afterEach(function () {
fixture.innerHTML = '';
axe._audit = null;
axe._tree = undefined;
});

it('should work', function (done) {
Expand All @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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) {
Expand Down
28 changes: 15 additions & 13 deletions test/integration/full/context/context.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@
<div id="shadow-host"></div>
</div>
<div id="mocha"></div>
<script src="/test/testutils.js"></script>
<script>
var host = document.getElementById('shadow-host');
var list = document.getElementsByTagName('ul')[0];
var shadowRoot = host.attachShadow({ mode: 'open' });
var shadowContent = document.createElement('div');
shadowContent.setAttribute('style', 'background-color: #333');
var content = document.createElement('p');
content.style.color = '#000';
content.textContent = 'Failing Text';
shadowContent.appendChild(content);
var shadowList = shadowContent.appendChild(list);
shadowList.style.display = 'block';
shadowRoot.appendChild(shadowContent);
if (axe.testUtils.shadowSupport.v1) {
var host = document.getElementById('shadow-host');
var list = document.getElementsByTagName('ul')[0];
var shadowRoot = host.attachShadow({ mode: 'open' });
var shadowContent = document.createElement('div');
shadowContent.setAttribute('style', 'background-color: #333');
var content = document.createElement('p');
content.style.color = '#000';
content.textContent = 'Failing Text';
shadowContent.appendChild(content);
var shadowList = shadowContent.appendChild(list);
shadowList.style.display = 'block';
shadowRoot.appendChild(shadowContent);
}
</script>
<script src="/test/testutils.js"></script>
<script src="context.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
Expand Down

0 comments on commit cc66eb2

Please sign in to comment.