Skip to content

Commit

Permalink
fix(utils): make cache global instead of only setup in axe.run (#1535)
Browse files Browse the repository at this point in the history
* fix(utils): make cache global instead of only setup in axe.run

* make cache not public

* add integration test for plugins

* use backgroundcolor instead of background
  • Loading branch information
straker committed May 9, 2019
1 parent 80ae444 commit 91a04c5
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 21 deletions.
10 changes: 5 additions & 5 deletions lib/commons/aria/is-accessible-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ const idRefsRegex = /^idrefs?$/;
function cacheIdRefs(node, refAttrs) {
if (node.hasAttribute) {
if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) {
axe._cache.idRefs[node.getAttribute('for')] = true;
axe._cache.get('idRefs')[node.getAttribute('for')] = true;
}

refAttrs
.filter(attr => node.hasAttribute(attr))
.forEach(attr => {
const attrValue = node.getAttribute(attr);
axe.utils.tokenList(attrValue).forEach(id => {
axe._cache.idRefs[id] = true;
axe._cache.get('idRefs')[id] = true;
});
});
}
Expand All @@ -35,8 +35,8 @@ aria.isAccessibleRef = function isAccessibleRef(node) {

// because axe.commons is not available in axe.utils, we can't do
// this caching when we build up the virtual tree
if (!axe._cache.idRefs) {
axe._cache.idRefs = {};
if (!axe._cache.get('idRefs')) {
axe._cache.set('idRefs', {});
// Get all idref(s) attributes on the lookup table
const refAttrs = Object.keys(aria.lookupTable.attributes).filter(attr => {
const { type } = aria.lookupTable.attributes[attr];
Expand All @@ -46,5 +46,5 @@ aria.isAccessibleRef = function isAccessibleRef(node) {
cacheIdRefs(root, refAttrs);
}

return axe._cache.idRefs[id] === true;
return axe._cache.get('idRefs')[id] === true;
};
6 changes: 3 additions & 3 deletions lib/commons/dom/is-skip-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ dom.isSkipLink = function(element) {
}

let firstPageLink;
if (typeof axe._cache.firstPageLink !== 'undefined') {
firstPageLink = axe._cache.firstPageLink;
if (typeof axe._cache.get('firstPageLink') !== 'undefined') {
firstPageLink = axe._cache.get('firstPageLink');
} else {
// define a skip link as any anchor element whose href starts with `#...`
// and which precedes the first anchor element whose href doesn't start
Expand All @@ -29,7 +29,7 @@ dom.isSkipLink = function(element) {
)[0];

// null will signify no first page link
axe._cache.firstPageLink = firstPageLink || null;
axe._cache.set('firstPageLink', firstPageLink || null);
}

// if there are no page links then all all links will need to be
Expand Down
33 changes: 33 additions & 0 deletions lib/core/base/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
(function() {
'use strict';
let _cache = {};

const cache = {
/**
* Set an item in the cache.
* @param {String} key - Name of the key.
* @param {*} value - Value to store.
*/
set(key, value) {
_cache[key] = value;
},

/**
* Retrieve an item from the cache.
* @param {String} key - Name of the key the value was stored as.
* @returns {*} The item stored
*/
get(key) {
return _cache[key];
},

/**
* Clear the cache.
*/
clear() {
_cache = {};
}
};

axe._cache = cache;
})();
6 changes: 1 addition & 5 deletions lib/core/public/run-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

// Clean up after resolve / reject
function cleanup() {
axe._cache = undefined;
axe._cache.clear();
axe._tree = undefined;
axe._selectorData = undefined;
}
Expand All @@ -19,10 +19,6 @@ function cleanup() {
function runRules(context, options, resolve, reject) {
'use strict';
try {
axe._cache = {
nodeMap: new WeakMap()
};

context = new Context(context);
axe._tree = context.flatTree;
axe._selectorData = axe.utils.getSelectorData(context.flatTree);
Expand Down
21 changes: 17 additions & 4 deletions lib/core/utils/flattened-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function virtualDOMfromNode(node, shadowId) {
return vNodeCache._tabbableElements;
}
};
axe._cache.nodeMap.set(node, vNode);
axe._cache.get('nodeMap').set(node, vNode);
return vNode;
}

Expand Down Expand Up @@ -78,11 +78,11 @@ function getSlotChildren(node) {
* @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow
* ancestor of the node
*/
axe.utils.getFlattenedTree = function(node, shadowId) {
function flattenTree(node, shadowId) {
// using a closure here and therefore cannot easily refactor toreduce the statements
var retVal, realArray, nodeName;
function reduceShadowDOM(res, child) {
var replacements = axe.utils.getFlattenedTree(child, shadowId);
var replacements = flattenTree(child, shadowId);
if (replacements) {
res = res.concat(replacements);
}
Expand Down Expand Up @@ -144,6 +144,19 @@ axe.utils.getFlattenedTree = function(node, shadowId) {
return undefined;
}
}
}

/**
* Recursvely returns an array of the virtual DOM nodes at this level
* excluding comment nodes and the shadow DOM nodes <content> and <slot>
*
* @param {Node} node the current node
* @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow
* ancestor of the node
*/
axe.utils.getFlattenedTree = function(node, shadowId) {
axe._cache.set('nodeMap', new WeakMap());
return flattenTree(node, shadowId);
};

/**
Expand All @@ -154,5 +167,5 @@ axe.utils.getFlattenedTree = function(node, shadowId) {
*/
axe.utils.getNodeFromTree = function(vNode, node) {
const el = node || vNode;
return axe._cache.nodeMap.get(el);
return axe._cache.get('nodeMap') ? axe._cache.get('nodeMap').get(el) : null;
};
32 changes: 32 additions & 0 deletions test/core/base/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
describe('axe._cache', function() {
'use strict';

it('should set items from the cache without error', function() {
function fn() {
axe._cache.set('foo', 'bar');
}
assert.doesNotThrow(fn);
});

it('should not throw for non-string keys', function() {
function fn() {
axe._cache.set(1, 'bar');
axe._cache.set({}, 'bar');
axe._cache.set(null, 'bar');
}
assert.doesNotThrow(fn);
});

it('should get an item from the cache', function() {
axe._cache.set('foo', 'bar');
var value = axe._cache.get('foo');
assert.equal(value, 'bar');
});

it('should clear the cache', function() {
axe._cache.set('foo', 'bar');
axe._cache.clear();
var value = axe._cache.get('foo');
assert.isUndefined(value);
});
});
27 changes: 27 additions & 0 deletions test/integration/full/plugin/plugin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Plugin Test</title>
<link
rel="stylesheet"
type="text/css"
href="/node_modules/mocha/mocha.css"
/>
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/axe.js"></script>
<script>
mocha.setup({
timeout: 10000,
ui: 'bdd'
});
var assert = chai.assert;
</script>
</head>
<body>
<h1 class="my-heading">Hello World</h1>
<main id="mocha"></main>
<script src="plugin.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
107 changes: 107 additions & 0 deletions test/integration/full/plugin/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
describe('plugin test', function() {
it('should register the plugin', function() {
axe.registerPlugin({
id: 'doStuff',
run: function(id, action, options, callback) {
var frames;
var q = axe.utils.queue();
var that = this;
frames = axe.utils.toArray(document.querySelectorAll('iframe, frame'));

frames.forEach(function(frame) {
q.defer(function(done) {
axe.utils.sendCommandToFrame(
frame,
{
options: options,
command: 'run-doStuff',
parameter: id,
action: action
},
function() {
done();
}
);
});
});

if (!options.context.length) {
q.defer(function(done) {
that._registry[id][action].call(
that._registry[id],
document,
options,
done
);
});
}
q.then(callback);
},
commands: [
{
id: 'run-doStuff',
callback: function(data, callback) {
return axe.plugins.doStuff.run(
data.parameter,
data.action,
data.options,
callback
);
}
}
]
});

assert.isOk(axe.plugins.doStuff);
});

it('should add plugin instance', function() {
var highlight = {
id: 'highlight',
highlighter: function(node) {
node.style.backgroundColor = 'yellow';
this._node = node;
},
run: function(contextNode, options, done) {
var that = this;
Array.prototype.slice
.call(contextNode.querySelectorAll(options.selector))
.forEach(function(node) {
that.highlighter(node, options);
});
done();
},
cleanup: function(done) {
this._node.style.backgroundColor = '';
done();
}
};

axe.plugins.doStuff.add(highlight);
assert.equal(axe.plugins.doStuff._registry.highlight, highlight);
});

it('should run the plugin', function(done) {
var h1 = document.querySelector('.my-heading');

axe.plugins.doStuff.run(
'highlight',
'run',
{
selector: '.my-heading',
context: []
},
function() {
assert.equal(h1.style.backgroundColor, 'yellow');
done();
}
);
});

it('should cleanup the plugin', function() {
var h1 = document.querySelector('.my-heading');

axe.cleanup();
assert.equal(h1.style.backgroundColor, '');
});
});
6 changes: 2 additions & 4 deletions test/testutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,6 @@ testUtils.isIE11 = (function isIE11(navigator) {

axe.testUtils = testUtils;

beforeEach(function() {
axe._cache = {
nodeMap: new WeakMap()
};
afterEach(function() {
axe._cache.clear();
});

0 comments on commit 91a04c5

Please sign in to comment.