Showing with 253 additions and 26 deletions.
  1. +65 −8 src/core.js
  2. +3 −0 src/core/config.js
  3. +38 −18 src/test.js
  4. +147 −0 test/main/modules.js
@@ -9,12 +9,16 @@ 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 module, moduleFns;
var currentModule = config.currentModule;

if ( arguments.length === 2 ) {
if ( testEnvironment instanceof Function ) {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}

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

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

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

if ( executeNow instanceof Function ) {
config.moduleStack.push( module );
setCurrentModule( module );
executeNow.call( module.testEnvironment, moduleFns );
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,
tests: []
};

var env = {};
if ( parentModule ) {
extend( env, parentModule.testEnvironment );
delete env.beforeEach;
delete env.afterEach;
}
extend( env, testEnvironment );
module.testEnvironment = env;

config.modules.push( module );
return module;
}

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

},

// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
@@ -262,3 +309,13 @@ function done() {
runtime: runtime
});
}

function setHook( module, hookName ) {
if ( module.testEnvironment === undefined ) {
module.testEnvironment = {};
}

return function( callback ) {
module.testEnvironment[ hookName ] = callback;
};
}
@@ -55,6 +55,9 @@ var config = {
// Set of all modules.
modules: [],

// Stack of nested modules
moduleStack: [],

// The first unnamed module
currentModule: {
name: "",
@@ -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 ) );
@@ -120,6 +118,11 @@ Test.prototype = {
QUnit.start();
}
}

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

after: function() {
@@ -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 );
}
};
},
@@ -150,16 +156,20 @@ Test.prototype = {
hooks: function( handler ) {
var hooks = [];

// Hooks are ignored on skipped tests
if ( this.skip ) {
return hooks;
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 ) );
}
}

if ( this.module.testEnvironment &&
QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
// Hooks are ignored on skipped tests
if ( !this.skip ) {
processHooks( this, this.module );
}

return hooks;
},

@@ -355,6 +365,17 @@ Test.prototype = {
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
fullName = ( this.module.name + ": " + this.testName ).toLowerCase();

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;
}
}

// Internally-generated tests are always valid
if ( this.callback && this.callback.validTest ) {
return true;
@@ -364,7 +385,7 @@ Test.prototype = {
return false;
}

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

@@ -385,7 +406,6 @@ Test.prototype = {
// Otherwise, do the opposite
return !include;
}

};

// Resets the test setup. Useful for tests that modify the DOM.
@@ -147,3 +147,150 @@ QUnit.module( "Deprecated setup/teardown", {
QUnit.test( "before/after order", function( assert ) {
assert.expect( 1 );
});

QUnit.module( "pre-nested modules");

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" );
});
});

QUnit.test( "modules with nested functions does not spread beyond", function( assert ) {
assert.equal( assert.test.module.name, "pre-nested modules" );
});

QUnit.module( "contained suite arguments", function( hooks ) {
QUnit.test( "hook functions", function( assert ) {
assert.strictEqual( typeof hooks.beforeEach, "function" );
assert.strictEqual( typeof hooks.afterEach, "function" );
} );

QUnit.module( "outer hooks", function( hooks ) {
var beforeEach = hooks.beforeEach;
var afterEach = hooks.afterEach;

beforeEach( function( assert ) {
assert.ok( true, "beforeEach called" );
} );

afterEach( function( assert ) {
assert.ok( true, "afterEach called" );
} );

QUnit.test( "call hooks", function( assert ) {
assert.expect( 2 );
} );

QUnit.module( "stacked inner hooks", function( hooks ) {
var beforeEach = hooks.beforeEach;
var afterEach = hooks.afterEach;

beforeEach( function( assert ) {
assert.ok( true, "nested beforeEach called" );
} );

afterEach( function( assert ) {
assert.ok( true, "nested afterEach called" );
} );

QUnit.test( "call hooks", function( assert ) {
assert.expect( 4 );
} );
} );
} );
} );

QUnit.module( "contained suite `this`", function( hooks ) {
this.outer = 1;

hooks.beforeEach( function() {
this.outer++;
} );

hooks.afterEach( function( assert ) {
assert.equal(
this.outer, 42,
"in-test environment modifications are visible by afterEach callbacks"
);
} );

QUnit.test( "`this` is shared from modules to the tests", function( assert ) {
assert.equal( this.outer, 2 );
this.outer = 42;
} );

QUnit.test( "sibling tests don't share environments", function( assert ) {
assert.equal( this.outer, 2 );
this.outer = 42;
} );

QUnit.module( "nested suite `this`", function( hooks ) {
this.inner = true;

hooks.beforeEach( function( assert ) {
assert.ok( this.outer );
assert.ok( this.inner );
} );

hooks.afterEach( function( assert ) {
assert.ok( this.outer );
assert.ok( this.inner );

// This change affects the outermodule afterEach assertion.
this.outer = 42;
} );

QUnit.test( "inner modules share outer environments", function( assert ) {
assert.ok( this.outer );
assert.ok( this.inner );
} );
} );

QUnit.test( "tests can't see environments from nested modules", function( assert ) {
assert.strictEqual( this.inner, undefined );
this.outer = 42;
} );
} );