Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision ac01c25a

Von Moritz Bunkus vor fast 10 Jahren hinzugefügt

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

JavaScript-Test-Framework auf Basis von QUnit

Unterschiede anzeigen:

SL/Controller/JSTests.pm
1
package SL::Controller::JSTests;
2

  
3
use strict;
4

  
5
use parent qw(SL::Controller::Base);
6

  
7
use File::Find ();
8
use File::Spec ();
9

  
10
use SL::System::Process;
11

  
12
use Rose::Object::MakeMethods::Generic
13
(
14
  'scalar --get_set_init' => [ qw(all_scripts scripts_to_run) ],
15
);
16

  
17
#
18
# actions
19
#
20

  
21
sub action_run {
22
  my ($self) = @_;
23

  
24
  $::request->layout->use_stylesheet("css/qunit.css");
25
  $self->render('js_tests/run', title => $::locale->text('Run JavaScript unit tests'));
26
}
27

  
28
#
29
# helpers
30
#
31

  
32
sub init_all_scripts {
33
  my ($self) = @_;
34

  
35
  my $exe_dir = SL::System::Process->exe_dir;
36

  
37
  my @scripts;
38
  my $wanted = sub {
39
    return if ( ! -f $File::Find::name ) || ($File::Find::name !~ m{\.js$});
40
    push @scripts, File::Spec->abs2rel($File::Find::name, $exe_dir);
41
  };
42

  
43
  File::Find::find($wanted, $exe_dir . '/js/t');
44

  
45
  return \@scripts;
46
}
47

  
48
sub init_scripts_to_run {
49
  my ($self) = @_;
50
  my $filter = $::form->{file_filter} || '.';
51
  return [ grep { m{$filter} } @{ $self->all_scripts } ];
52
}
53

  
54
1;
css/qunit-1.17.1.css
1
/*!
2
 * QUnit 1.17.1
3
 * http://qunitjs.com/
4
 *
5
 * Copyright jQuery Foundation and other contributors
6
 * Released under the MIT license
7
 * http://jquery.org/license
8
 *
9
 * Date: 2015-01-20T19:39Z
10
 */
11

  
12
/** Font Family and Sizes */
13

  
14
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
15
	font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
16
}
17

  
18
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
19
#qunit-tests { font-size: smaller; }
20

  
21

  
22
/** Resets */
23

  
24
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
25
	margin: 0;
26
	padding: 0;
27
}
28

  
29

  
30
/** Header */
31

  
32
#qunit-header {
33
	padding: 0.5em 0 0.5em 1em;
34

  
35
	color: #8699A4;
36
	background-color: #0D3349;
37

  
38
	font-size: 1.5em;
39
	line-height: 1em;
40
	font-weight: 400;
41

  
42
	border-radius: 5px 5px 0 0;
43
}
44

  
45
#qunit-header a {
46
	text-decoration: none;
47
	color: #C2CCD1;
48
}
49

  
50
#qunit-header a:hover,
51
#qunit-header a:focus {
52
	color: #FFF;
53
}
54

  
55
#qunit-testrunner-toolbar label {
56
	display: inline-block;
57
	padding: 0 0.5em 0 0.1em;
58
}
59

  
60
#qunit-banner {
61
	height: 5px;
62
}
63

  
64
#qunit-testrunner-toolbar {
65
	padding: 0.5em 1em 0.5em 1em;
66
	color: #5E740B;
67
	background-color: #EEE;
68
	overflow: hidden;
69
}
70

  
71
#qunit-userAgent {
72
	padding: 0.5em 1em 0.5em 1em;
73
	background-color: #2B81AF;
74
	color: #FFF;
75
	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
76
}
77

  
78
#qunit-modulefilter-container {
79
	float: right;
80
	padding: 0.2em;
81
}
82

  
83
.qunit-url-config {
84
	display: inline-block;
85
	padding: 0.1em;
86
}
87

  
88
.qunit-filter {
89
	display: block;
90
	float: right;
91
	margin-left: 1em;
92
}
93

  
94
/** Tests: Pass/Fail */
95

  
96
#qunit-tests {
97
	list-style-position: inside;
98
}
99

  
100
#qunit-tests li {
101
	padding: 0.4em 1em 0.4em 1em;
102
	border-bottom: 1px solid #FFF;
103
	list-style-position: inside;
104
}
105

  
106
#qunit-tests > li {
107
	display: none;
108
}
109

  
110
#qunit-tests li.running,
111
#qunit-tests li.pass,
112
#qunit-tests li.fail,
113
#qunit-tests li.skipped {
114
	display: list-item;
115
}
116

  
117
#qunit-tests.hidepass li.running,
118
#qunit-tests.hidepass li.pass {
119
	display: none;
120
}
121

  
122
#qunit-tests li strong {
123
	cursor: pointer;
124
}
125

  
126
#qunit-tests li.skipped strong {
127
	cursor: default;
128
}
129

  
130
#qunit-tests li a {
131
	padding: 0.5em;
132
	color: #C2CCD1;
133
	text-decoration: none;
134
}
135
#qunit-tests li a:hover,
136
#qunit-tests li a:focus {
137
	color: #000;
138
}
139

  
140
#qunit-tests li .runtime {
141
	float: right;
142
	font-size: smaller;
143
}
144

  
145
.qunit-assert-list {
146
	margin-top: 0.5em;
147
	padding: 0.5em;
148

  
149
	background-color: #FFF;
150

  
151
	border-radius: 5px;
152
}
153

  
154
.qunit-collapsed {
155
	display: none;
156
}
157

  
158
#qunit-tests table {
159
	border-collapse: collapse;
160
	margin-top: 0.2em;
161
}
162

  
163
#qunit-tests th {
164
	text-align: right;
165
	vertical-align: top;
166
	padding: 0 0.5em 0 0;
167
}
168

  
169
#qunit-tests td {
170
	vertical-align: top;
171
}
172

  
173
#qunit-tests pre {
174
	margin: 0;
175
	white-space: pre-wrap;
176
	word-wrap: break-word;
177
}
178

  
179
#qunit-tests del {
180
	background-color: #E0F2BE;
181
	color: #374E0C;
182
	text-decoration: none;
183
}
184

  
185
#qunit-tests ins {
186
	background-color: #FFCACA;
187
	color: #500;
188
	text-decoration: none;
189
}
190

  
191
/*** Test Counts */
192

  
193
#qunit-tests b.counts                       { color: #000; }
194
#qunit-tests b.passed                       { color: #5E740B; }
195
#qunit-tests b.failed                       { color: #710909; }
196

  
197
#qunit-tests li li {
198
	padding: 5px;
199
	background-color: #FFF;
200
	border-bottom: none;
201
	list-style-position: inside;
202
}
203

  
204
/*** Passing Styles */
205

  
206
#qunit-tests li li.pass {
207
	color: #3C510C;
208
	background-color: #FFF;
209
	border-left: 10px solid #C6E746;
210
}
211

  
212
#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
213
#qunit-tests .pass .test-name               { color: #366097; }
214

  
215
#qunit-tests .pass .test-actual,
216
#qunit-tests .pass .test-expected           { color: #999; }
217

  
218
#qunit-banner.qunit-pass                    { background-color: #C6E746; }
219

  
220
/*** Failing Styles */
221

  
222
#qunit-tests li li.fail {
223
	color: #710909;
224
	background-color: #FFF;
225
	border-left: 10px solid #EE5757;
226
	white-space: pre;
227
}
228

  
229
#qunit-tests > li:last-child {
230
	border-radius: 0 0 5px 5px;
231
}
232

  
233
#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
234
#qunit-tests .fail .test-name,
235
#qunit-tests .fail .module-name             { color: #000; }
236

  
237
#qunit-tests .fail .test-actual             { color: #EE5757; }
238
#qunit-tests .fail .test-expected           { color: #008000; }
239

  
240
#qunit-banner.qunit-fail                    { background-color: #EE5757; }
241

  
242
/*** Skipped tests */
243

  
244
#qunit-tests .skipped {
245
	background-color: #EBECE9;
246
}
247

  
248
#qunit-tests .qunit-skipped-label {
249
	background-color: #F4FF77;
250
	display: inline-block;
251
	font-style: normal;
252
	color: #366097;
253
	line-height: 1.8em;
254
	padding: 0 0.5em;
255
	margin: -0.4em 0.4em -0.4em 0;
256
}
257

  
258
/** Result */
259

  
260
#qunit-testresult {
261
	padding: 0.5em 1em 0.5em 1em;
262

  
263
	color: #2B81AF;
264
	background-color: #D2E0E6;
265

  
266
	border-bottom: 1px solid #FFF;
267
}
268
#qunit-testresult .module-name {
269
	font-weight: 700;
270
}
271

  
272
/** Fixture */
273

  
274
#qunit-fixture {
275
	position: absolute;
276
	top: -10000px;
277
	left: -10000px;
278
	width: 1000px;
279
	height: 1000px;
280
}
css/qunit.css
1
qunit-1.17.1.css
js/qunit-1.17.1.js
1
/*!
2
 * QUnit 1.17.1
3
 * http://qunitjs.com/
4
 *
5
 * Copyright jQuery Foundation and other contributors
6
 * Released under the MIT license
7
 * http://jquery.org/license
8
 *
9
 * Date: 2015-01-20T19:39Z
10
 */
11

  
12
(function( window ) {
13

  
14
var QUnit,
15
	config,
16
	onErrorFnPrev,
17
	loggingCallbacks = {},
18
	fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
19
	toString = Object.prototype.toString,
20
	hasOwn = Object.prototype.hasOwnProperty,
21
	// Keep a local reference to Date (GH-283)
22
	Date = window.Date,
23
	now = Date.now || function() {
24
		return new Date().getTime();
25
	},
26
	globalStartCalled = false,
27
	runStarted = false,
28
	setTimeout = window.setTimeout,
29
	clearTimeout = window.clearTimeout,
30
	defined = {
31
		document: window.document !== undefined,
32
		setTimeout: window.setTimeout !== undefined,
33
		sessionStorage: (function() {
34
			var x = "qunit-test-string";
35
			try {
36
				sessionStorage.setItem( x, x );
37
				sessionStorage.removeItem( x );
38
				return true;
39
			} catch ( e ) {
40
				return false;
41
			}
42
		}())
43
	},
44
	/**
45
	 * Provides a normalized error string, correcting an issue
46
	 * with IE 7 (and prior) where Error.prototype.toString is
47
	 * not properly implemented
48
	 *
49
	 * Based on http://es5.github.com/#x15.11.4.4
50
	 *
51
	 * @param {String|Error} error
52
	 * @return {String} error message
53
	 */
54
	errorString = function( error ) {
55
		var name, message,
56
			errorString = error.toString();
57
		if ( errorString.substring( 0, 7 ) === "[object" ) {
58
			name = error.name ? error.name.toString() : "Error";
59
			message = error.message ? error.message.toString() : "";
60
			if ( name && message ) {
61
				return name + ": " + message;
62
			} else if ( name ) {
63
				return name;
64
			} else if ( message ) {
65
				return message;
66
			} else {
67
				return "Error";
68
			}
69
		} else {
70
			return errorString;
71
		}
72
	},
73
	/**
74
	 * Makes a clone of an object using only Array or Object as base,
75
	 * and copies over the own enumerable properties.
76
	 *
77
	 * @param {Object} obj
78
	 * @return {Object} New object with only the own properties (recursively).
79
	 */
80
	objectValues = function( obj ) {
81
		var key, val,
82
			vals = QUnit.is( "array", obj ) ? [] : {};
83
		for ( key in obj ) {
84
			if ( hasOwn.call( obj, key ) ) {
85
				val = obj[ key ];
86
				vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
87
			}
88
		}
89
		return vals;
90
	};
91

  
92
QUnit = {};
93

  
94
/**
95
 * Config object: Maintain internal state
96
 * Later exposed as QUnit.config
97
 * `config` initialized at top of scope
98
 */
99
config = {
100
	// The queue of tests to run
101
	queue: [],
102

  
103
	// block until document ready
104
	blocking: true,
105

  
106
	// by default, run previously failed tests first
107
	// very useful in combination with "Hide passed tests" checked
108
	reorder: true,
109

  
110
	// by default, modify document.title when suite is done
111
	altertitle: true,
112

  
113
	// by default, scroll to top of the page when suite is done
114
	scrolltop: true,
115

  
116
	// when enabled, all tests must call expect()
117
	requireExpects: false,
118

  
119
	// add checkboxes that are persisted in the query-string
120
	// when enabled, the id is set to `true` as a `QUnit.config` property
121
	urlConfig: [
122
		{
123
			id: "hidepassed",
124
			label: "Hide passed tests",
125
			tooltip: "Only show tests and assertions that fail. Stored as query-strings."
126
		},
127
		{
128
			id: "noglobals",
129
			label: "Check for Globals",
130
			tooltip: "Enabling this will test if any test introduces new properties on the " +
131
				"`window` object. Stored as query-strings."
132
		},
133
		{
134
			id: "notrycatch",
135
			label: "No try-catch",
136
			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
137
				"exceptions in IE reasonable. Stored as query-strings."
138
		}
139
	],
140

  
141
	// Set of all modules.
142
	modules: [],
143

  
144
	// The first unnamed module
145
	currentModule: {
146
		name: "",
147
		tests: []
148
	},
149

  
150
	callbacks: {}
151
};
152

  
153
// Push a loose unnamed module to the modules collection
154
config.modules.push( config.currentModule );
155

  
156
// Initialize more QUnit.config and QUnit.urlParams
157
(function() {
158
	var i, current,
159
		location = window.location || { search: "", protocol: "file:" },
160
		params = location.search.slice( 1 ).split( "&" ),
161
		length = params.length,
162
		urlParams = {};
163

  
164
	if ( params[ 0 ] ) {
165
		for ( i = 0; i < length; i++ ) {
166
			current = params[ i ].split( "=" );
167
			current[ 0 ] = decodeURIComponent( current[ 0 ] );
168

  
169
			// allow just a key to turn on a flag, e.g., test.html?noglobals
170
			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
171
			if ( urlParams[ current[ 0 ] ] ) {
172
				urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
173
			} else {
174
				urlParams[ current[ 0 ] ] = current[ 1 ];
175
			}
176
		}
177
	}
178

  
179
	if ( urlParams.filter === true ) {
180
		delete urlParams.filter;
181
	}
182

  
183
	QUnit.urlParams = urlParams;
184

  
185
	// String search anywhere in moduleName+testName
186
	config.filter = urlParams.filter;
187

  
188
	config.testId = [];
189
	if ( urlParams.testId ) {
190

  
191
		// Ensure that urlParams.testId is an array
192
		urlParams.testId = [].concat( urlParams.testId );
193
		for ( i = 0; i < urlParams.testId.length; i++ ) {
194
			config.testId.push( urlParams.testId[ i ] );
195
		}
196
	}
197

  
198
	// Figure out if we're running the tests from a server or not
199
	QUnit.isLocal = location.protocol === "file:";
200
}());
201

  
202
// Root QUnit object.
203
// `QUnit` initialized at top of scope
204
extend( QUnit, {
205

  
206
	// call on start of module test to prepend name to all tests
207
	module: function( name, testEnvironment ) {
208
		var currentModule = {
209
			name: name,
210
			testEnvironment: testEnvironment,
211
			tests: []
212
		};
213

  
214
		// DEPRECATED: handles setup/teardown functions,
215
		// beforeEach and afterEach should be used instead
216
		if ( testEnvironment && testEnvironment.setup ) {
217
			testEnvironment.beforeEach = testEnvironment.setup;
218
			delete testEnvironment.setup;
219
		}
220
		if ( testEnvironment && testEnvironment.teardown ) {
221
			testEnvironment.afterEach = testEnvironment.teardown;
222
			delete testEnvironment.teardown;
223
		}
224

  
225
		config.modules.push( currentModule );
226
		config.currentModule = currentModule;
227
	},
228

  
229
	// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
230
	asyncTest: function( testName, expected, callback ) {
231
		if ( arguments.length === 2 ) {
232
			callback = expected;
233
			expected = null;
234
		}
235

  
236
		QUnit.test( testName, expected, callback, true );
237
	},
238

  
239
	test: function( testName, expected, callback, async ) {
240
		var test;
241

  
242
		if ( arguments.length === 2 ) {
243
			callback = expected;
244
			expected = null;
245
		}
246

  
247
		test = new Test({
248
			testName: testName,
249
			expected: expected,
250
			async: async,
251
			callback: callback
252
		});
253

  
254
		test.queue();
255
	},
256

  
257
	skip: function( testName ) {
258
		var test = new Test({
259
			testName: testName,
260
			skip: true
261
		});
262

  
263
		test.queue();
264
	},
265

  
266
	// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
267
	// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
268
	start: function( count ) {
269
		var globalStartAlreadyCalled = globalStartCalled;
270

  
271
		if ( !config.current ) {
272
			globalStartCalled = true;
273

  
274
			if ( runStarted ) {
275
				throw new Error( "Called start() outside of a test context while already started" );
276
			} else if ( globalStartAlreadyCalled || count > 1 ) {
277
				throw new Error( "Called start() outside of a test context too many times" );
278
			} else if ( config.autostart ) {
279
				throw new Error( "Called start() outside of a test context when " +
280
					"QUnit.config.autostart was true" );
281
			} else if ( !config.pageLoaded ) {
282

  
283
				// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
284
				config.autostart = true;
285
				return;
286
			}
287
		} else {
288

  
289
			// If a test is running, adjust its semaphore
290
			config.current.semaphore -= count || 1;
291

  
292
			// Don't start until equal number of stop-calls
293
			if ( config.current.semaphore > 0 ) {
294
				return;
295
			}
296

  
297
			// throw an Error if start is called more often than stop
298
			if ( config.current.semaphore < 0 ) {
299
				config.current.semaphore = 0;
300

  
301
				QUnit.pushFailure(
302
					"Called start() while already started (test's semaphore was 0 already)",
303
					sourceFromStacktrace( 2 )
304
				);
305
				return;
306
			}
307
		}
308

  
309
		resumeProcessing();
310
	},
311

  
312
	// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
313
	stop: function( count ) {
314

  
315
		// If there isn't a test running, don't allow QUnit.stop() to be called
316
		if ( !config.current ) {
317
			throw new Error( "Called stop() outside of a test context" );
318
		}
319

  
320
		// If a test is running, adjust its semaphore
321
		config.current.semaphore += count || 1;
322

  
323
		pauseProcessing();
324
	},
325

  
326
	config: config,
327

  
328
	// Safe object type checking
329
	is: function( type, obj ) {
330
		return QUnit.objectType( obj ) === type;
331
	},
332

  
333
	objectType: function( obj ) {
334
		if ( typeof obj === "undefined" ) {
335
			return "undefined";
336
		}
337

  
338
		// Consider: typeof null === object
339
		if ( obj === null ) {
340
			return "null";
341
		}
342

  
343
		var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
344
			type = match && match[ 1 ] || "";
345

  
346
		switch ( type ) {
347
			case "Number":
348
				if ( isNaN( obj ) ) {
349
					return "nan";
350
				}
351
				return "number";
352
			case "String":
353
			case "Boolean":
354
			case "Array":
355
			case "Date":
356
			case "RegExp":
357
			case "Function":
358
				return type.toLowerCase();
359
		}
360
		if ( typeof obj === "object" ) {
361
			return "object";
362
		}
363
		return undefined;
364
	},
365

  
366
	extend: extend,
367

  
368
	load: function() {
369
		config.pageLoaded = true;
370

  
371
		// Initialize the configuration options
372
		extend( config, {
373
			stats: { all: 0, bad: 0 },
374
			moduleStats: { all: 0, bad: 0 },
375
			started: 0,
376
			updateRate: 1000,
377
			autostart: true,
378
			filter: ""
379
		}, true );
380

  
381
		config.blocking = false;
382

  
383
		if ( config.autostart ) {
384
			resumeProcessing();
385
		}
386
	}
387
});
388

  
389
// Register logging callbacks
390
(function() {
391
	var i, l, key,
392
		callbacks = [ "begin", "done", "log", "testStart", "testDone",
393
			"moduleStart", "moduleDone" ];
394

  
395
	function registerLoggingCallback( key ) {
396
		var loggingCallback = function( callback ) {
397
			if ( QUnit.objectType( callback ) !== "function" ) {
398
				throw new Error(
399
					"QUnit logging methods require a callback function as their first parameters."
400
				);
401
			}
402

  
403
			config.callbacks[ key ].push( callback );
404
		};
405

  
406
		// DEPRECATED: This will be removed on QUnit 2.0.0+
407
		// Stores the registered functions allowing restoring
408
		// at verifyLoggingCallbacks() if modified
409
		loggingCallbacks[ key ] = loggingCallback;
410

  
411
		return loggingCallback;
412
	}
413

  
414
	for ( i = 0, l = callbacks.length; i < l; i++ ) {
415
		key = callbacks[ i ];
416

  
417
		// Initialize key collection of logging callback
418
		if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
419
			config.callbacks[ key ] = [];
420
		}
421

  
422
		QUnit[ key ] = registerLoggingCallback( key );
423
	}
424
})();
425

  
426
// `onErrorFnPrev` initialized at top of scope
427
// Preserve other handlers
428
onErrorFnPrev = window.onerror;
429

  
430
// Cover uncaught exceptions
431
// Returning true will suppress the default browser handler,
432
// returning false will let it run.
433
window.onerror = function( error, filePath, linerNr ) {
434
	var ret = false;
435
	if ( onErrorFnPrev ) {
436
		ret = onErrorFnPrev( error, filePath, linerNr );
437
	}
438

  
439
	// Treat return value as window.onerror itself does,
440
	// Only do our handling if not suppressed.
441
	if ( ret !== true ) {
442
		if ( QUnit.config.current ) {
443
			if ( QUnit.config.current.ignoreGlobalErrors ) {
444
				return true;
445
			}
446
			QUnit.pushFailure( error, filePath + ":" + linerNr );
447
		} else {
448
			QUnit.test( "global failure", extend(function() {
449
				QUnit.pushFailure( error, filePath + ":" + linerNr );
450
			}, { validTest: true } ) );
451
		}
452
		return false;
453
	}
454

  
455
	return ret;
456
};
457

  
458
function done() {
459
	var runtime, passed;
460

  
461
	config.autorun = true;
462

  
463
	// Log the last module results
464
	if ( config.previousModule ) {
465
		runLoggingCallbacks( "moduleDone", {
466
			name: config.previousModule.name,
467
			tests: config.previousModule.tests,
468
			failed: config.moduleStats.bad,
469
			passed: config.moduleStats.all - config.moduleStats.bad,
470
			total: config.moduleStats.all,
471
			runtime: now() - config.moduleStats.started
472
		});
473
	}
474
	delete config.previousModule;
475

  
476
	runtime = now() - config.started;
477
	passed = config.stats.all - config.stats.bad;
478

  
479
	runLoggingCallbacks( "done", {
480
		failed: config.stats.bad,
481
		passed: passed,
482
		total: config.stats.all,
483
		runtime: runtime
484
	});
485
}
486

  
487
// Doesn't support IE6 to IE9
488
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
489
function extractStacktrace( e, offset ) {
490
	offset = offset === undefined ? 4 : offset;
491

  
492
	var stack, include, i;
493

  
494
	if ( e.stacktrace ) {
495

  
496
		// Opera 12.x
497
		return e.stacktrace.split( "\n" )[ offset + 3 ];
498
	} else if ( e.stack ) {
499

  
500
		// Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
501
		stack = e.stack.split( "\n" );
502
		if ( /^error$/i.test( stack[ 0 ] ) ) {
503
			stack.shift();
504
		}
505
		if ( fileName ) {
506
			include = [];
507
			for ( i = offset; i < stack.length; i++ ) {
508
				if ( stack[ i ].indexOf( fileName ) !== -1 ) {
509
					break;
510
				}
511
				include.push( stack[ i ] );
512
			}
513
			if ( include.length ) {
514
				return include.join( "\n" );
515
			}
516
		}
517
		return stack[ offset ];
518
	} else if ( e.sourceURL ) {
519

  
520
		// Safari < 6
521
		// exclude useless self-reference for generated Error objects
522
		if ( /qunit.js$/.test( e.sourceURL ) ) {
523
			return;
524
		}
525

  
526
		// for actual exceptions, this is useful
527
		return e.sourceURL + ":" + e.line;
528
	}
529
}
530

  
531
function sourceFromStacktrace( offset ) {
532
	var e = new Error();
533
	if ( !e.stack ) {
534
		try {
535
			throw e;
536
		} catch ( err ) {
537
			// This should already be true in most browsers
538
			e = err;
539
		}
540
	}
541
	return extractStacktrace( e, offset );
542
}
543

  
544
function synchronize( callback, last ) {
545
	if ( QUnit.objectType( callback ) === "array" ) {
546
		while ( callback.length ) {
547
			synchronize( callback.shift() );
548
		}
549
		return;
550
	}
551
	config.queue.push( callback );
552

  
553
	if ( config.autorun && !config.blocking ) {
554
		process( last );
555
	}
556
}
557

  
558
function process( last ) {
559
	function next() {
560
		process( last );
561
	}
562
	var start = now();
563
	config.depth = ( config.depth || 0 ) + 1;
564

  
565
	while ( config.queue.length && !config.blocking ) {
566
		if ( !defined.setTimeout || config.updateRate <= 0 ||
567
				( ( now() - start ) < config.updateRate ) ) {
568
			if ( config.current ) {
569

  
570
				// Reset async tracking for each phase of the Test lifecycle
571
				config.current.usedAsync = false;
572
			}
573
			config.queue.shift()();
574
		} else {
575
			setTimeout( next, 13 );
576
			break;
577
		}
578
	}
579
	config.depth--;
580
	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
581
		done();
582
	}
583
}
584

  
585
function begin() {
586
	var i, l,
587
		modulesLog = [];
588

  
589
	// If the test run hasn't officially begun yet
590
	if ( !config.started ) {
591

  
592
		// Record the time of the test run's beginning
593
		config.started = now();
594

  
595
		verifyLoggingCallbacks();
596

  
597
		// Delete the loose unnamed module if unused.
598
		if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
599
			config.modules.shift();
600
		}
601

  
602
		// Avoid unnecessary information by not logging modules' test environments
603
		for ( i = 0, l = config.modules.length; i < l; i++ ) {
604
			modulesLog.push({
605
				name: config.modules[ i ].name,
606
				tests: config.modules[ i ].tests
607
			});
608
		}
609

  
610
		// The test run is officially beginning now
611
		runLoggingCallbacks( "begin", {
612
			totalTests: Test.count,
613
			modules: modulesLog
614
		});
615
	}
616

  
617
	config.blocking = false;
618
	process( true );
619
}
620

  
621
function resumeProcessing() {
622
	runStarted = true;
623

  
624
	// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
625
	if ( defined.setTimeout ) {
626
		setTimeout(function() {
627
			if ( config.current && config.current.semaphore > 0 ) {
628
				return;
629
			}
630
			if ( config.timeout ) {
631
				clearTimeout( config.timeout );
632
			}
633

  
634
			begin();
635
		}, 13 );
636
	} else {
637
		begin();
638
	}
639
}
640

  
641
function pauseProcessing() {
642
	config.blocking = true;
643

  
644
	if ( config.testTimeout && defined.setTimeout ) {
645
		clearTimeout( config.timeout );
646
		config.timeout = setTimeout(function() {
647
			if ( config.current ) {
648
				config.current.semaphore = 0;
649
				QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
650
			} else {
651
				throw new Error( "Test timed out" );
652
			}
653
			resumeProcessing();
654
		}, config.testTimeout );
655
	}
656
}
657

  
658
function saveGlobal() {
659
	config.pollution = [];
660

  
661
	if ( config.noglobals ) {
662
		for ( var key in window ) {
663
			if ( hasOwn.call( window, key ) ) {
664
				// in Opera sometimes DOM element ids show up here, ignore them
665
				if ( /^qunit-test-output/.test( key ) ) {
666
					continue;
667
				}
668
				config.pollution.push( key );
669
			}
670
		}
671
	}
672
}
673

  
674
function checkPollution() {
675
	var newGlobals,
676
		deletedGlobals,
677
		old = config.pollution;
678

  
679
	saveGlobal();
680

  
681
	newGlobals = diff( config.pollution, old );
682
	if ( newGlobals.length > 0 ) {
683
		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
684
	}
685

  
686
	deletedGlobals = diff( old, config.pollution );
687
	if ( deletedGlobals.length > 0 ) {
688
		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
689
	}
690
}
691

  
692
// returns a new Array with the elements that are in a but not in b
693
function diff( a, b ) {
694
	var i, j,
695
		result = a.slice();
696

  
697
	for ( i = 0; i < result.length; i++ ) {
698
		for ( j = 0; j < b.length; j++ ) {
699
			if ( result[ i ] === b[ j ] ) {
700
				result.splice( i, 1 );
701
				i--;
702
				break;
703
			}
704
		}
705
	}
706
	return result;
707
}
708

  
709
function extend( a, b, undefOnly ) {
710
	for ( var prop in b ) {
711
		if ( hasOwn.call( b, prop ) ) {
712

  
713
			// Avoid "Member not found" error in IE8 caused by messing with window.constructor
714
			if ( !( prop === "constructor" && a === window ) ) {
715
				if ( b[ prop ] === undefined ) {
716
					delete a[ prop ];
717
				} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
718
					a[ prop ] = b[ prop ];
719
				}
720
			}
721
		}
722
	}
723

  
724
	return a;
725
}
726

  
727
function runLoggingCallbacks( key, args ) {
728
	var i, l, callbacks;
729

  
730
	callbacks = config.callbacks[ key ];
731
	for ( i = 0, l = callbacks.length; i < l; i++ ) {
732
		callbacks[ i ]( args );
733
	}
734
}
735

  
736
// DEPRECATED: This will be removed on 2.0.0+
737
// This function verifies if the loggingCallbacks were modified by the user
738
// If so, it will restore it, assign the given callback and print a console warning
739
function verifyLoggingCallbacks() {
740
	var loggingCallback, userCallback;
741

  
742
	for ( loggingCallback in loggingCallbacks ) {
743
		if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
744

  
745
			userCallback = QUnit[ loggingCallback ];
746

  
747
			// Restore the callback function
748
			QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
749

  
750
			// Assign the deprecated given callback
751
			QUnit[ loggingCallback ]( userCallback );
752

  
753
			if ( window.console && window.console.warn ) {
754
				window.console.warn(
755
					"QUnit." + loggingCallback + " was replaced with a new value.\n" +
756
					"Please, check out the documentation on how to apply logging callbacks.\n" +
757
					"Reference: http://api.qunitjs.com/category/callbacks/"
758
				);
759
			}
760
		}
761
	}
762
}
763

  
764
// from jquery.js
765
function inArray( elem, array ) {
766
	if ( array.indexOf ) {
767
		return array.indexOf( elem );
768
	}
769

  
770
	for ( var i = 0, length = array.length; i < length; i++ ) {
771
		if ( array[ i ] === elem ) {
772
			return i;
773
		}
774
	}
775

  
776
	return -1;
777
}
778

  
779
function Test( settings ) {
780
	var i, l;
781

  
782
	++Test.count;
783

  
784
	extend( this, settings );
785
	this.assertions = [];
786
	this.semaphore = 0;
787
	this.usedAsync = false;
788
	this.module = config.currentModule;
789
	this.stack = sourceFromStacktrace( 3 );
790

  
791
	// Register unique strings
792
	for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
793
		if ( this.module.tests[ i ].name === this.testName ) {
794
			this.testName += " ";
795
		}
796
	}
797

  
798
	this.testId = generateHash( this.module.name, this.testName );
799

  
800
	this.module.tests.push({
801
		name: this.testName,
802
		testId: this.testId
803
	});
804

  
805
	if ( settings.skip ) {
806

  
807
		// Skipped tests will fully ignore any sent callback
808
		this.callback = function() {};
809
		this.async = false;
810
		this.expected = 0;
811
	} else {
812
		this.assert = new Assert( this );
813
	}
814
}
815

  
816
Test.count = 0;
817

  
818
Test.prototype = {
819
	before: function() {
820
		if (
821

  
822
			// Emit moduleStart when we're switching from one module to another
823
			this.module !== config.previousModule ||
824

  
825
				// They could be equal (both undefined) but if the previousModule property doesn't
826
				// yet exist it means this is the first test in a suite that isn't wrapped in a
827
				// module, in which case we'll just emit a moduleStart event for 'undefined'.
828
				// Without this, reporters can get testStart before moduleStart  which is a problem.
829
				!hasOwn.call( config, "previousModule" )
830
		) {
831
			if ( hasOwn.call( config, "previousModule" ) ) {
832
				runLoggingCallbacks( "moduleDone", {
833
					name: config.previousModule.name,
834
					tests: config.previousModule.tests,
835
					failed: config.moduleStats.bad,
836
					passed: config.moduleStats.all - config.moduleStats.bad,
837
					total: config.moduleStats.all,
838
					runtime: now() - config.moduleStats.started
839
				});
840
			}
841
			config.previousModule = this.module;
842
			config.moduleStats = { all: 0, bad: 0, started: now() };
843
			runLoggingCallbacks( "moduleStart", {
844
				name: this.module.name,
845
				tests: this.module.tests
846
			});
847
		}
848

  
849
		config.current = this;
850

  
851
		this.testEnvironment = extend( {}, this.module.testEnvironment );
852
		delete this.testEnvironment.beforeEach;
853
		delete this.testEnvironment.afterEach;
854

  
855
		this.started = now();
856
		runLoggingCallbacks( "testStart", {
857
			name: this.testName,
858
			module: this.module.name,
859
			testId: this.testId
860
		});
861

  
862
		if ( !config.pollution ) {
863
			saveGlobal();
864
		}
865
	},
866

  
867
	run: function() {
868
		var promise;
869

  
870
		config.current = this;
871

  
872
		if ( this.async ) {
873
			QUnit.stop();
874
		}
875

  
876
		this.callbackStarted = now();
877

  
878
		if ( config.notrycatch ) {
879
			promise = this.callback.call( this.testEnvironment, this.assert );
880
			this.resolvePromise( promise );
881
			return;
882
		}
883

  
884
		try {
885
			promise = this.callback.call( this.testEnvironment, this.assert );
886
			this.resolvePromise( promise );
887
		} catch ( e ) {
888
			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
889
				this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
890

  
891
			// else next test will carry the responsibility
892
			saveGlobal();
893

  
894
			// Restart the tests if they're blocking
895
			if ( config.blocking ) {
896
				QUnit.start();
897
			}
898
		}
899
	},
900

  
901
	after: function() {
902
		checkPollution();
903
	},
904

  
905
	queueHook: function( hook, hookName ) {
906
		var promise,
907
			test = this;
908
		return function runHook() {
909
			config.current = test;
910
			if ( config.notrycatch ) {
911
				promise = hook.call( test.testEnvironment, test.assert );
912
				test.resolvePromise( promise, hookName );
913
				return;
914
			}
915
			try {
916
				promise = hook.call( test.testEnvironment, test.assert );
917
				test.resolvePromise( promise, hookName );
918
			} catch ( error ) {
919
				test.pushFailure( hookName + " failed on " + test.testName + ": " +
920
					( error.message || error ), extractStacktrace( error, 0 ) );
921
			}
922
		};
923
	},
924

  
925
	// Currently only used for module level hooks, can be used to add global level ones
926
	hooks: function( handler ) {
927
		var hooks = [];
928

  
929
		// Hooks are ignored on skipped tests
930
		if ( this.skip ) {
931
			return hooks;
932
		}
933

  
934
		if ( this.module.testEnvironment &&
935
				QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
936
			hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
937
		}
938

  
939
		return hooks;
940
	},
941

  
942
	finish: function() {
943
		config.current = this;
944
		if ( config.requireExpects && this.expected === null ) {
945
			this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
946
				"not called.", this.stack );
947
		} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
948
			this.pushFailure( "Expected " + this.expected + " assertions, but " +
949
				this.assertions.length + " were run", this.stack );
950
		} else if ( this.expected === null && !this.assertions.length ) {
951
			this.pushFailure( "Expected at least one assertion, but none were run - call " +
952
				"expect(0) to accept zero assertions.", this.stack );
953
		}
954

  
955
		var i,
956
			bad = 0;
957

  
958
		this.runtime = now() - this.started;
959
		config.stats.all += this.assertions.length;
960
		config.moduleStats.all += this.assertions.length;
961

  
962
		for ( i = 0; i < this.assertions.length; i++ ) {
963
			if ( !this.assertions[ i ].result ) {
964
				bad++;
965
				config.stats.bad++;
966
				config.moduleStats.bad++;
967
			}
968
		}
969

  
970
		runLoggingCallbacks( "testDone", {
971
			name: this.testName,
972
			module: this.module.name,
973
			skipped: !!this.skip,
974
			failed: bad,
975
			passed: this.assertions.length - bad,
976
			total: this.assertions.length,
977
			runtime: this.runtime,
978

  
979
			// HTML Reporter use
980
			assertions: this.assertions,
981
			testId: this.testId,
982

  
983
			// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
984
			duration: this.runtime
985
		});
986

  
987
		// QUnit.reset() is deprecated and will be replaced for a new
988
		// fixture reset function on QUnit 2.0/2.1.
989
		// It's still called here for backwards compatibility handling
990
		QUnit.reset();
991

  
992
		config.current = undefined;
993
	},
994

  
995
	queue: function() {
996
		var bad,
997
			test = this;
998

  
999
		if ( !this.valid() ) {
1000
			return;
1001
		}
1002

  
1003
		function run() {
1004

  
1005
			// each of these can by async
1006
			synchronize([
1007
				function() {
1008
					test.before();
1009
				},
1010

  
1011
				test.hooks( "beforeEach" ),
1012

  
1013
				function() {
1014
					test.run();
1015
				},
1016

  
1017
				test.hooks( "afterEach" ).reverse(),
1018

  
1019
				function() {
1020
					test.after();
1021
				},
1022
				function() {
1023
					test.finish();
1024
				}
1025
			]);
1026
		}
1027

  
1028
		// `bad` initialized at top of scope
1029
		// defer when previous test run passed, if storage is available
1030
		bad = QUnit.config.reorder && defined.sessionStorage &&
1031
				+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1032

  
1033
		if ( bad ) {
1034
			run();
1035
		} else {
1036
			synchronize( run, true );
1037
		}
1038
	},
1039

  
1040
	push: function( result, actual, expected, message ) {
1041
		var source,
1042
			details = {
1043
				module: this.module.name,
1044
				name: this.testName,
1045
				result: result,
1046
				message: message,
1047
				actual: actual,
1048
				expected: expected,
1049
				testId: this.testId,
1050
				runtime: now() - this.started
1051
			};
1052

  
1053
		if ( !result ) {
1054
			source = sourceFromStacktrace();
1055

  
1056
			if ( source ) {
1057
				details.source = source;
1058
			}
1059
		}
1060

  
1061
		runLoggingCallbacks( "log", details );
1062

  
1063
		this.assertions.push({
1064
			result: !!result,
1065
			message: message
1066
		});
1067
	},
1068

  
1069
	pushFailure: function( message, source, actual ) {
1070
		if ( !this instanceof Test ) {
1071
			throw new Error( "pushFailure() assertion outside test context, was " +
1072
				sourceFromStacktrace( 2 ) );
1073
		}
1074

  
1075
		var details = {
1076
				module: this.module.name,
1077
				name: this.testName,
1078
				result: false,
1079
				message: message || "error",
1080
				actual: actual || null,
1081
				testId: this.testId,
1082
				runtime: now() - this.started
1083
			};
1084

  
1085
		if ( source ) {
1086
			details.source = source;
1087
		}
1088

  
1089
		runLoggingCallbacks( "log", details );
1090

  
1091
		this.assertions.push({
1092
			result: false,
1093
			message: message
1094
		});
1095
	},
1096

  
1097
	resolvePromise: function( promise, phase ) {
1098
		var then, message,
1099
			test = this;
1100
		if ( promise != null ) {
1101
			then = promise.then;
1102
			if ( QUnit.objectType( then ) === "function" ) {
1103
				QUnit.stop();
1104
				then.call(
1105
					promise,
1106
					QUnit.start,
1107
					function( error ) {
1108
						message = "Promise rejected " +
1109
							( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1110
							" " + test.testName + ": " + ( error.message || error );
1111
						test.pushFailure( message, extractStacktrace( error, 0 ) );
1112

  
1113
						// else next test will carry the responsibility
1114
						saveGlobal();
1115

  
1116
						// Unblock
1117
						QUnit.start();
1118
					}
1119
				);
1120
			}
1121
		}
1122
	},
1123

  
1124
	valid: function() {
1125
		var include,
1126
			filter = config.filter,
1127
			module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1128
			fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
1129

  
1130
		// Internally-generated tests are always valid
1131
		if ( this.callback && this.callback.validTest ) {
1132
			return true;
1133
		}
1134

  
1135
		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