| (function(root){ |
| 'use strict'; |
| // testharness doesn't know about async test queues, |
| // so this wrapper takes care of that |
| |
| /* USAGE: |
| runParallelAsyncHarness({ |
| // list of data to test, must be array of objects. |
| // each object must contain a "name" property to describe the test |
| // besides name, the object can contain whatever data you need |
| tests: [ |
| {name: "name of test 1", custom: "data"}, |
| {name: "name of test 2", custom: "data"}, |
| // ... |
| ], |
| |
| // number of tests (tests, not test-cases!) to run concurrently |
| testsPerSlice: 100, |
| |
| // time in milliseconds a test-run takes |
| duration: 1000, |
| |
| // test-cases to run for for the test - there must be at least one |
| // each case creates its separate async_test() instance |
| cases: { |
| // test case named "test1" |
| test1: { |
| // run as a async_test.step() this callback contains your primary assertions |
| start: function(testCaseKey, data, options){}, |
| // run as a async_test.step() this callback contains assertions to be run |
| // when the test ended, immediately before teardown |
| done: function(testCaseKey, data, options){} |
| }, |
| // ... |
| } |
| |
| // all callbacks are optional: |
| |
| // invoked for individual test before it starts so you can setup the environment |
| // like DOM, CSS, adding event listeners and such |
| setup: function(data, options){}, |
| |
| // invoked after a test ended, so you can clean up the environment |
| // like DOM, CSS, removing event listeners and such |
| teardown: function(data, options){}, |
| |
| // invoked before a batch of tests ("slice") are run concurrently |
| // tests is an array of test data objects |
| sliceStart: function(options, tests) |
| |
| // invoked after a batch of tests ("slice") were run concurrently |
| // tests is an array of test data objects |
| sliceDone: function(options, tests) |
| |
| // invoked once all tests are done |
| done: function(options){} |
| }) |
| */ |
| root.runParallelAsyncHarness = function(options) { |
| if (!options.cases) { |
| throw new Error("Options don't contain test cases!"); |
| } |
| |
| var noop = function(){}; |
| |
| // add a 100ms buffer to the test timeout, just in case |
| var duration = Math.ceil(options.duration + 100); |
| |
| // names of individual tests |
| var cases = Object.keys(options.cases); |
| |
| // run tests in a batch of slices |
| // primarily not to overload weak devices (tablets, phones, …) |
| // with too many tests running simultaneously |
| var iteration = -1; |
| var testPerSlice = options.testsPerSlice || 100; |
| var slices = Math.ceil(options.tests.length / testPerSlice); |
| |
| // initialize all async test cases |
| // Note: satisfying testharness.js needs to know all async tests before load-event |
| options.tests.forEach(function(data, index) { |
| data.cases = {}; |
| cases.forEach(function(name) { |
| data.cases[name] = async_test(data.name + " / " + name); |
| }); |
| }); |
| |
| function runLoop() { |
| iteration++; |
| if (iteration >= slices) { |
| // no more slice, we're done |
| (options.done || noop)(options); |
| return; |
| } |
| |
| // grab a slice of testss and initialize them |
| var offset = iteration * testPerSlice; |
| var tests = options.tests.slice(offset, offset + testPerSlice); |
| tests.forEach(function(data) { |
| (options.setup || noop)(data, options); |
| |
| }); |
| |
| // kick off the current slice of tests |
| (options.sliceStart || noop)(options, tests); |
| |
| // perform individual "start" test-case |
| tests.forEach(function(data) { |
| cases.forEach(function(name) { |
| data.cases[name].step(function() { |
| (options.cases[name].start || noop)(data.cases[name], data, options); |
| }); |
| }); |
| }); |
| |
| // conclude slice (possibly abort) |
| var concludeSlice = function() { |
| tests.forEach(function(data) { |
| // perform individual "done" test-case |
| cases.forEach(function(name) { |
| data.cases[name].step(function() { |
| (options.cases[name].done || noop)(data.cases[name], data, options); |
| }); |
| }); |
| // clean up after individual test |
| (options.teardown || noop)(data, options); |
| // tell harness we're done with individual test-cases |
| cases.forEach(function(name) { |
| data.cases[name].done(); |
| }); |
| }); |
| |
| // finish the test for current slice of tests |
| (options.sliceDone || noop)(options, tests); |
| |
| // next test please, give the browser 50ms to do catch its breath |
| setTimeout(runLoop, 50); |
| } |
| |
| // wait on RAF before cleanup to make sure all queued event handlers have run |
| setTimeout(function() {requestAnimationFrame(concludeSlice)},duration); |
| } |
| |
| // allow DOMContentLoaded before actually doing something |
| setTimeout(runLoop, 100); |
| }; |
| |
| })(window); |