Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision ac01c25a

Von Moritz Bunkus vor etwa 10 Jahren hinzugefügt

  • ID ac01c25abcbe7d2ede91723ac91d519bd170fd1f
  • Vorgänger f34953ca
  • Nachfolger 4e68be50

JavaScript-Test-Framework auf Basis von QUnit

Unterschiede anzeigen:

SL/Controller/JSTests.pm
package SL::Controller::JSTests;
use strict;
use parent qw(SL::Controller::Base);
use File::Find ();
use File::Spec ();
use SL::System::Process;
use Rose::Object::MakeMethods::Generic
(
'scalar --get_set_init' => [ qw(all_scripts scripts_to_run) ],
);
#
# actions
#
sub action_run {
my ($self) = @_;
$::request->layout->use_stylesheet("css/qunit.css");
$self->render('js_tests/run', title => $::locale->text('Run JavaScript unit tests'));
}
#
# helpers
#
sub init_all_scripts {
my ($self) = @_;
my $exe_dir = SL::System::Process->exe_dir;
my @scripts;
my $wanted = sub {
return if ( ! -f $File::Find::name ) || ($File::Find::name !~ m{\.js$});
push @scripts, File::Spec->abs2rel($File::Find::name, $exe_dir);
};
File::Find::find($wanted, $exe_dir . '/js/t');
return \@scripts;
}
sub init_scripts_to_run {
my ($self) = @_;
my $filter = $::form->{file_filter} || '.';
return [ grep { m{$filter} } @{ $self->all_scripts } ];
}
1;
css/qunit-1.17.1.css
/*!
* QUnit 1.17.1
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-01-20T19:39Z
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699A4;
background-color: #0D3349;
font-size: 1.5em;
line-height: 1em;
font-weight: 400;
border-radius: 5px 5px 0 0;
}
#qunit-header a {
text-decoration: none;
color: #C2CCD1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #FFF;
}
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 0.5em 0 0.1em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 1em 0.5em 1em;
color: #5E740B;
background-color: #EEE;
overflow: hidden;
}
#qunit-userAgent {
padding: 0.5em 1em 0.5em 1em;
background-color: #2B81AF;
color: #FFF;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
padding: 0.2em;
}
.qunit-url-config {
display: inline-block;
padding: 0.1em;
}
.qunit-filter {
display: block;
float: right;
margin-left: 1em;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 1em 0.4em 1em;
border-bottom: 1px solid #FFF;
list-style-position: inside;
}
#qunit-tests > li {
display: none;
}
#qunit-tests li.running,
#qunit-tests li.pass,
#qunit-tests li.fail,
#qunit-tests li.skipped {
display: list-item;
}
#qunit-tests.hidepass li.running,
#qunit-tests.hidepass li.pass {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li.skipped strong {
cursor: default;
}
#qunit-tests li a {
padding: 0.5em;
color: #C2CCD1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em;
padding: 0.5em;
background-color: #FFF;
border-radius: 5px;
}
.qunit-collapsed {
display: none;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: 0.2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 0.5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #E0F2BE;
color: #374E0C;
text-decoration: none;
}
#qunit-tests ins {
background-color: #FFCACA;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: #000; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3C510C;
background-color: #FFF;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #FFF;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
}
#qunit-tests .fail { color: #000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: #008000; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/*** Skipped tests */
#qunit-tests .skipped {
background-color: #EBECE9;
}
#qunit-tests .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
font-style: normal;
color: #366097;
line-height: 1.8em;
padding: 0 0.5em;
margin: -0.4em 0.4em -0.4em 0;
}
/** Result */
#qunit-testresult {
padding: 0.5em 1em 0.5em 1em;
color: #2B81AF;
background-color: #D2E0E6;
border-bottom: 1px solid #FFF;
}
#qunit-testresult .module-name {
font-weight: 700;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}
css/qunit.css
qunit-1.17.1.css
js/qunit-1.17.1.js
/*!
* QUnit 1.17.1
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-01-20T19:39Z
*/
(function( window ) {
var QUnit,
config,
onErrorFnPrev,
loggingCallbacks = {},
fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
now = Date.now || function() {
return new Date().getTime();
},
globalStartCalled = false,
runStarted = false,
setTimeout = window.setTimeout,
clearTimeout = window.clearTimeout,
defined = {
document: window.document !== undefined,
setTimeout: window.setTimeout !== undefined,
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem( x, x );
sessionStorage.removeItem( x );
return true;
} catch ( e ) {
return false;
}
}())
},
/**
* Provides a normalized error string, correcting an issue
* with IE 7 (and prior) where Error.prototype.toString is
* not properly implemented
*
* Based on http://es5.github.com/#x15.11.4.4
*
* @param {String|Error} error
* @return {String} error message
*/
errorString = function( error ) {
var name, message,
errorString = error.toString();
if ( errorString.substring( 0, 7 ) === "[object" ) {
name = error.name ? error.name.toString() : "Error";
message = error.message ? error.message.toString() : "";
if ( name && message ) {
return name + ": " + message;
} else if ( name ) {
return name;
} else if ( message ) {
return message;
} else {
return "Error";
}
} else {
return errorString;
}
},
/**
* Makes a clone of an object using only Array or Object as base,
* and copies over the own enumerable properties.
*
* @param {Object} obj
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
if ( hasOwn.call( obj, key ) ) {
val = obj[ key ];
vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
}
}
return vals;
};
QUnit = {};
/**
* Config object: Maintain internal state
* Later exposed as QUnit.config
* `config` initialized at top of scope
*/
config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// by default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder: true,
// by default, modify document.title when suite is done
altertitle: true,
// by default, scroll to top of the page when suite is done
scrolltop: true,
// when enabled, all tests must call expect()
requireExpects: false,
// add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
id: "hidepassed",
label: "Hide passed tests",
tooltip: "Only show tests and assertions that fail. Stored as query-strings."
},
{
id: "noglobals",
label: "Check for Globals",
tooltip: "Enabling this will test if any test introduces new properties on the " +
"`window` object. Stored as query-strings."
},
{
id: "notrycatch",
label: "No try-catch",
tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
"exceptions in IE reasonable. Stored as query-strings."
}
],
// Set of all modules.
modules: [],
// The first unnamed module
currentModule: {
name: "",
tests: []
},
callbacks: {}
};
// Push a loose unnamed module to the modules collection
config.modules.push( config.currentModule );
// Initialize more QUnit.config and QUnit.urlParams
(function() {
var i, current,
location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {};
if ( params[ 0 ] ) {
for ( i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
if ( urlParams[ current[ 0 ] ] ) {
urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
} else {
urlParams[ current[ 0 ] ] = current[ 1 ];
}
}
}
if ( urlParams.filter === true ) {
delete urlParams.filter;
}
QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
config.testId = [];
if ( urlParams.testId ) {
// Ensure that urlParams.testId is an array
urlParams.testId = [].concat( urlParams.testId );
for ( i = 0; i < urlParams.testId.length; i++ ) {
config.testId.push( urlParams.testId[ i ] );
}
}
// Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === "file:";
}());
// Root QUnit object.
// `QUnit` initialized at top of scope
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: []
};
// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
if ( testEnvironment && testEnvironment.setup ) {
testEnvironment.beforeEach = testEnvironment.setup;
delete testEnvironment.setup;
}
if ( testEnvironment && testEnvironment.teardown ) {
testEnvironment.afterEach = testEnvironment.teardown;
delete testEnvironment.teardown;
}
config.modules.push( currentModule );
config.currentModule = currentModule;
},
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test( testName, expected, callback, true );
},
test: function( testName, expected, callback, async ) {
var test;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
test = new Test({
testName: testName,
expected: expected,
async: async,
callback: callback
});
test.queue();
},
skip: function( testName ) {
var test = new Test({
testName: testName,
skip: true
});
test.queue();
},
// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
start: function( count ) {
var globalStartAlreadyCalled = globalStartCalled;
if ( !config.current ) {
globalStartCalled = true;
if ( runStarted ) {
throw new Error( "Called start() outside of a test context while already started" );
} else if ( globalStartAlreadyCalled || count > 1 ) {
throw new Error( "Called start() outside of a test context too many times" );
} else if ( config.autostart ) {
throw new Error( "Called start() outside of a test context when " +
"QUnit.config.autostart was true" );
} else if ( !config.pageLoaded ) {
// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
config.autostart = true;
return;
}
} else {
// If a test is running, adjust its semaphore
config.current.semaphore -= count || 1;
// Don't start until equal number of stop-calls
if ( config.current.semaphore > 0 ) {
return;
}
// throw an Error if start is called more often than stop
if ( config.current.semaphore < 0 ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() while already started (test's semaphore was 0 already)",
sourceFromStacktrace( 2 )
);
return;
}
}
resumeProcessing();
},
// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
stop: function( count ) {
// If there isn't a test running, don't allow QUnit.stop() to be called
if ( !config.current ) {
throw new Error( "Called stop() outside of a test context" );
}
// If a test is running, adjust its semaphore
config.current.semaphore += count || 1;
pauseProcessing();
},
config: config,
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) === type;
},
objectType: function( obj ) {
if ( typeof obj === "undefined" ) {
return "undefined";
}
// Consider: typeof null === object
if ( obj === null ) {
return "null";
}
var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
type = match && match[ 1 ] || "";
switch ( type ) {
case "Number":
if ( isNaN( obj ) ) {
return "nan";
}
return "number";
case "String":
case "Boolean":
case "Array":
case "Date":
case "RegExp":
case "Function":
return type.toLowerCase();
}
if ( typeof obj === "object" ) {
return "object";
}
return undefined;
},
extend: extend,
load: function() {
config.pageLoaded = true;
// Initialize the configuration options
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: 0,
updateRate: 1000,
autostart: true,
filter: ""
}, true );
config.blocking = false;
if ( config.autostart ) {
resumeProcessing();
}
}
});
// Register logging callbacks
(function() {
var i, l, key,
callbacks = [ "begin", "done", "log", "testStart", "testDone",
"moduleStart", "moduleDone" ];
function registerLoggingCallback( key ) {
var loggingCallback = function( callback ) {
if ( QUnit.objectType( callback ) !== "function" ) {
throw new Error(
"QUnit logging methods require a callback function as their first parameters."
);
}
config.callbacks[ key ].push( callback );
};
// DEPRECATED: This will be removed on QUnit 2.0.0+
// Stores the registered functions allowing restoring
// at verifyLoggingCallbacks() if modified
loggingCallbacks[ key ] = loggingCallback;
return loggingCallback;
}
for ( i = 0, l = callbacks.length; i < l; i++ ) {
key = callbacks[ i ];
// Initialize key collection of logging callback
if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
config.callbacks[ key ] = [];
}
QUnit[ key ] = registerLoggingCallback( key );
}
})();
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
onErrorFnPrev = window.onerror;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window.onerror = function( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
}
// Treat return value as window.onerror itself does,
// Only do our handling if not suppressed.
if ( ret !== true ) {
if ( QUnit.config.current ) {
if ( QUnit.config.current.ignoreGlobalErrors ) {
return true;
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", extend(function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: true } ) );
}
return false;
}
return ret;
};
function done() {
var runtime, passed;
config.autorun = true;
// Log the last module results
if ( config.previousModule ) {
runLoggingCallbacks( "moduleDone", {
name: config.previousModule.name,
tests: config.previousModule.tests,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all,
runtime: now() - config.moduleStats.started
});
}
delete config.previousModule;
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
runLoggingCallbacks( "done", {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
});
}
// Doesn't support IE6 to IE9
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
offset = offset === undefined ? 4 : offset;
var stack, include, i;
if ( e.stacktrace ) {
// Opera 12.x
return e.stacktrace.split( "\n" )[ offset + 3 ];
} else if ( e.stack ) {
// Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
stack = e.stack.split( "\n" );
if ( /^error$/i.test( stack[ 0 ] ) ) {
stack.shift();
}
if ( fileName ) {
include = [];
for ( i = offset; i < stack.length; i++ ) {
if ( stack[ i ].indexOf( fileName ) !== -1 ) {
break;
}
include.push( stack[ i ] );
}
if ( include.length ) {
return include.join( "\n" );
}
}
return stack[ offset ];
} else if ( e.sourceURL ) {
// Safari < 6
// exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) {
return;
}
// for actual exceptions, this is useful
return e.sourceURL + ":" + e.line;
}
}
function sourceFromStacktrace( offset ) {
var e = new Error();
if ( !e.stack ) {
try {
throw e;
} catch ( err ) {
// This should already be true in most browsers
e = err;
}
}
return extractStacktrace( e, offset );
}
function synchronize( callback, last ) {
if ( QUnit.objectType( callback ) === "array" ) {
while ( callback.length ) {
synchronize( callback.shift() );
}
return;
}
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
process( last );
}
}
function process( last ) {
function next() {
process( last );
}
var start = now();
config.depth = ( config.depth || 0 ) + 1;
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 ||
( ( now() - start ) < config.updateRate ) ) {
if ( config.current ) {
// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}
config.queue.shift()();
} else {
setTimeout( next, 13 );
break;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}
function begin() {
var i, l,
modulesLog = [];
// If the test run hasn't officially begun yet
if ( !config.started ) {
// Record the time of the test run's beginning
config.started = now();
verifyLoggingCallbacks();
// Delete the loose unnamed module if unused.
if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
config.modules.shift();
}
// Avoid unnecessary information by not logging modules' test environments
for ( i = 0, l = config.modules.length; i < l; i++ ) {
modulesLog.push({
name: config.modules[ i ].name,
tests: config.modules[ i ].tests
});
}
// The test run is officially beginning now
runLoggingCallbacks( "begin", {
totalTests: Test.count,
modules: modulesLog
});
}
config.blocking = false;
process( true );
}
function resumeProcessing() {
runStarted = true;
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
if ( defined.setTimeout ) {
setTimeout(function() {
if ( config.current && config.current.semaphore > 0 ) {
return;
}
if ( config.timeout ) {
clearTimeout( config.timeout );
}
begin();
}, 13 );
} else {
begin();
}
}
function pauseProcessing() {
config.blocking = true;
if ( config.testTimeout && defined.setTimeout ) {
clearTimeout( config.timeout );
config.timeout = setTimeout(function() {
if ( config.current ) {
config.current.semaphore = 0;
QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
} else {
throw new Error( "Test timed out" );
}
resumeProcessing();
}, config.testTimeout );
}
}
function saveGlobal() {
config.pollution = [];
if ( config.noglobals ) {
for ( var key in window ) {
if ( hasOwn.call( window, key ) ) {
// in Opera sometimes DOM element ids show up here, ignore them
if ( /^qunit-test-output/.test( key ) ) {
continue;
}
config.pollution.push( key );
}
}
}
}
function checkPollution() {
var newGlobals,
deletedGlobals,
old = config.pollution;
saveGlobal();
newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
}
}
// returns a new Array with the elements that are in a but not in b
function diff( a, b ) {
var i, j,
result = a.slice();
for ( i = 0; i < result.length; i++ ) {
for ( j = 0; j < b.length; j++ ) {
if ( result[ i ] === b[ j ] ) {
result.splice( i, 1 );
i--;
break;
}
}
}
return result;
}
function extend( a, b, undefOnly ) {
for ( var prop in b ) {
if ( hasOwn.call( b, prop ) ) {
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
if ( !( prop === "constructor" && a === window ) ) {
if ( b[ prop ] === undefined ) {
delete a[ prop ];
} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
a[ prop ] = b[ prop ];
}
}
}
}
return a;
}
function runLoggingCallbacks( key, args ) {
var i, l, callbacks;
callbacks = config.callbacks[ key ];
for ( i = 0, l = callbacks.length; i < l; i++ ) {
callbacks[ i ]( args );
}
}
// DEPRECATED: This will be removed on 2.0.0+
// This function verifies if the loggingCallbacks were modified by the user
// If so, it will restore it, assign the given callback and print a console warning
function verifyLoggingCallbacks() {
var loggingCallback, userCallback;
for ( loggingCallback in loggingCallbacks ) {
if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
userCallback = QUnit[ loggingCallback ];
// Restore the callback function
QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
// Assign the deprecated given callback
QUnit[ loggingCallback ]( userCallback );
if ( window.console && window.console.warn ) {
window.console.warn(
"QUnit." + loggingCallback + " was replaced with a new value.\n" +
"Please, check out the documentation on how to apply logging callbacks.\n" +
"Reference: http://api.qunitjs.com/category/callbacks/"
);
}
}
}
}
// from jquery.js
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
}
function Test( settings ) {
var i, l;
++Test.count;
extend( this, settings );
this.assertions = [];
this.semaphore = 0;
this.usedAsync = false;
this.module = config.currentModule;
this.stack = sourceFromStacktrace( 3 );
// Register unique strings
for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
if ( this.module.tests[ i ].name === this.testName ) {
this.testName += " ";
}
}
this.testId = generateHash( this.module.name, this.testName );
this.module.tests.push({
name: this.testName,
testId: this.testId
});
if ( settings.skip ) {
// Skipped tests will fully ignore any sent callback
this.callback = function() {};
this.async = false;
this.expected = 0;
} else {
this.assert = new Assert( this );
}
}
Test.count = 0;
Test.prototype = {
before: function() {
if (
// Emit moduleStart when we're switching from one module to another
this.module !== config.previousModule ||
// They could be equal (both undefined) but if the previousModule property doesn't
// yet exist it means this is the first test in a suite that isn't wrapped in a
// module, in which case we'll just emit a moduleStart event for 'undefined'.
// Without this, reporters can get testStart before moduleStart which is a problem.
!hasOwn.call( config, "previousModule" )
) {
if ( hasOwn.call( config, "previousModule" ) ) {
runLoggingCallbacks( "moduleDone", {
name: config.previousModule.name,
tests: config.previousModule.tests,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all,
runtime: now() - config.moduleStats.started
});
}
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0, started: now() };
runLoggingCallbacks( "moduleStart", {
name: this.module.name,
tests: this.module.tests
});
}
config.current = this;
this.testEnvironment = extend( {}, this.module.testEnvironment );
delete this.testEnvironment.beforeEach;
delete this.testEnvironment.afterEach;
this.started = now();
runLoggingCallbacks( "testStart", {
name: this.testName,
module: this.module.name,
testId: this.testId
});
if ( !config.pollution ) {
saveGlobal();
}
},
run: function() {
var promise;
config.current = this;
if ( this.async ) {
QUnit.stop();
}
this.callbackStarted = now();
if ( config.notrycatch ) {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
return;
}
try {
promise = this.callback.call( this.testEnvironment, this.assert );
this.resolvePromise( promise );
} catch ( e ) {
this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
QUnit.start();
}
}
},
after: function() {
checkPollution();
},
queueHook: function( hook, hookName ) {
var promise,
test = this;
return function runHook() {
config.current = test;
if ( config.notrycatch ) {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
return;
}
try {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
} catch ( error ) {
test.pushFailure( hookName + " failed on " + test.testName + ": " +
( error.message || error ), extractStacktrace( error, 0 ) );
}
};
},
// Currently only used for module level hooks, can be used to add global level ones
hooks: function( handler ) {
var hooks = [];
// Hooks are ignored on skipped tests
if ( this.skip ) {
return hooks;
}
if ( this.module.testEnvironment &&
QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
}
return hooks;
},
finish: function() {
config.current = this;
if ( config.requireExpects && this.expected === null ) {
this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
"not called.", this.stack );
} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
this.pushFailure( "Expected " + this.expected + " assertions, but " +
this.assertions.length + " were run", this.stack );
} else if ( this.expected === null && !this.assertions.length ) {
this.pushFailure( "Expected at least one assertion, but none were run - call " +
"expect(0) to accept zero assertions.", this.stack );
}
var i,
bad = 0;
this.runtime = now() - this.started;
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
for ( i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[ i ].result ) {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
runLoggingCallbacks( "testDone", {
name: this.testName,
module: this.module.name,
skipped: !!this.skip,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length,
runtime: this.runtime,
// HTML Reporter use
assertions: this.assertions,
testId: this.testId,
// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
duration: this.runtime
});
// QUnit.reset() is deprecated and will be replaced for a new
// fixture reset function on QUnit 2.0/2.1.
// It's still called here for backwards compatibility handling
QUnit.reset();
config.current = undefined;
},
queue: function() {
var bad,
test = this;
if ( !this.valid() ) {
return;
}
function run() {
// each of these can by async
synchronize([
function() {
test.before();
},
test.hooks( "beforeEach" ),
function() {
test.run();
},
test.hooks( "afterEach" ).reverse(),
function() {
test.after();
},
function() {
test.finish();
}
]);
}
// `bad` initialized at top of scope
// defer when previous test run passed, if storage is available
bad = QUnit.config.reorder && defined.sessionStorage &&
+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
if ( bad ) {
run();
} else {
synchronize( run, true );
}
},
push: function( result, actual, expected, message ) {
var source,
details = {
module: this.module.name,
name: this.testName,
result: result,
message: message,
actual: actual,
expected: expected,
testId: this.testId,
runtime: now() - this.started
};
if ( !result ) {
source = sourceFromStacktrace();
if ( source ) {
details.source = source;
}
}
runLoggingCallbacks( "log", details );
this.assertions.push({
result: !!result,
message: message
});
},
pushFailure: function( message, source, actual ) {
if ( !this instanceof Test ) {
throw new Error( "pushFailure() assertion outside test context, was " +
sourceFromStacktrace( 2 ) );
}
var details = {
module: this.module.name,
name: this.testName,
result: false,
message: message || "error",
actual: actual || null,
testId: this.testId,
runtime: now() - this.started
};
if ( source ) {
details.source = source;
}
runLoggingCallbacks( "log", details );
this.assertions.push({
result: false,
message: message
});
},
resolvePromise: function( promise, phase ) {
var then, message,
test = this;
if ( promise != null ) {
then = promise.then;
if ( QUnit.objectType( then ) === "function" ) {
QUnit.stop();
then.call(
promise,
QUnit.start,
function( error ) {
message = "Promise rejected " +
( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
" " + test.testName + ": " + ( error.message || error );
test.pushFailure( message, extractStacktrace( error, 0 ) );
// else next test will carry the responsibility
saveGlobal();
// Unblock
QUnit.start();
}
);
}
}
},
valid: function() {
var include,
filter = config.filter,
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
// Internally-generated tests are always valid
if ( this.callback && this.callback.validTest ) {
return true;
}
if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.

Auch abrufbar als: Unified diff