Skip to content

Commit

Permalink
Core: Adding promise handling on qunit callbacks: begin, moduleStart,…
Browse files Browse the repository at this point in the history
… testStart, testDone, moduleDone, and done (#1307)

* Core: Adding promise handling on qunit callbacks
* Tests: Fix cli tests to work with promise handling in qunit callbacks
* Core: handling callback promises serially
* Adding es6-promise as a local polyfill for unsupported browsers
* Adding documenation to qunit callbacks for handling promises
  • Loading branch information
step2yeung authored and trentmwillis committed Nov 1, 2018
1 parent ed88074 commit b1a2552
Show file tree
Hide file tree
Showing 21 changed files with 1,232 additions and 83 deletions.
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ module.exports = function( grunt ) {
"test/reorderError1.html",
"test/reorderError2.html",
"test/callbacks.html",
"test/callbacks-promises.html",
"test/events.html",
"test/events-in-test.html",
"test/logs.html",
Expand Down
13 changes: 12 additions & 1 deletion docs/callbacks/QUnit.begin.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.begin( callback )`

Register a callback to fire whenever the test suite begins.
Register a callback to fire whenever the test suite begins. The callback can return a promise that will be waited for before the next callback is handled.

`QUnit.begin()` is called once before running any tests.

Expand Down Expand Up @@ -39,3 +39,14 @@ QUnit.begin( ( { totalTests } ) => {
console.log( `Test amount: ${totalTests}` );
});
```

Returning a promise:

```js
QUnit.begin( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
13 changes: 12 additions & 1 deletion docs/callbacks/QUnit.done.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.done( callback )`

Register a callback to fire whenever the test suite ends.
Register a callback to fire whenever the test suite ends. The callback can return a promise that will be waited for before the next callback is handled.

| parameter | description |
|-----------|-------------|
Expand Down Expand Up @@ -40,3 +40,14 @@ QUnit.done( ( { total, failed, passed, runtime } ) => {
console.log( `Total: ${total}, Failed: ${failed}, Passed: ${passed}, Runtime: ${runtime}` );
});
```

Returning a promise:

```js
QUnit.done( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
2 changes: 2 additions & 0 deletions docs/callbacks/QUnit.log.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The properties of the details argument are listed below as options.
|-----------|-------------|
| callback (function) | Callback to execute. Provides a single argument with the callback details object |

**NOTE: Callback in QUnit.log() does not handle promises and must be synchronous.**

#### Callback details: `callback( details: { result, actual, expected, message, source, module, name, runtime, todo } )`

| parameter | description |
Expand Down
15 changes: 13 additions & 2 deletions docs/callbacks/QUnit.moduleDone.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.moduleDone( callback )`

Register a callback to fire whenever a module ends.
Register a callback to fire whenever a module ends. The callback can return a promise that will be waited for before the next callback is handled.

| parameter | description |
|-----------|-------------|
Expand Down Expand Up @@ -40,4 +40,15 @@ Using modern syntax:
QUnit.moduleDone( ( { name, failed, total } ) => {
console.log( `Finished running: ${name} Failed/total: ${failed}, ${total}` );
});
```
```

Returning a promise:

```js
QUnit.moduleDone( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
13 changes: 12 additions & 1 deletion docs/callbacks/QUnit.moduleStart.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.moduleStart( callback )`

Register a callback to fire whenever a module begins.
Register a callback to fire whenever a module begins. The callback can return a promise that will be waited for before the next callback is handled.

| parameter | description |
|-----------|-------------|
Expand Down Expand Up @@ -37,3 +37,14 @@ QUnit.moduleStart( ( { name } ) => {
console.log( `Now running: ${name}` );
});
```

Returning a promise:

```js
QUnit.moduleStart( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
2 changes: 2 additions & 0 deletions docs/callbacks/QUnit.on.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Register a callback to fire whenever the specified event is emitted. Conforms to
| eventName (string) | The name of the event for which to execute the provided callback. |
| callback (function) | Callback to execute. Receives a single argument representing the data for the event. |

**NOTE: Callback in QUnit.on() does not handle promises and must be synchronous.**

### Example

Printing results of a test suite.
Expand Down
13 changes: 12 additions & 1 deletion docs/callbacks/QUnit.testDone.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.testDone( callback )`

Register a callback to fire whenever a test ends.
Register a callback to fire whenever a test ends. The callback can return a promise that will be waited for before the next callback is handled.

| parameter | description |
|-----------|-------------|
Expand Down Expand Up @@ -70,3 +70,14 @@ QUnit.testDone( ( { module, name, total, passed, failed, skipped, todo, runtime
console.log( JSON.stringify( result, null, 2 ) );
} );
```

Returning a promise:

```js
QUnit.testDone( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
13 changes: 12 additions & 1 deletion docs/callbacks/QUnit.testStart.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ categories:

## `QUnit.testStart( callback )`

Register a callback to fire whenever a test begins.
Register a callback to fire whenever a test begins. The callback can return a promise that will be waited for before the next callback is handled.

| parameter | description |
|-----------|-------------|
Expand Down Expand Up @@ -40,3 +40,14 @@ QUnit.testStart( ( { module, name } ) => {
console.log( `Now running: ${module}: ${name}` );
});
```

Returning a promise:

```js
QUnit.testStart( () => {
return new Promise(function(resolve, reject) {
// do some async work
resolve();
});
});
```
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"babel-preset-env": "1.6.1",
"co": "4.6.0",
"commitplease": "3.1.0",
"es6-promise": "^4.2.5",
"eslint-config-jquery": "1.0.1",
"eslint-plugin-html": "4.0.1",
"eslint-plugin-qunit": "3.2.0",
Expand All @@ -70,6 +71,7 @@
"proxyquire": "1.8.0",
"requirejs": "2.3.5",
"rollup-plugin-babel": "3.0.2",
"rollup-plugin-node-resolve": "^3.4.0",
"semver": "5.4.1"
},
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/* eslint-env node */

var babel = require( "rollup-plugin-babel" );
var resolve = require( "rollup-plugin-node-resolve" );

module.exports = {
format: "iife",
exports: "none",
plugins: [
babel( {
exclude: "node_modules/**"
} )
resolve( { modulesOnly: true } ),
babel()
],

// eslint-disable-next-line no-multi-str
Expand Down
12 changes: 8 additions & 4 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ function scheduleBegin() {
}
}

function unblockAndAdvanceQueue() {
config.blocking = false;
ProcessingQueue.advance();
}

export function begin() {
var i, l,
modulesLog = [];
Expand Down Expand Up @@ -171,11 +176,10 @@ export function begin() {
runLoggingCallbacks( "begin", {
totalTests: Test.count,
modules: modulesLog
} );
} ).then( unblockAndAdvanceQueue );
} else {
unblockAndAdvanceQueue();
}

config.blocking = false;
ProcessingQueue.advance();
}

exportQUnit( QUnit );
Expand Down
22 changes: 17 additions & 5 deletions src/core/logging.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import config from "./config";
import { objectType } from "./utilities";
import Promise from "../promise";

// Register logging callbacks
export function registerLoggingCallbacks( obj ) {
Expand Down Expand Up @@ -34,10 +35,21 @@ export function registerLoggingCallbacks( obj ) {
}

export function runLoggingCallbacks( key, args ) {
var i, l, callbacks;

callbacks = config.callbacks[ key ];
for ( i = 0, l = callbacks.length; i < l; i++ ) {
callbacks[ i ]( args );
var callbacks = config.callbacks[ key ];

// Handling 'log' callbacks separately. Unlike the other callbacks,
// the log callback is not controlled by the processing queue,
// but rather used by asserts. Hence to promisfy the 'log' callback
// would mean promisfying each step of a test
if ( key === "log" ) {
callbacks.map( callback => callback( args ) );
return;
}

// ensure that each callback is executed serially
return callbacks.reduce( ( promiseChain, callback ) => {
return promiseChain.then( () => {
return Promise.resolve( callback( args ) );
} );
}, Promise.resolve( [] ) );
}
45 changes: 30 additions & 15 deletions src/core/processing-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
runLoggingCallbacks
} from "./logging";

import Promise from "../promise";
import {
globalSuite
} from "../core";
Expand All @@ -33,31 +34,44 @@ const taskQueue = [];
function advance() {
advanceTaskQueue();

if ( !taskQueue.length ) {
if ( !taskQueue.length && !config.blocking && !config.current ) {
advanceTestQueue();
}
}

/**
* Advances the taskQueue to the next task if it is ready and not empty.
* Advances the taskQueue with an increased depth
*/
function advanceTaskQueue() {
const start = now();
config.depth = ( config.depth || 0 ) + 1;

while ( taskQueue.length && !config.blocking ) {
processTaskQueue( start );

config.depth--;
}

/**
* Process the first task on the taskQueue as a promise.
* Each task is a function returned by https://github.com/qunitjs/qunit/blob/master/src/test.js#L381
*/
function processTaskQueue( start ) {
if ( taskQueue.length && !config.blocking ) {
const elapsedTime = now() - start;

if ( !defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) {
const task = taskQueue.shift();
task();
Promise.resolve( task() ).then( function() {
if ( !taskQueue.length ) {
advance();
} else {
processTaskQueue( start );
}
} );
} else {
setTimeout( advance );
break;
}
}

config.depth--;
}

/**
Expand Down Expand Up @@ -179,18 +193,19 @@ function done() {
failed: config.stats.bad,
total: config.stats.all,
runtime
} );
} ).then( () => {

// Clear own storage items if all tests passed
if ( storage && config.stats.bad === 0 ) {
for ( let i = storage.length - 1; i >= 0; i-- ) {
const key = storage.key( i );
// Clear own storage items if all tests passed
if ( storage && config.stats.bad === 0 ) {
for ( let i = storage.length - 1; i >= 0; i-- ) {
const key = storage.key( i );

if ( key.indexOf( "qunit-test-" ) === 0 ) {
storage.removeItem( key );
if ( key.indexOf( "qunit-test-" ) === 0 ) {
storage.removeItem( key );
}
}
}
}
} );
}

const ProcessingQueue = {
Expand Down
3 changes: 3 additions & 0 deletions src/promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import _Promise from "es6-promise/lib/es6-promise";

export default typeof Promise !== "undefined" ? Promise : _Promise;

0 comments on commit b1a2552

Please sign in to comment.