Skip to content

Commit

Permalink
Core: Introduce before/after hooks for modules
Browse files Browse the repository at this point in the history
  • Loading branch information
Trent Willis committed Jan 19, 2016
1 parent 7c94f6f commit 9bdb29f
Show file tree
Hide file tree
Showing 5 changed files with 366 additions and 17 deletions.
8 changes: 6 additions & 2 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ extend( QUnit, {
module = createModule();

moduleFns = {
before: setHook( module, "before" ),
beforeEach: setHook( module, "beforeEach" ),
afterEach: setHook( module, "afterEach" )
afterEach: setHook( module, "afterEach" ),
after: setHook( module, "after" )
};

if ( executeNow instanceof Function ) {
Expand All @@ -56,11 +58,13 @@ extend( QUnit, {
var module = {
name: moduleName,
parentModule: parentModule,
tests: []
tests: [],
testsRun: 0
};

var env = {};
if ( parentModule ) {
parentModule.childModule = module;
extend( env, parentModule.testEnvironment );
delete env.beforeEach;
delete env.afterEach;
Expand Down
50 changes: 48 additions & 2 deletions src/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ Test.prototype = {
config.current = this;

if ( this.module.testEnvironment ) {
delete this.module.testEnvironment.before;
delete this.module.testEnvironment.beforeEach;
delete this.module.testEnvironment.afterEach;
delete this.module.testEnvironment.after;
}
this.testEnvironment = extend( {}, this.module.testEnvironment );

Expand Down Expand Up @@ -132,10 +134,22 @@ Test.prototype = {
checkPollution();
},

queueHook: function( hook, hookName ) {
queueHook: function( hook, hookName, hookOwner ) {
var promise,
test = this;
return function runHook() {
if ( hookName === "before" ) {
if ( hookOwner.testsRun !== 0 ) {
return;
}

test.preserveEnvironment = true;
}

if ( hookName === "after" && hookOwner.testsRun !== numberOfTests(hookOwner) - 1 ) {
return;
}

config.current = test;
if ( config.notrycatch ) {
callHook();
Expand Down Expand Up @@ -165,7 +179,7 @@ Test.prototype = {
}
if ( module.testEnvironment &&
QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
hooks.push( test.queueHook( module.testEnvironment[ handler ], handler, module ) );
}
}

Expand Down Expand Up @@ -204,6 +218,7 @@ Test.prototype = {
}
}

notifyTestsRan( this.module );
runLoggingCallbacks( "testDone", {
name: this.testName,
module: this.module.name,
Expand Down Expand Up @@ -232,6 +247,13 @@ Test.prototype = {
config.current = undefined;
},

preserveTestEnvironment: function() {
if ( this.preserveEnvironment ) {
this.module.testEnvironment = this.testEnvironment;
this.testEnvironment = extend( {}, this.module.testEnvironment );
}
},

queue: function() {
var priority,
test = this;
Expand All @@ -248,16 +270,25 @@ Test.prototype = {
test.before();
},

test.hooks( "before" ),

function() {
test.preserveTestEnvironment();
},

test.hooks( "beforeEach" ),

function() {
test.run();
},

test.hooks( "afterEach" ).reverse(),
test.hooks( "after" ).reverse(),

function() {
test.after();
},

function() {
test.finish();
}
Expand Down Expand Up @@ -601,3 +632,18 @@ function only( testName, expected, callback, async ) {

newTest.queue();
}

function numberOfTests( module ) {
var count = module.tests.length;
while ( module = module.childModule ) {
count += module.tests.length;
}
return count;
}

function notifyTestsRan( module ) {
module.testsRun++;
while ( module = module.parentModule ) {
module.testsRun++;
}
}
165 changes: 163 additions & 2 deletions test/main/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,35 @@ QUnit.test( "fails if callback is called more than callback call count", functio

});

QUnit.module( "assert.async fails if callback is called more than once in", {
before: function( assert ) {

// Having an outer async flow in this test avoids the need to manually modify QUnit
// internals in order to avoid post-`done` assertions causing additional failures
var done = assert.async();

assert.expect( 1 );

// Duck-punch to force an Error to be thrown instead of a `pushFailure` call
assert.test.pushFailure = function( msg ) {
throw new Error( msg );
};

var overDone = assert.async();
overDone();

assert.throws(function() {
overDone();
}, new RegExp( "Too many calls to the `assert.async` callback" ) );

done();
}
});

QUnit.test( "before", function() {
// noop
});

QUnit.module( "assert.async fails if callback is called more than once in", {
beforeEach: function( assert ) {

Expand Down Expand Up @@ -290,6 +319,52 @@ QUnit.test( "afterEach", function( /* assert */ ) {
// noop
});

QUnit.module( "assert.async fails if callback is called more than once in", {
after: function( assert ) {

// Having an outer async flow in this test avoids the need to manually modify QUnit
// internals in order to avoid post-`done` assertions causing additional failures
var done = assert.async();

assert.expect( 1 );

// Duck-punch to force an Error to be thrown instead of a `pushFailure` call
assert.test.pushFailure = function( msg ) {
throw new Error( msg );
};

var overDone = assert.async();
overDone();

assert.throws(function() {
overDone();
}, new RegExp( "Too many calls to the `assert.async` callback" ) );

done();
}
});

QUnit.test( "after", function() {
// noop
});

QUnit.module( "assert.async in before", {
before: function( assert ) {
var done = assert.async(),
testContext = this;
setTimeout(function() {
testContext.state = "before";
done();
});
}
});

QUnit.test( "before synchronized", function( assert ) {
assert.expect( 1 );
assert.equal( this.state, "before", "before synchronized before test callback was " +
"executed" );
});

QUnit.module( "assert.async in beforeEach", {
beforeEach: function( assert ) {
var done = assert.async(),
Expand Down Expand Up @@ -338,6 +413,37 @@ QUnit.test( "afterEach will synchronize", function( assert ) {
assert.expect( 1 );
});

QUnit.module( "assert.async before after", {
after: function( assert ) {
assert.equal( this.state, "done", "test callback synchronized before after was " +
"executed" );
}
});

QUnit.test( "after will synchronize", function( assert ) {
assert.expect( 1 );
var done = assert.async(),
testContext = this;
setTimeout(function() {
testContext.state = "done";
done();
});
});

QUnit.module( "assert.async in after", {
after: function( assert ) {
var done = assert.async();
setTimeout(function() {
assert.ok( true, "after synchronized before test was finished" );
done();
});
}
});

QUnit.test( "after will synchronize", function( assert ) {
assert.expect( 1 );
});

QUnit.module( "assert.async callback event loop timing" );

QUnit.test( "`done` can be called synchronously", function( assert ) {
Expand Down Expand Up @@ -461,6 +567,30 @@ QUnit.test( "cannot allow assertions between first `done` call and second `asser
});
});

QUnit.module( "assert after last done in before fail, but allow other phases to run", {
before: function( assert ) {
_setupForFailingAssertionsAfterAsyncDone.call( this, assert );

// THIS IS THE ACTUAL TEST!
assert.expect( 3 );
this._assertCatch(function() {
assert.async()();

// FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`)
assert.ok( true, "should fail with a special `done`-related error message if called " +
"after final `done` even if result is passing" );
});
},

after: function( assert ) {
assert.ok( true, "This assertion should still run in after" );
}
});

QUnit.test( "before will fail but test and after will still run", function( assert ) {
assert.ok( true, "This assertion should still run in the test callback" );
});

QUnit.module( "assert after last done in beforeEach fail, but allow other phases to run", {
beforeEach: function( assert ) {
_setupForFailingAssertionsAfterAsyncDone.call( this, assert );
Expand All @@ -486,19 +616,27 @@ QUnit.test( "beforeEach will fail but test and afterEach will still run", functi
});

QUnit.module( "assert after last done in test fail, but allow other phases to run", {
before: function( assert ) {
assert.ok( true, "This assertion should still run in before" );
},

beforeEach: function( assert ) {
_setupForFailingAssertionsAfterAsyncDone.call( this, assert );

assert.expect( 3 );
assert.expect( 5 );
assert.ok( true, "This assertion should still run in beforeEach" );
},

afterEach: function( assert ) {
assert.ok( true, "This assertion should still run in afterEach" );
},

after: function( assert ) {
assert.ok( true, "This assertion should still run in after" );
}
});

QUnit.test( "test will fail, but beforeEach and afterEach will still run", function( assert ) {
QUnit.test( "test will fail, but other hooks will still run", function( assert ) {
this._assertCatch(function() {
assert.async()();

Expand Down Expand Up @@ -530,3 +668,26 @@ QUnit.module( "assert after last done in afterEach fail, but allow other phases
QUnit.test( "afterEach will fail but beforeEach and test will still run", function( assert ) {
assert.ok( true, "This assertion should still run in the test callback" );
});

QUnit.module( "assert after last done in after fail, but allow other phases to run", {
before: function( assert ) {
_setupForFailingAssertionsAfterAsyncDone.call( this, assert );

assert.expect( 3 );
assert.ok( true, "This assertion should still run in before" );
},

after: function( assert ) {
this._assertCatch(function() {
assert.async()();

// FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`)
assert.ok( true, "should fail with a special `done`-related error message if called " +
"after final `done` even if result is passing" );
});
}
});

QUnit.test( "after will fail but before and test will still run", function( assert ) {
assert.ok( true, "This assertion should still run in the test callback" );
});
Loading

0 comments on commit 9bdb29f

Please sign in to comment.