2 * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
6 * Test.Simple doesn't work on IE < 6.
8 * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
9 * itself against IE 5.5
13 if (typeof(SimpleTest
) == "undefined") {
17 var parentRunner
= null;
18 if (typeof(parent
) != "undefined" && parent
.TestRunner
) {
19 parentRunner
= parent
.TestRunner
;
20 } else if (parent
&& parent
.wrappedJSObject
&&
21 parent
.wrappedJSObject
.TestRunner
) {
22 parentRunner
= parent
.wrappedJSObject
.TestRunner
;
25 // Check to see if the TestRunner is present and has logging
27 SimpleTest
._logEnabled
= parentRunner
.logEnabled
;
30 SimpleTest
._tests
= [];
31 SimpleTest
._stopOnLoad
= true;
34 * Something like assert.
36 SimpleTest
.ok = function (condition
, name
, diag
) {
37 var test
= {'result': !!condition
, 'name': name
, 'diag': diag
|| ""};
38 if (SimpleTest
._logEnabled
)
39 SimpleTest
._logResult(test
, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
40 SimpleTest
._tests
.push(test
);
44 * Roughly equivalent to ok(a==b, name)
46 SimpleTest
.is = function (a
, b
, name
) {
47 var repr
= MochiKit
.Base
.repr
;
48 SimpleTest
.ok(a
== b
, name
, "got " + repr(a
) + ", expected " + repr(b
));
51 SimpleTest
.isnot = function (a
, b
, name
) {
52 var repr
= MochiKit
.Base
.repr
;
53 SimpleTest
.ok(a
!= b
, name
, "Didn't expect " + repr(a
) + ", but got it.");
56 // --------------- Test.Builder/Test.More todo() -----------------
58 SimpleTest
.todo = function(condition
, name
, diag
) {
59 var test
= {'result': !!condition
, 'name': name
, 'diag': diag
|| "", todo
: true};
60 if (SimpleTest
._logEnabled
)
61 SimpleTest
._logResult(test
, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
62 SimpleTest
._tests
.push(test
);
65 SimpleTest
._logResult = function(test
, passString
, failString
) {
66 var msg
= test
.result
? passString
: failString
;
68 if (parentRunner
.currentTestURL
)
69 msg
+= parentRunner
.currentTestURL
;
70 msg
+= " | " + test
.name
;
73 diag
= " - " + test
.diag
;
76 parentRunner
.logger
.error(msg
+ diag
);
78 parentRunner
.logger
.log(msg
);
81 parentRunner
.logger
.log(msg
);
83 parentRunner
.logger
.error(msg
+ diag
);
88 * Copies of is and isnot with the call to ok replaced by a call to todo.
91 SimpleTest
.todo_is = function (a
, b
, name
) {
92 var repr
= MochiKit
.Base
.repr
;
93 SimpleTest
.todo(a
== b
, name
, "got " + repr(a
) + ", expected " + repr(b
));
96 SimpleTest
.todo_isnot = function (a
, b
, name
) {
97 var repr
= MochiKit
.Base
.repr
;
98 SimpleTest
.todo(a
!= b
, name
, "Didn't expect " + repr(a
) + ", but got it.");
103 * Makes a test report, returns it as a DIV element.
105 SimpleTest
.report = function () {
106 var DIV
= MochiKit
.DOM
.DIV
;
110 var results
= MochiKit
.Base
.map(
113 if (test
.todo
&& !test
.result
) {
116 msg
= "todo - " + test
.name
+ " " + test
.diag
;
117 } else if (test
.result
&&!test
.todo
) {
120 msg
= "ok - " + test
.name
;
124 msg
= "not ok - " + test
.name
+ " " + test
.diag
;
126 return DIV({"class": cls
}, msg
);
130 var summary_class
= ((failed
== 0) ? 'all_pass' : 'some_fail');
131 return DIV({'class': 'tests_report'},
132 DIV({'class': 'tests_summary ' + summary_class
},
133 DIV({'class': 'tests_passed'}, "Passed: " + passed
),
134 DIV({'class': 'tests_failed'}, "Failed: " + failed
),
135 DIV({'class': 'tests_todo'}, "Todo: " + todo
)),
141 * Toggle element visibility
143 SimpleTest
.toggle = function(el
) {
144 if (MochiKit
.Style
.computedStyle(el
, 'display') == 'block') {
145 el
.style
.display
= 'none';
147 el
.style
.display
= 'block';
152 * Toggle visibility for divs with a specific class.
154 SimpleTest
.toggleByClass = function (cls
, evt
) {
155 var elems
= getElementsByTagAndClassName('div', cls
);
156 MochiKit
.Base
.map(SimpleTest
.toggle
, elems
);
158 evt
.preventDefault();
162 * Shows the report in the browser
165 SimpleTest
.showReport = function() {
166 var togglePassed
= A({'href': '#'}, "Toggle passed tests");
167 var toggleFailed
= A({'href': '#'}, "Toggle failed tests");
168 togglePassed
.onclick
= partial(SimpleTest
.toggleByClass
, 'test_ok');
169 toggleFailed
.onclick
= partial(SimpleTest
.toggleByClass
, 'test_not_ok');
170 var body
= document
.body
; // Handles HTML documents
173 body
= document
.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
176 var firstChild
= body
.childNodes
[0];
179 addNode = function (el
) {
180 body
.insertBefore(el
, firstChild
);
183 addNode = function (el
) {
187 addNode(togglePassed
);
188 addNode(SPAN(null, " "));
189 addNode(toggleFailed
);
190 addNode(SimpleTest
.report());
194 * Tells SimpleTest to don't finish the test when the document is loaded,
195 * useful for asynchronous tests.
197 * When SimpleTest.waitForExplicitFinish is called,
198 * explicit SimpleTest.finish() is required.
200 SimpleTest
.waitForExplicitFinish = function () {
201 SimpleTest
._stopOnLoad
= false;
205 * Talks to the TestRunner if being ran on a iframe and the parent has a
208 SimpleTest
.talkToRunner = function () {
210 parentRunner
.testFinished(document
);
215 * Finishes the tests. This is automatically called, except when
216 * SimpleTest.waitForExplicitFinish() has been invoked.
218 SimpleTest
.finish = function () {
219 SimpleTest
.showReport();
220 SimpleTest
.talkToRunner();
224 addLoadEvent(function() {
225 if (SimpleTest
._stopOnLoad
) {
230 // --------------- Test.Builder/Test.More isDeeply() -----------------
233 SimpleTest
.DNE
= {dne
: 'Does not exist'};
234 SimpleTest
.LF
= "\r\n";
235 SimpleTest
._isRef = function (object
) {
236 var type
= typeof(object
);
237 return type
== 'object' || type
== 'function';
241 SimpleTest
._deepCheck = function (e1
, e2
, stack
, seen
) {
243 // Either they're both references or both not.
244 var sameRef
= !(!SimpleTest
._isRef(e1
) ^ !SimpleTest
._isRef(e2
));
245 if (e1
== null && e2
== null) {
247 } else if (e1
!= null ^ e2
!= null) {
249 } else if (e1
== SimpleTest
.DNE
^ e2
== SimpleTest
.DNE
) {
251 } else if (sameRef
&& e1
== e2
) {
252 // Handles primitives and any variables that reference the same
253 // object, including functions.
255 } else if (SimpleTest
.isa(e1
, 'Array') && SimpleTest
.isa(e2
, 'Array')) {
256 ok
= SimpleTest
._eqArray(e1
, e2
, stack
, seen
);
257 } else if (typeof e1
== "object" && typeof e2
== "object") {
258 ok
= SimpleTest
._eqAssoc(e1
, e2
, stack
, seen
);
260 // If we get here, they're not the same (function references must
261 // always simply rererence the same function).
262 stack
.push({ vals
: [e1
, e2
] });
268 SimpleTest
._eqArray = function (a1
, a2
, stack
, seen
) {
269 // Return if they're the same object.
270 if (a1
== a2
) return true;
272 // JavaScript objects have no unique identifiers, so we have to store
273 // references to them all in an array, and then compare the references
274 // directly. It's slow, but probably won't be much of an issue in
275 // practice. Start by making a local copy of the array to as to avoid
276 // confusing a reference seen more than once (such as [a, a]) for a
277 // circular reference.
278 for (var j
= 0; j
< seen
.length
; j
++) {
279 if (seen
[j
][0] == a1
) {
280 return seen
[j
][1] == a2
;
284 // If we get here, we haven't seen a1 before, so store it with reference
286 seen
.push([ a1
, a2
]);
289 // Only examines enumerable attributes. Only works for numeric arrays!
290 // Associative arrays return 0. So call _eqAssoc() for them, instead.
291 var max
= a1
.length
> a2
.length
? a1
.length
: a2
.length
;
292 if (max
== 0) return SimpleTest
._eqAssoc(a1
, a2
, stack
, seen
);
293 for (var i
= 0; i
< max
; i
++) {
294 var e1
= i
> a1
.length
- 1 ? SimpleTest
.DNE
: a1
[i
];
295 var e2
= i
> a2
.length
- 1 ? SimpleTest
.DNE
: a2
[i
];
296 stack
.push({ type
: 'Array', idx
: i
, vals
: [e1
, e2
] });
297 if (ok
= SimpleTest
._deepCheck(e1
, e2
, stack
, seen
)) {
306 SimpleTest
._eqAssoc = function (o1
, o2
, stack
, seen
) {
307 // Return if they're the same object.
308 if (o1
== o2
) return true;
310 // JavaScript objects have no unique identifiers, so we have to store
311 // references to them all in an array, and then compare the references
312 // directly. It's slow, but probably won't be much of an issue in
313 // practice. Start by making a local copy of the array to as to avoid
314 // confusing a reference seen more than once (such as [a, a]) for a
315 // circular reference.
316 seen
= seen
.slice(0);
317 for (var j
= 0; j
< seen
.length
; j
++) {
318 if (seen
[j
][0] == o1
) {
319 return seen
[j
][1] == o2
;
323 // If we get here, we haven't seen o1 before, so store it with reference
325 seen
.push([ o1
, o2
]);
327 // They should be of the same class.
330 // Only examines enumerable attributes.
331 var o1Size
= 0; for (var i
in o1
) o1Size
++;
332 var o2Size
= 0; for (var i
in o2
) o2Size
++;
333 var bigger
= o1Size
> o2Size
? o1
: o2
;
334 for (var i
in bigger
) {
335 var e1
= o1
[i
] == undefined ? SimpleTest
.DNE
: o1
[i
];
336 var e2
= o2
[i
] == undefined ? SimpleTest
.DNE
: o2
[i
];
337 stack
.push({ type
: 'Object', idx
: i
, vals
: [e1
, e2
] });
338 if (ok
= SimpleTest
._deepCheck(e1
, e2
, stack
, seen
)) {
347 SimpleTest
._formatStack = function (stack
) {
348 var variable
= '$Foo';
349 for (var i
= 0; i
< stack
.length
; i
++) {
350 var entry
= stack
[i
];
351 var type
= entry
['type'];
352 var idx
= entry
['idx'];
354 if (/^\d+$/.test(idx
)) {
355 // Numeric array index.
356 variable
+= '[' + idx
+ ']';
358 // Associative array index.
359 idx
= idx
.replace("'", "\\'");
360 variable
+= "['" + idx
+ "']";
365 var vals
= stack
[stack
.length
-1]['vals'].slice(0, 2);
367 variable
.replace('$Foo', 'got'),
368 variable
.replace('$Foo', 'expected')
371 var out
= "Structures begin differing at:" + SimpleTest
.LF
;
372 for (var i
= 0; i
< vals
.length
; i
++) {
377 val
== SimpleTest
.DNE
? "Does not exist" : "'" + val
+ "'";
381 out
+= vars
[0] + ' = ' + vals
[0] + SimpleTest
.LF
;
382 out
+= vars
[1] + ' = ' + vals
[1] + SimpleTest
.LF
;
388 SimpleTest
.isDeeply = function (it
, as
, name
) {
390 // ^ is the XOR operator.
391 if (SimpleTest
._isRef(it
) ^ SimpleTest
._isRef(as
)) {
392 // One's a reference, one isn't.
394 } else if (!SimpleTest
._isRef(it
) && !SimpleTest
._isRef(as
)) {
395 // Neither is an object.
396 ok
= SimpleTest
.is(it
, as
, name
);
398 // We have two objects. Do a deep comparison.
399 var stack
= [], seen
= [];
400 if ( SimpleTest
._deepCheck(it
, as
, stack
, seen
)) {
401 ok
= SimpleTest
.ok(true, name
);
403 ok
= SimpleTest
.ok(false, name
, SimpleTest
._formatStack(stack
));
409 SimpleTest
.typeOf = function (object
) {
410 var c
= Object
.prototype.toString
.apply(object
);
411 var name
= c
.substring(8, c
.length
- 1);
412 if (name
!= 'Object') return name
;
413 // It may be a non-core class. Try to extract the class name from
414 // the constructor function. This may not work in all implementations.
415 if (/function ([^(\s]+)/.test(Function
.toString
.call(object
.constructor))) {
422 SimpleTest
.isa = function (object
, clas
) {
423 return SimpleTest
.typeOf(object
) == clas
;
427 var ok
= SimpleTest
.ok
;
428 var is
= SimpleTest
.is
;
429 var isnot
= SimpleTest
.isnot
;
430 var todo
= SimpleTest
.todo
;
431 var todo_is
= SimpleTest
.todo_is
;
432 var todo_isnot
= SimpleTest
.todo_isnot
;
433 var isDeeply
= SimpleTest
.isDeeply
;
434 var oldOnError
= window
.onerror
;
435 window
.onerror = function (ev
) {
436 is(0, 1, "Error thrown during test: " + ev
);
443 if (SimpleTest
._stopOnLoad
== false) {
444 // Need to finish() manually here