diff --git a/lib/core/public/load.js b/lib/core/public/load.js index 58f23a2eaf..a6bcb395ec 100644 --- a/lib/core/public/load.js +++ b/lib/core/public/load.js @@ -18,7 +18,11 @@ function runCommand(data, keepalive, callback) { switch (data.command) { case 'rules': - return runRules(context, options, resolve, reject); + return runRules(context, options, function (results, cleanup) { + resolve(results); + // Cleanup AFTER resolve so that selectors can be generated + cleanup(); + }, reject); case 'cleanup-plugin': return cleanupPlugins(resolve, reject); default: diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index 46209263c2..83432c9251 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -1,9 +1,8 @@ /*global Context */ /*exported runRules */ -function complete (cb, ...args) { - cb(...args); - // Clean up after resolve / reject +// Clean up after resolve / reject +function cleanup () { axe._tree = undefined; axe._selectorData = undefined; } @@ -13,7 +12,8 @@ function complete (cb, ...args) { * @private * @param {Object} context The `Context` specification object @see Context * @param {Array} options Optional RuleOptions - * @param {Function} callback The function to invoke when analysis is complete; receives an array of `RuleResult`s + * @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'; @@ -22,7 +22,8 @@ function runRules(context, options, resolve, reject) { axe._tree = context.flatTree; axe._selectorData = axe.utils.getSelectorData(context.flatTree); } catch (e) { - return complete(reject, e); + cleanup(); + return reject(e); } var q = axe.utils.queue(); @@ -66,15 +67,18 @@ function runRules(context, options, resolve, reject) { results = results.map(axe.utils.finalizeRuleResult); } try { - complete(resolve, results); + resolve(results, cleanup); } catch(e) { - complete(axe.log, e); + cleanup(); + axe.log(e); } } catch (e) { - complete(reject, e); + cleanup(); + reject(e); } }).catch((e) => { - complete(reject, e); + cleanup(); + reject(e); }); } diff --git a/lib/core/public/run.js b/lib/core/public/run.js index d3d48dbd19..1389734b91 100644 --- a/lib/core/public/run.js +++ b/lib/core/public/run.js @@ -112,33 +112,35 @@ axe.run = function (context, options, callback) { }); } - axe._runRules(context, options, function (rawResults) { - let respond = function (results) { - 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) { - 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; +}; \ No newline at end of file diff --git a/test/core/public/run-rules.js b/test/core/public/run-rules.js index 38e3943a7a..15a2ad2511 100644 --- a/test/core/public/run-rules.js +++ b/test/core/public/run-rules.js @@ -651,7 +651,7 @@ describe('runRules', function () { }, isNotCalled); }); - it('should clear up axe._tree / axe._selectorData after resolving', function (done) { + it('should return a cleanup method', function (done) { axe._load({ rules: [{ id: 'html', selector: 'html', @@ -663,14 +663,14 @@ describe('runRules', function () { } }], messages: {}}); - runRules(document, {}, function resolve() { + runRules(document, {}, function resolve(out, cleanup) { assert.isDefined(axe._tree); assert.isDefined(axe._selectorData); - setTimeout(function () { - assert.isUndefined(axe._tree); - assert.isUndefined(axe._selectorData); - done(); - }, 10); + + cleanup(); + assert.isUndefined(axe._tree); + assert.isUndefined(axe._selectorData); + done(); }, isNotCalled); }); @@ -681,18 +681,10 @@ describe('runRules', function () { createFrames(function () { setTimeout(function () { - runRules(document, {}, function () { - assert.ok(false, 'You shall not pass!'); + runRules(document, {}, isNotCalled, function () { + assert.isUndefined(axe._tree); + assert.isUndefined(axe._selectorData); done(); - }, - function () { - assert.isDefined(axe._tree); - assert.isDefined(axe._selectorData); - setTimeout(function () { - assert.isUndefined(axe._tree); - assert.isUndefined(axe._selectorData); - done(); - }, 10); }); }, 100); }); diff --git a/test/core/public/run.js b/test/core/public/run.js index ce1f2a7b42..e01a914780 100644 --- a/test/core/public/run.js +++ b/test/core/public/run.js @@ -122,7 +122,7 @@ describe('axe.run', function () { it('gives results to the second argument on the callback', function (done) { axe._runRules = function (ctxt, opt, resolve) { axe._runRules = origRunRules; - resolve('MB Bomb'); + resolve('MB Bomb', noop); }; axe.run({ reporter: 'raw' }, function (err, result) { @@ -135,7 +135,7 @@ describe('axe.run', function () { it('does not run the callback twice if it throws', function (done) { var calls = 0; axe._runRules = function (ctxt, opt, resolve) { - resolve([]); + resolve([], noop); }; var log = axe.log; @@ -155,13 +155,30 @@ describe('axe.run', function () { 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*/ - it('returns an error to catch if axe fails', - (!window.Promise) ? undefined : function (done) { + 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!'); @@ -177,11 +194,10 @@ describe('axe.run', function () { assert.instanceOf(p, window.Promise); }); - it('returns a promise if no callback was given', - (!window.Promise) ? undefined : function (done) { + promiseIt('returns a promise if no callback was given', function (done) { axe._runRules = function (ctxt, opt, resolve) { axe._runRules = origRunRules; - resolve('World party'); + resolve('World party', noop); }; var p = axe.run({ reporter: 'raw' }); @@ -193,10 +209,9 @@ describe('axe.run', function () { assert.instanceOf(p, window.Promise); }); - it('does not error if then() throws', - (!window.Promise) ? undefined : function (done) { + promiseIt('does not error if then() throws', function (done) { axe._runRules = function (ctxt, opt, resolve) { - resolve([]); + resolve([], noop); }; axe.run() @@ -211,6 +226,24 @@ describe('axe.run', function () { 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); + }); }); diff --git a/test/core/reporters/na.js b/test/core/reporters/na.js index f0e71feaa6..f9cdff995c 100644 --- a/test/core/reporters/na.js +++ b/test/core/reporters/na.js @@ -84,7 +84,7 @@ describe('reporters - na', function() { }); orig = axe._runRules; axe._runRules = function(ctxt, options, cb) { - cb(results); + cb(results, function noop () {}); }; }); diff --git a/test/core/reporters/no-passes.js b/test/core/reporters/no-passes.js index 431930cf1d..6ff896b9cf 100644 --- a/test/core/reporters/no-passes.js +++ b/test/core/reporters/no-passes.js @@ -74,7 +74,7 @@ describe('reporters - no-passes', function() { }); orig = axe._runRules; axe._runRules = function(ctxt, options, cb) { - cb(results); + cb(results, function noop () {}); }; }); diff --git a/test/core/reporters/raw.js b/test/core/reporters/raw.js index c2ce6d0211..f2290d6039 100644 --- a/test/core/reporters/raw.js +++ b/test/core/reporters/raw.js @@ -5,7 +5,7 @@ describe('reporters - raw', function () { axe._load({}); var orig = axe._runRules; axe._runRules = function (_, __, cb) { - cb('foo'); + cb('foo', function noop () {}); }; axe.run({ reporter: 'raw'}, function (err, results) { diff --git a/test/core/reporters/v1.js b/test/core/reporters/v1.js index 7766d8ddb5..4e54d1c0ce 100644 --- a/test/core/reporters/v1.js +++ b/test/core/reporters/v1.js @@ -132,7 +132,7 @@ describe('reporters - v1', function() { }); orig = axe._runRules; axe._runRules = function(ctxt, options, cb) { - cb(results); + cb(results, function noop () {}); }; }); diff --git a/test/core/reporters/v2.js b/test/core/reporters/v2.js index 76b8581c73..409207e1fc 100644 --- a/test/core/reporters/v2.js +++ b/test/core/reporters/v2.js @@ -74,7 +74,7 @@ describe('reporters - v2', function() { }); orig = axe._runRules; axe._runRules = function(ctxt, options, cb) { - cb(results); + cb(results, function noop () {}); }; }); diff --git a/test/integration/full/rerun/frames/frame.html b/test/integration/full/rerun/frames/frame.html new file mode 100644 index 0000000000..672061d966 --- /dev/null +++ b/test/integration/full/rerun/frames/frame.html @@ -0,0 +1,10 @@ + + + + + + + +

Just a frame

+ + diff --git a/test/integration/full/rerun/rerun.html b/test/integration/full/rerun/rerun.html new file mode 100644 index 0000000000..bb8929eb6e --- /dev/null +++ b/test/integration/full/rerun/rerun.html @@ -0,0 +1,26 @@ + + + +Rerun Test + + + + + + + + + + + +
+ + + + diff --git a/test/integration/full/rerun/rerun.js b/test/integration/full/rerun/rerun.js new file mode 100644 index 0000000000..b6520a6bc2 --- /dev/null +++ b/test/integration/full/rerun/rerun.js @@ -0,0 +1,30 @@ +describe('rerun axe in the same tick' + window.location.pathname, function () { + 'use strict'; + + before(function (done) { + axe.testUtils.awaitNestedLoad(done); + }); + + it('can run multiple times without interfering with itself', function (done) { + var options = { runOnly: { + type: 'rule', + values: ['html-has-lang'] + }}; + + // First run + axe.run(options, function (err1, res1) { + assert.isNull(err1); + + // Second run, on the same tick + axe.run(options, function (err2, res2) { + assert.isNull(err2); + + delete res1.timestamp; + delete res2.timestamp; + assert.deepEqual(res1, res2); + done(); + }); + }); + }); +}); + \ No newline at end of file