1
// script.aculo.us unittest.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
3
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
5
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
7
// Permission is hereby granted, free of charge, to any person obtaining
8
// a copy of this software and associated documentation files (the
9
// "Software"), to deal in the Software without restriction, including
10
// without limitation the rights to use, copy, modify, merge, publish,
11
// distribute, sublicense, and/or sell copies of the Software, and to
12
// permit persons to whom the Software is furnished to do so, subject to
13
// the following conditions:
15
// The above copyright notice and this permission notice shall be
16
// included in all copies or substantial portions of the Software.
18
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
// experimental, Firefox-only
28
Event.simulateMouse = function(element, eventName) {
29
var options = Object.extend({
33
}, arguments[2] || {});
34
var oEvent = document.createEvent("MouseEvents");
35
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
36
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
37
false, false, false, false, 0, $(element));
39
if(this.mark) Element.remove(this.mark);
40
this.mark = document.createElement('div');
41
this.mark.appendChild(document.createTextNode(" "));
42
document.body.appendChild(this.mark);
43
this.mark.style.position = 'absolute';
44
this.mark.style.top = options.pointerY + "px";
45
this.mark.style.left = options.pointerX + "px";
46
this.mark.style.width = "5px";
47
this.mark.style.height = "5px;";
48
this.mark.style.borderTop = "1px solid red;"
49
this.mark.style.borderLeft = "1px solid red;"
52
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
54
$(element).dispatchEvent(oEvent);
57
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
58
// You need to downgrade to 1.0.4 for now to get this working
59
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
60
Event.simulateKey = function(element, eventName) {
61
var options = Object.extend({
68
}, arguments[2] || {});
70
var oEvent = document.createEvent("KeyEvents");
71
oEvent.initKeyEvent(eventName, true, true, window,
72
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
73
options.keyCode, options.charCode );
74
$(element).dispatchEvent(oEvent);
77
Event.simulateKeys = function(element, command) {
78
for(var i=0; i<command.length; i++) {
79
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
86
// security exception workaround
87
Test.Unit.inspect = Object.inspect;
89
Test.Unit.Logger = Class.create();
90
Test.Unit.Logger.prototype = {
91
initialize: function(log) {
94
this._createLogTable();
97
start: function(testName) {
98
if (!this.log) return;
99
this.testName = testName;
100
this.lastLogLine = document.createElement('tr');
101
this.statusCell = document.createElement('td');
102
this.nameCell = document.createElement('td');
103
this.nameCell.appendChild(document.createTextNode(testName));
104
this.messageCell = document.createElement('td');
105
this.lastLogLine.appendChild(this.statusCell);
106
this.lastLogLine.appendChild(this.nameCell);
107
this.lastLogLine.appendChild(this.messageCell);
108
this.loglines.appendChild(this.lastLogLine);
110
finish: function(status, summary) {
111
if (!this.log) return;
112
this.lastLogLine.className = status;
113
this.statusCell.innerHTML = status;
114
this.messageCell.innerHTML = this._toHTML(summary);
116
message: function(message) {
117
if (!this.log) return;
118
this.messageCell.innerHTML = this._toHTML(message);
120
summary: function(summary) {
121
if (!this.log) return;
122
this.logsummary.innerHTML = this._toHTML(summary);
124
_createLogTable: function() {
126
'<div id="logsummary"></div>' +
127
'<table id="logtable">' +
128
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
129
'<tbody id="loglines"></tbody>' +
131
this.logsummary = $('logsummary')
132
this.loglines = $('loglines');
134
_toHTML: function(txt) {
135
return txt.escapeHTML().replace(/\n/g,"<br/>");
139
Test.Unit.Runner = Class.create();
140
Test.Unit.Runner.prototype = {
141
initialize: function(testcases) {
142
this.options = Object.extend({
144
}, arguments[1] || {});
145
this.options.resultsURL = this.parseResultsURLQueryParameter();
146
if (this.options.testLog) {
147
this.options.testLog = $(this.options.testLog) || null;
149
if(this.options.tests) {
151
for(var i = 0; i < this.options.tests.length; i++) {
152
if(/^test/.test(this.options.tests[i])) {
153
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
157
if (this.options.test) {
158
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
161
for(var testcase in testcases) {
162
if(/^test/.test(testcase)) {
164
new Test.Unit.Testcase(
165
this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
166
testcases[testcase], testcases["setup"], testcases["teardown"]
172
this.currentTest = 0;
173
this.logger = new Test.Unit.Logger(this.options.testLog);
174
setTimeout(this.runTests.bind(this), 1000);
176
parseResultsURLQueryParameter: function() {
177
return window.location.search.parseQuery()["resultsURL"];
180
// "ERROR" if there was an error,
181
// "FAILURE" if there was a failure, or
182
// "SUCCESS" if there was neither
183
getResult: function() {
184
var hasFailure = false;
185
for(var i=0;i<this.tests.length;i++) {
186
if (this.tests[i].errors > 0) {
189
if (this.tests[i].failures > 0) {
199
postResults: function() {
200
if (this.options.resultsURL) {
201
new Ajax.Request(this.options.resultsURL,
202
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
205
runTests: function() {
206
var test = this.tests[this.currentTest];
210
this.logger.summary(this.summary());
213
if(!test.isWaiting) {
214
this.logger.start(test.name);
218
this.logger.message("Waiting for " + test.timeToWait + "ms");
219
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
221
this.logger.finish(test.status(), test.summary());
223
// tail recursive, hopefully the browser will skip the stackframe
227
summary: function() {
232
for(var i=0;i<this.tests.length;i++) {
233
assertions += this.tests[i].assertions;
234
failures += this.tests[i].failures;
235
errors += this.tests[i].errors;
238
(this.options.context ? this.options.context + ': ': '') +
239
this.tests.length + " tests, " +
240
assertions + " assertions, " +
241
failures + " failures, " +
246
Test.Unit.Assertions = Class.create();
247
Test.Unit.Assertions.prototype = {
248
initialize: function() {
254
summary: function() {
256
this.assertions + " assertions, " +
257
this.failures + " failures, " +
258
this.errors + " errors" + "\n" +
259
this.messages.join("\n"));
264
fail: function(message) {
266
this.messages.push("Failure: " + message);
268
info: function(message) {
269
this.messages.push("Info: " + message);
271
error: function(error) {
273
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
276
if (this.failures > 0) return 'failed';
277
if (this.errors > 0) return 'error';
280
assert: function(expression) {
281
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
282
try { expression ? this.pass() :
283
this.fail(message); }
284
catch(e) { this.error(e); }
286
assertEqual: function(expected, actual) {
287
var message = arguments[2] || "assertEqual";
288
try { (expected == actual) ? this.pass() :
289
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
290
'", actual "' + Test.Unit.inspect(actual) + '"'); }
291
catch(e) { this.error(e); }
293
assertEnumEqual: function(expected, actual) {
294
var message = arguments[2] || "assertEnumEqual";
295
try { $A(expected).length == $A(actual).length &&
296
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
297
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
298
', actual ' + Test.Unit.inspect(actual)); }
299
catch(e) { this.error(e); }
301
assertNotEqual: function(expected, actual) {
302
var message = arguments[2] || "assertNotEqual";
303
try { (expected != actual) ? this.pass() :
304
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
305
catch(e) { this.error(e); }
307
assertIdentical: function(expected, actual) {
308
var message = arguments[2] || "assertIdentical";
309
try { (expected === actual) ? this.pass() :
310
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
311
'", actual "' + Test.Unit.inspect(actual) + '"'); }
312
catch(e) { this.error(e); }
314
assertNotIdentical: function(expected, actual) {
315
var message = arguments[2] || "assertNotIdentical";
316
try { !(expected === actual) ? this.pass() :
317
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
318
'", actual "' + Test.Unit.inspect(actual) + '"'); }
319
catch(e) { this.error(e); }
321
assertNull: function(obj) {
322
var message = arguments[1] || 'assertNull'
323
try { (obj==null) ? this.pass() :
324
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
325
catch(e) { this.error(e); }
327
assertMatch: function(expected, actual) {
328
var message = arguments[2] || 'assertMatch';
329
var regex = new RegExp(expected);
330
try { (regex.exec(actual)) ? this.pass() :
331
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
332
catch(e) { this.error(e); }
334
assertHidden: function(element) {
335
var message = arguments[1] || 'assertHidden';
336
this.assertEqual("none", element.style.display, message);
338
assertNotNull: function(object) {
339
var message = arguments[1] || 'assertNotNull';
340
this.assert(object != null, message);
342
assertType: function(expected, actual) {
343
var message = arguments[2] || 'assertType';
345
(actual.constructor == expected) ? this.pass() :
346
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
347
'", actual "' + (actual.constructor) + '"'); }
348
catch(e) { this.error(e); }
350
assertNotOfType: function(expected, actual) {
351
var message = arguments[2] || 'assertNotOfType';
353
(actual.constructor != expected) ? this.pass() :
354
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
355
'", actual "' + (actual.constructor) + '"'); }
356
catch(e) { this.error(e); }
358
assertInstanceOf: function(expected, actual) {
359
var message = arguments[2] || 'assertInstanceOf';
361
(actual instanceof expected) ? this.pass() :
362
this.fail(message + ": object was not an instance of the expected type"); }
363
catch(e) { this.error(e); }
365
assertNotInstanceOf: function(expected, actual) {
366
var message = arguments[2] || 'assertNotInstanceOf';
368
!(actual instanceof expected) ? this.pass() :
369
this.fail(message + ": object was an instance of the not expected type"); }
370
catch(e) { this.error(e); }
372
assertRespondsTo: function(method, obj) {
373
var message = arguments[2] || 'assertRespondsTo';
375
(obj[method] && typeof obj[method] == 'function') ? this.pass() :
376
this.fail(message + ": object doesn't respond to [" + method + "]"); }
377
catch(e) { this.error(e); }
379
assertReturnsTrue: function(method, obj) {
380
var message = arguments[2] || 'assertReturnsTrue';
383
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
385
this.fail(message + ": method returned false"); }
386
catch(e) { this.error(e); }
388
assertReturnsFalse: function(method, obj) {
389
var message = arguments[2] || 'assertReturnsFalse';
392
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
394
this.fail(message + ": method returned true"); }
395
catch(e) { this.error(e); }
397
assertRaise: function(exceptionName, method) {
398
var message = arguments[2] || 'assertRaise';
401
this.fail(message + ": exception expected but none was raised"); }
403
(e.name==exceptionName) ? this.pass() : this.error(e);
406
assertElementsMatch: function() {
407
var expressions = $A(arguments), elements = $A(expressions.shift());
408
if (elements.length != expressions.length) {
409
this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
412
elements.zip(expressions).all(function(pair, index) {
413
var element = $(pair.first()), expression = pair.last();
414
if (element.match(expression)) return true;
415
this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
416
}.bind(this)) && this.pass();
418
assertElementMatches: function(element, expression) {
419
this.assertElementsMatch([element], expression);
421
benchmark: function(operation, iterations) {
422
var startAt = new Date();
423
(iterations || 1).times(operation);
424
var timeTaken = ((new Date())-startAt);
425
this.info((arguments[2] || 'Operation') + ' finished ' +
426
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
429
_isVisible: function(element) {
430
element = $(element);
431
if(!element.parentNode) return true;
432
this.assertNotNull(element);
433
if(element.style && Element.getStyle(element, 'display') == 'none')
436
return this._isVisible(element.parentNode);
438
assertNotVisible: function(element) {
439
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
441
assertVisible: function(element) {
442
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
444
benchmark: function(operation, iterations) {
445
var startAt = new Date();
446
(iterations || 1).times(operation);
447
var timeTaken = ((new Date())-startAt);
448
this.info((arguments[2] || 'Operation') + ' finished ' +
449
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
454
Test.Unit.Testcase = Class.create();
455
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
456
initialize: function(name, test, setup, teardown) {
457
Test.Unit.Assertions.prototype.initialize.bind(this)();
460
if(typeof test == 'string') {
461
test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
462
test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
463
this.test = function() {
464
eval('with(this){'+test+'}');
467
this.test = test || function() {};
470
this.setup = setup || function() {};
471
this.teardown = teardown || function() {};
472
this.isWaiting = false;
473
this.timeToWait = 1000;
475
wait: function(time, nextPart) {
476
this.isWaiting = true;
477
this.test = nextPart;
478
this.timeToWait = time;
483
if (!this.isWaiting) this.setup.bind(this)();
484
this.isWaiting = false;
485
this.test.bind(this)();
487
if(!this.isWaiting) {
488
this.teardown.bind(this)();
492
catch(e) { this.error(e); }
496
// *EXPERIMENTAL* BDD-style testing to please non-technical folk
497
// This draws many ideas from RSpec http://rspec.rubyforge.org/
499
Test.setupBDDExtensionMethods = function(){
501
shouldEqual: 'assertEqual',
502
shouldNotEqual: 'assertNotEqual',
503
shouldEqualEnum: 'assertEnumEqual',
504
shouldBeA: 'assertType',
505
shouldNotBeA: 'assertNotOfType',
506
shouldBeAn: 'assertType',
507
shouldNotBeAn: 'assertNotOfType',
508
shouldBeNull: 'assertNull',
509
shouldNotBeNull: 'assertNotNull',
511
shouldBe: 'assertReturnsTrue',
512
shouldNotBe: 'assertReturnsFalse',
513
shouldRespondTo: 'assertRespondsTo'
515
Test.BDDMethods = {};
516
for(m in METHODMAP) {
517
Test.BDDMethods[m] = eval(
519
'var args = $A(arguments);'+
520
'var scope = args.shift();'+
521
'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }');
523
[Array.prototype, String.prototype, Number.prototype].each(
524
function(p){ Object.extend(p, Test.BDDMethods) }
528
Test.context = function(name, spec, log){
529
Test.setupBDDExtensionMethods();
531
var compiledSpec = {};
533
for(specName in spec) {
537
compiledSpec[specName] = spec[specName];
540
var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
541
var body = spec[specName].toString().split('\n').slice(1);
542
if(/^\{/.test(body[0])) body = body.slice(1);
544
body = body.map(function(statement){
545
return statement.strip()
547
compiledSpec[testName] = body.join('\n');
548
titles[testName] = specName;
551
new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
b'\\ No newline at end of file'