Skip to content

Commit

Permalink
Core: Nested modules
Browse files Browse the repository at this point in the history
Allows modules to be nested. This is achieved by adding a third possible
argument to the QUnit.module method, which is a function that is called
for immediate processing. Also, testEnvironments for parent modules
are invoked in recursively for each test (outer-first).

Fixes qunitjs#543
  • Loading branch information
Askelkana authored and leobalter committed Sep 10, 2015
1 parent 1967b78 commit 9543457
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 26 deletions.
48 changes: 40 additions & 8 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ QUnit.version = "@VERSION";
extend( QUnit, {

// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
var currentModule = {
name: name,
testEnvironment: testEnvironment,
tests: []
};
module: function( name, testEnvironment, executeNow ) {
var currentModule = config.currentModule;
var module;
if ( arguments.length === 2 ) {
if ( testEnvironment instanceof Function ) {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}

// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
Expand All @@ -27,8 +30,37 @@ extend( QUnit, {
delete testEnvironment.teardown;
}

config.modules.push( currentModule );
config.currentModule = currentModule;
module = createModule();

if ( executeNow instanceof Function ) {
config.moduleStack.push( module );
setCurrentModule( module );
executeNow();
config.moduleStack.pop();
module = module.parentModule || currentModule;
}

setCurrentModule( module );

function createModule() {
var parentModule = config.moduleStack.length ?
config.moduleStack.slice( -1 )[ 0 ] : null;
var moduleName = parentModule !== null ?
[ parentModule.name, name ].join( " > " ) : name;
var module = {
name: moduleName,
parentModule: parentModule,
testEnvironment: testEnvironment,
tests: []
};
config.modules.push( module );
return module;
}

function setCurrentModule( module ) {
config.currentModule = module;
}

},

// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
Expand Down
3 changes: 3 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ var config = {
// Set of all modules.
modules: [],

// Stack of nested modules
moduleStack: [],

// The first unnamed module
currentModule: {
name: "",
Expand Down
56 changes: 38 additions & 18 deletions src/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,12 @@ Test.prototype = {
this.callbackStarted = now();

if ( config.notrycatch ) {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
runTest( this );
return;
}

try {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
runTest( this );
} catch ( e ) {
this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
Expand All @@ -120,6 +118,11 @@ Test.prototype = {
QUnit.start();
}
}

function runTest( test ) {
promise = test.callback.call( test.testEnvironment, test.assert );
test.resolvePromise( promise );
}
},

after: function() {
Expand All @@ -132,16 +135,19 @@ Test.prototype = {
return function runHook() {
config.current = test;
if ( config.notrycatch ) {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
callHook();
return;
}
try {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
callHook();
} catch ( error ) {
test.pushFailure( hookName + " failed on " + test.testName + ": " +
( error.message || error ), extractStacktrace( error, 0 ) );
( error.message || error ), extractStacktrace( error, 0 ) );
}

function callHook() {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
}
};
},
Expand All @@ -151,16 +157,20 @@ Test.prototype = {
var hooks = [];

// Hooks are ignored on skipped tests
if ( this.skip ) {
return hooks;
if ( !this.skip ) {
processHooks( this, this.module );
}
return hooks;

if ( this.module.testEnvironment &&
QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
function processHooks( test, module ) {
if ( module.parentModule ) {
processHooks( test, module.parentModule );
}
if ( module.testEnvironment &&
QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
}
}

return hooks;
},

finish: function() {
Expand Down Expand Up @@ -364,7 +374,7 @@ Test.prototype = {
return false;
}

if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
if ( module && !testInModuleChain( this.module ) ) {
return false;
}

Expand All @@ -384,8 +394,18 @@ Test.prototype = {

// Otherwise, do the opposite
return !include;
}

function testInModuleChain( testModule ) {
var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
if ( testModuleName === module ) {
return true;
} else if ( testModule.parentModule ) {
return testInModuleChain( testModule.parentModule );
} else {
return false;
}
}
}
};

// Resets the test setup. Useful for tests that modify the DOM.
Expand Down
48 changes: 48 additions & 0 deletions test/main/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,51 @@ QUnit.module( "Deprecated setup/teardown", {
QUnit.test( "before/after order", function( assert ) {
assert.expect( 1 );
});

QUnit.module( "nested modules", function() {
QUnit.module( "first outer", {
afterEach: function( assert ) {
assert.ok( true, "first outer module afterEach called" );
},
beforeEach: function( assert ) {
assert.ok( true, "first outer beforeEach called" );
}
},
function() {
QUnit.module( "first inner", {
afterEach: function( assert ) {
assert.ok( true, "first inner module afterEach called" );
},
beforeEach: function( assert ) {
assert.ok( true, "first inner module beforeEach called" );
}
},
function() {
QUnit.test( "in module, before- and afterEach called in out-in-out " +
"order",
function( assert ) {
var module = assert.test.module;
assert.equal( module.name,
"nested modules > first outer > first inner" );
assert.expect( 5 );
});
});
QUnit.test( "test after nested module is processed", function( assert ) {
var module = assert.test.module;
assert.equal( module.name, "nested modules > first outer" );
assert.expect( 3 );
});
QUnit.module( "second inner" );
QUnit.test( "test after non-nesting module declared", function( assert ) {
var module = assert.test.module;
assert.equal( module.name, "nested modules > first outer > second inner" );
assert.expect( 3 );
});
});
QUnit.module( "second outer" );
QUnit.test( "test after all nesting modules processed and new module declared",
function( assert ) {
var module = assert.test.module;
assert.equal( module.name, "nested modules > second outer" );
});
});

0 comments on commit 9543457

Please sign in to comment.