1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005 Jon Tirsen (http://www.tirsen.com)
3 // (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 // experimental, Firefox-only
26 Event
.simulateMouse = function(element
, eventName
) {
27 var options
= Object
.extend({
31 }, arguments
[2] || {});
32 var oEvent
= document
.createEvent("MouseEvents");
33 oEvent
.initMouseEvent(eventName
, true, true, document
.defaultView
,
34 options
.buttons
, options
.pointerX
, options
.pointerY
, options
.pointerX
, options
.pointerY
,
35 false, false, false, false, 0, $(element
));
37 if(this.mark
) Element
.remove(this.mark
);
38 this.mark
= document
.createElement('div');
39 this.mark
.appendChild(document
.createTextNode(" "));
40 document
.body
.appendChild(this.mark
);
41 this.mark
.style
.position
= 'absolute';
42 this.mark
.style
.top
= options
.pointerY
+ "px";
43 this.mark
.style
.left
= options
.pointerX
+ "px";
44 this.mark
.style
.width
= "5px";
45 this.mark
.style
.height
= "5px;";
46 this.mark
.style
.borderTop
= "1px solid red;"
47 this.mark
.style
.borderLeft
= "1px solid red;"
50 alert('['+new Date().getTime().toString()+'] '+eventName
+'/'+Test
.Unit
.inspect(options
));
52 $(element
).dispatchEvent(oEvent
);
55 // 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.
56 // You need to downgrade to 1.0.4 for now to get this working
57 // See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
58 Event
.simulateKey = function(element
, eventName
) {
59 var options
= Object
.extend({
66 }, arguments
[2] || {});
68 var oEvent
= document
.createEvent("KeyEvents");
69 oEvent
.initKeyEvent(eventName
, true, true, window
,
70 options
.ctrlKey
, options
.altKey
, options
.shiftKey
, options
.metaKey
,
71 options
.keyCode
, options
.charCode
);
72 $(element
).dispatchEvent(oEvent
);
75 Event
.simulateKeys = function(element
, command
) {
76 for(var i
=0; i
<command
.length
; i
++) {
77 Event
.simulateKey(element
,'keypress',{charCode
:command
.charCodeAt(i
)});
84 // security exception workaround
85 Test
.Unit
.inspect
= Object
.inspect
;
87 Test
.Unit
.Logger
= Class
.create();
88 Test
.Unit
.Logger
.prototype = {
89 initialize: function(log
) {
92 this._createLogTable();
95 start: function(testName
) {
96 if (!this.log
) return;
97 this.testName
= testName
;
98 this.lastLogLine
= document
.createElement('tr');
99 this.statusCell
= document
.createElement('td');
100 this.nameCell
= document
.createElement('td');
101 this.nameCell
.appendChild(document
.createTextNode(testName
));
102 this.messageCell
= document
.createElement('td');
103 this.lastLogLine
.appendChild(this.statusCell
);
104 this.lastLogLine
.appendChild(this.nameCell
);
105 this.lastLogLine
.appendChild(this.messageCell
);
106 this.loglines
.appendChild(this.lastLogLine
);
108 finish: function(status
, summary
) {
109 if (!this.log
) return;
110 this.lastLogLine
.className
= status
;
111 this.statusCell
.innerHTML
= status
;
112 this.messageCell
.innerHTML
= this._toHTML(summary
);
114 message: function(message
) {
115 if (!this.log
) return;
116 this.messageCell
.innerHTML
= this._toHTML(message
);
118 summary: function(summary
) {
119 if (!this.log
) return;
120 this.logsummary
.innerHTML
= this._toHTML(summary
);
122 _createLogTable: function() {
124 '<div id="logsummary"></div>' +
125 '<table id="logtable">' +
126 '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
127 '<tbody id="loglines"></tbody>' +
129 this.logsummary
= $('logsummary')
130 this.loglines
= $('loglines');
132 _toHTML: function(txt
) {
133 return txt
.escapeHTML().replace(/\n/g,"<br/>");
137 Test
.Unit
.Runner
= Class
.create();
138 Test
.Unit
.Runner
.prototype = {
139 initialize: function(testcases
) {
140 this.options
= Object
.extend({
142 }, arguments
[1] || {});
143 this.options
.resultsURL
= this.parseResultsURLQueryParameter();
144 if (this.options
.testLog
) {
145 this.options
.testLog
= $(this.options
.testLog
) || null;
147 if(this.options
.tests
) {
149 for(var i
= 0; i
< this.options
.tests
.length
; i
++) {
150 if(/^test/.test(this.options
.tests
[i
])) {
151 this.tests
.push(new Test
.Unit
.Testcase(this.options
.tests
[i
], testcases
[this.options
.tests
[i
]], testcases
["setup"], testcases
["teardown"]));
155 if (this.options
.test
) {
156 this.tests
= [new Test
.Unit
.Testcase(this.options
.test
, testcases
[this.options
.test
], testcases
["setup"], testcases
["teardown"])];
159 for(var testcase
in testcases
) {
160 if(/^test/.test(testcase
)) {
161 this.tests
.push(new Test
.Unit
.Testcase(testcase
, testcases
[testcase
], testcases
["setup"], testcases
["teardown"]));
166 this.currentTest
= 0;
167 this.logger
= new Test
.Unit
.Logger(this.options
.testLog
);
168 setTimeout(this.runTests
.bind(this), 1000);
170 parseResultsURLQueryParameter: function() {
171 return window
.location
.search
.parseQuery()["resultsURL"];
174 // "ERROR" if there was an error,
175 // "FAILURE" if there was a failure, or
176 // "SUCCESS" if there was neither
177 getResult: function() {
178 var hasFailure
= false;
179 for(var i
=0;i
<this.tests
.length
;i
++) {
180 if (this.tests
[i
].errors
> 0) {
183 if (this.tests
[i
].failures
> 0) {
193 postResults: function() {
194 if (this.options
.resultsURL
) {
195 new Ajax
.Request(this.options
.resultsURL
,
196 { method
: 'get', parameters
: 'result=' + this.getResult(), asynchronous
: false });
199 runTests: function() {
200 var test
= this.tests
[this.currentTest
];
204 this.logger
.summary(this.summary());
207 if(!test
.isWaiting
) {
208 this.logger
.start(test
.name
);
212 this.logger
.message("Waiting for " + test
.timeToWait
+ "ms");
213 setTimeout(this.runTests
.bind(this), test
.timeToWait
|| 1000);
215 this.logger
.finish(test
.status(), test
.summary());
217 // tail recursive, hopefully the browser will skip the stackframe
221 summary: function() {
226 for(var i
=0;i
<this.tests
.length
;i
++) {
227 assertions
+= this.tests
[i
].assertions
;
228 failures
+= this.tests
[i
].failures
;
229 errors
+= this.tests
[i
].errors
;
232 this.tests
.length
+ " tests, " +
233 assertions
+ " assertions, " +
234 failures
+ " failures, " +
239 Test
.Unit
.Assertions
= Class
.create();
240 Test
.Unit
.Assertions
.prototype = {
241 initialize: function() {
247 summary: function() {
249 this.assertions
+ " assertions, " +
250 this.failures
+ " failures, " +
251 this.errors
+ " errors" + "\n" +
252 this.messages
.join("\n"));
257 fail: function(message
) {
259 this.messages
.push("Failure: " + message
);
261 info: function(message
) {
262 this.messages
.push("Info: " + message
);
264 error: function(error
) {
266 this.messages
.push(error
.name
+ ": "+ error
.message
+ "(" + Test
.Unit
.inspect(error
) +")");
269 if (this.failures
> 0) return 'failed';
270 if (this.errors
> 0) return 'error';
273 assert: function(expression
) {
274 var message
= arguments
[1] || 'assert: got "' + Test
.Unit
.inspect(expression
) + '"';
275 try { expression
? this.pass() :
276 this.fail(message
); }
277 catch(e
) { this.error(e
); }
279 assertEqual: function(expected
, actual
) {
280 var message
= arguments
[2] || "assertEqual";
281 try { (expected
== actual
) ? this.pass() :
282 this.fail(message
+ ': expected "' + Test
.Unit
.inspect(expected
) +
283 '", actual "' + Test
.Unit
.inspect(actual
) + '"'); }
284 catch(e
) { this.error(e
); }
286 assertEnumEqual: function(expected
, actual
) {
287 var message
= arguments
[2] || "assertEnumEqual";
288 try { $A(expected
).length
== $A(actual
).length
&&
289 expected
.zip(actual
).all(function(pair
) { return pair
[0] == pair
[1] }) ?
290 this.pass() : this.fail(message
+ ': expected ' + Test
.Unit
.inspect(expected
) +
291 ', actual ' + Test
.Unit
.inspect(actual
)); }
292 catch(e
) { this.error(e
); }
294 assertNotEqual: function(expected
, actual
) {
295 var message
= arguments
[2] || "assertNotEqual";
296 try { (expected
!= actual
) ? this.pass() :
297 this.fail(message
+ ': got "' + Test
.Unit
.inspect(actual
) + '"'); }
298 catch(e
) { this.error(e
); }
300 assertNull: function(obj
) {
301 var message
= arguments
[1] || 'assertNull'
302 try { (obj
==null) ? this.pass() :
303 this.fail(message
+ ': got "' + Test
.Unit
.inspect(obj
) + '"'); }
304 catch(e
) { this.error(e
); }
306 assertHidden: function(element
) {
307 var message
= arguments
[1] || 'assertHidden';
308 this.assertEqual("none", element
.style
.display
, message
);
310 assertNotNull: function(object
) {
311 var message
= arguments
[1] || 'assertNotNull';
312 this.assert(object
!= null, message
);
314 assertInstanceOf: function(expected
, actual
) {
315 var message
= arguments
[2] || 'assertInstanceOf';
317 (actual
instanceof expected
) ? this.pass() :
318 this.fail(message
+ ": object was not an instance of the expected type"); }
319 catch(e
) { this.error(e
); }
321 assertNotInstanceOf: function(expected
, actual
) {
322 var message
= arguments
[2] || 'assertNotInstanceOf';
324 !(actual
instanceof expected
) ? this.pass() :
325 this.fail(message
+ ": object was an instance of the not expected type"); }
326 catch(e
) { this.error(e
); }
328 _isVisible: function(element
) {
329 element
= $(element
);
330 if(!element
.parentNode
) return true;
331 this.assertNotNull(element
);
332 if(element
.style
&& Element
.getStyle(element
, 'display') == 'none')
335 return this._isVisible(element
.parentNode
);
337 assertNotVisible: function(element
) {
338 this.assert(!this._isVisible(element
), Test
.Unit
.inspect(element
) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments
[1]));
340 assertVisible: function(element
) {
341 this.assert(this._isVisible(element
), Test
.Unit
.inspect(element
) + " was not visible. " + ("" || arguments
[1]));
343 benchmark: function(operation
, iterations
) {
344 var startAt
= new Date();
345 (iterations
|| 1).times(operation
);
346 var timeTaken
= ((new Date())-startAt
);
347 this.info((arguments
[2] || 'Operation') + ' finished ' +
348 iterations
+ ' iterations in ' + (timeTaken
/1000)+'s' );
353 Test
.Unit
.Testcase
= Class
.create();
354 Object
.extend(Object
.extend(Test
.Unit
.Testcase
.prototype, Test
.Unit
.Assertions
.prototype), {
355 initialize: function(name
, test
, setup
, teardown
) {
356 Test
.Unit
.Assertions
.prototype.initialize
.bind(this)();
358 this.test
= test
|| function() {};
359 this.setup
= setup
|| function() {};
360 this.teardown
= teardown
|| function() {};
361 this.isWaiting
= false;
362 this.timeToWait
= 1000;
364 wait: function(time
, nextPart
) {
365 this.isWaiting
= true;
366 this.test
= nextPart
;
367 this.timeToWait
= time
;
372 if (!this.isWaiting
) this.setup
.bind(this)();
373 this.isWaiting
= false;
374 this.test
.bind(this)();
376 if(!this.isWaiting
) {
377 this.teardown
.bind(this)();
381 catch(e
) { this.error(e
); }