|
1 /**
2 * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
3 *
4 * Why?
5 *
6 * Test.Simple doesn't work on IE < 6.
7 * TODO:
8 * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
9 * itself against IE 5.5
10 *
11 **/
12
13 if (typeof(SimpleTest) == "undefined") {
14 var SimpleTest = {};
15 }
16
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;
23 }
24
25 // Check to see if the TestRunner is present and has logging
26 if (parentRunner) {
27 SimpleTest._logEnabled = parentRunner.logEnabled;
28 }
29
30 SimpleTest._tests = [];
31 SimpleTest._stopOnLoad = true;
32
33 /**
34 * Something like assert.
35 **/
36 SimpleTest.ok = function (condition, name, diag) {
37 var test = {'result': !!condition, 'name': name, 'diag': diag || ""};
38 if (SimpleTest._logEnabled)
39 SimpleTest._logResult(test, "PASS", "FAIL");
40 SimpleTest._tests.push(test);
41 };
42
43 /**
44 * Roughly equivalent to ok(a==b, name)
45 **/
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));
49 };
50
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.");
54 };
55
56 // --------------- Test.Builder/Test.More todo() -----------------
57
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, "TODO WORKED?", "TODO");
62 SimpleTest._tests.push(test);
63 }
64
65 SimpleTest._logResult = function(test, passString, failString) {
66 var msg = test.result ? passString : failString;
67 msg += " | " + test.name;
68 if (test.result) {
69 if (test.todo)
70 parentRunner.logger.error(msg)
71 else
72 parentRunner.logger.log(msg);
73 } else {
74 msg += " | " + test.diag;
75 if (test.todo)
76 parentRunner.logger.log(msg)
77 else
78 parentRunner.logger.error(msg);
79 }
80 }
81
82
83 /**
84 * Makes a test report, returns it as a DIV element.
85 **/
86 SimpleTest.report = function () {
87 var DIV = MochiKit.DOM.DIV;
88 var passed = 0;
89 var failed = 0;
90 var todo = 0;
91 var results = MochiKit.Base.map(
92 function (test) {
93 var cls, msg;
94 if (test.todo && !test.result) {
95 todo++;
96 cls = "test_todo"
97 msg = "todo - " + test.name;
98 } else if (test.result &&!test.todo) {
99 passed++;
100 cls = "test_ok";
101 msg = "ok - " + test.name;
102 } else {
103 failed++;
104 cls = "test_not_ok";
105 msg = "not ok - " + test.name + " " + test.diag;
106 }
107 return DIV({"class": cls}, msg);
108 },
109 SimpleTest._tests
110 );
111 var summary_class = ((failed == 0) ? 'all_pass' : 'some_fail');
112 return DIV({'class': 'tests_report'},
113 DIV({'class': 'tests_summary ' + summary_class},
114 DIV({'class': 'tests_passed'}, "Passed: " + passed),
115 DIV({'class': 'tests_failed'}, "Failed: " + failed),
116 DIV({'class': 'tests_todo'}, "Todo: " + todo)),
117 results
118 );
119 };
120
121 /**
122 * Toggle element visibility
123 **/
124 SimpleTest.toggle = function(el) {
125 if (MochiKit.Style.computedStyle(el, 'display') == 'block') {
126 el.style.display = 'none';
127 } else {
128 el.style.display = 'block';
129 }
130 };
131
132 /**
133 * Toggle visibility for divs with a specific class.
134 **/
135 SimpleTest.toggleByClass = function (cls) {
136 var elems = getElementsByTagAndClassName('div', cls);
137 MochiKit.Base.map(SimpleTest.toggle, elems);
138 };
139
140 /**
141 * Shows the report in the browser
142 **/
143
144 SimpleTest.showReport = function() {
145 var togglePassed = A({'href': '#'}, "Toggle passed tests");
146 var toggleFailed = A({'href': '#'}, "Toggle failed tests");
147 togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
148 toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
149 var body = document.getElementsByTagName("body")[0];
150 var firstChild = body.childNodes[0];
151 var addNode;
152 if (firstChild) {
153 addNode = function (el) {
154 body.insertBefore(el, firstChild);
155 };
156 } else {
157 addNode = function (el) {
158 body.appendChild(el)
159 };
160 }
161 addNode(togglePassed);
162 addNode(SPAN(null, " "));
163 addNode(toggleFailed);
164 addNode(SimpleTest.report());
165 };
166
167 /**
168 * Tells SimpleTest to don't finish the test when the document is loaded,
169 * useful for asynchronous tests.
170 *
171 * When SimpleTest.waitForExplicitFinish is called,
172 * explicit SimpleTest.finish() is required.
173 **/
174 SimpleTest.waitForExplicitFinish = function () {
175 SimpleTest._stopOnLoad = false;
176 };
177
178 /**
179 * Talks to the TestRunner if being ran on a iframe and the parent has a
180 * TestRunner object.
181 **/
182 SimpleTest.talkToRunner = function () {
183 if (parentRunner) {
184 parentRunner.testFinished(document);
185 }
186 };
187
188 /**
189 * Finishes the tests. This is automatically called, except when
190 * SimpleTest.waitForExplicitFinish() has been invoked.
191 **/
192 SimpleTest.finish = function () {
193 SimpleTest.showReport();
194 SimpleTest.talkToRunner();
195 };
196
197
198 addLoadEvent(function() {
199 if (SimpleTest._stopOnLoad) {
200 SimpleTest.finish();
201 }
202 });
203
204 // --------------- Test.Builder/Test.More isDeeply() -----------------
205
206
207 SimpleTest.DNE = {dne: 'Does not exist'};
208 SimpleTest.LF = "\r\n";
209 SimpleTest._isRef = function (object) {
210 var type = typeof(object);
211 return type == 'object' || type == 'function';
212 };
213
214
215 SimpleTest._deepCheck = function (e1, e2, stack, seen) {
216 var ok = false;
217 // Either they're both references or both not.
218 var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
219 if (e1 == null && e2 == null) {
220 ok = true;
221 } else if (e1 != null ^ e2 != null) {
222 ok = false;
223 } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
224 ok = false;
225 } else if (sameRef && e1 == e2) {
226 // Handles primitives and any variables that reference the same
227 // object, including functions.
228 ok = true;
229 } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
230 ok = SimpleTest._eqArray(e1, e2, stack, seen);
231 } else if (typeof e1 == "object" && typeof e2 == "object") {
232 ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
233 } else {
234 // If we get here, they're not the same (function references must
235 // always simply rererence the same function).
236 stack.push({ vals: [e1, e2] });
237 ok = false;
238 }
239 return ok;
240 };
241
242 SimpleTest._eqArray = function (a1, a2, stack, seen) {
243 // Return if they're the same object.
244 if (a1 == a2) return true;
245
246 // JavaScript objects have no unique identifiers, so we have to store
247 // references to them all in an array, and then compare the references
248 // directly. It's slow, but probably won't be much of an issue in
249 // practice. Start by making a local copy of the array to as to avoid
250 // confusing a reference seen more than once (such as [a, a]) for a
251 // circular reference.
252 for (var j = 0; j < seen.length; j++) {
253 if (seen[j][0] == a1) {
254 return seen[j][1] == a2;
255 }
256 }
257
258 // If we get here, we haven't seen a1 before, so store it with reference
259 // to a2.
260 seen.push([ a1, a2 ]);
261
262 var ok = true;
263 // Only examines enumerable attributes. Only works for numeric arrays!
264 // Associative arrays return 0. So call _eqAssoc() for them, instead.
265 var max = a1.length > a2.length ? a1.length : a2.length;
266 if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen);
267 for (var i = 0; i < max; i++) {
268 var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i];
269 var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
270 stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
271 if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
272 stack.pop();
273 } else {
274 break;
275 }
276 }
277 return ok;
278 };
279
280 SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
281 // Return if they're the same object.
282 if (o1 == o2) return true;
283
284 // JavaScript objects have no unique identifiers, so we have to store
285 // references to them all in an array, and then compare the references
286 // directly. It's slow, but probably won't be much of an issue in
287 // practice. Start by making a local copy of the array to as to avoid
288 // confusing a reference seen more than once (such as [a, a]) for a
289 // circular reference.
290 seen = seen.slice(0);
291 for (var j = 0; j < seen.length; j++) {
292 if (seen[j][0] == o1) {
293 return seen[j][1] == o2;
294 }
295 }
296
297 // If we get here, we haven't seen o1 before, so store it with reference
298 // to o2.
299 seen.push([ o1, o2 ]);
300
301 // They should be of the same class.
302
303 var ok = true;
304 // Only examines enumerable attributes.
305 var o1Size = 0; for (var i in o1) o1Size++;
306 var o2Size = 0; for (var i in o2) o2Size++;
307 var bigger = o1Size > o2Size ? o1 : o2;
308 for (var i in bigger) {
309 var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i];
310 var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
311 stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
312 if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
313 stack.pop();
314 } else {
315 break;
316 }
317 }
318 return ok;
319 };
320
321 SimpleTest._formatStack = function (stack) {
322 var variable = '$Foo';
323 for (var i = 0; i < stack.length; i++) {
324 var entry = stack[i];
325 var type = entry['type'];
326 var idx = entry['idx'];
327 if (idx != null) {
328 if (/^\d+$/.test(idx)) {
329 // Numeric array index.
330 variable += '[' + idx + ']';
331 } else {
332 // Associative array index.
333 idx = idx.replace("'", "\\'");
334 variable += "['" + idx + "']";
335 }
336 }
337 }
338
339 var vals = stack[stack.length-1]['vals'].slice(0, 2);
340 var vars = [
341 variable.replace('$Foo', 'got'),
342 variable.replace('$Foo', 'expected')
343 ];
344
345 var out = "Structures begin differing at:" + SimpleTest.LF;
346 for (var i = 0; i < vals.length; i++) {
347 var val = vals[i];
348 if (val == null) {
349 val = 'undefined';
350 } else {
351 val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
352 }
353 }
354
355 out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
356 out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
357
358 return ' ' + out;
359 };
360
361
362 SimpleTest.isDeeply = function (it, as, name) {
363 var ok;
364 // ^ is the XOR operator.
365 if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
366 // One's a reference, one isn't.
367 ok = false;
368 } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
369 // Neither is an object.
370 ok = SimpleTest.is(it, as, name);
371 } else {
372 // We have two objects. Do a deep comparison.
373 var stack = [], seen = [];
374 if ( SimpleTest._deepCheck(it, as, stack, seen)) {
375 ok = SimpleTest.ok(true, name);
376 } else {
377 ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
378 }
379 }
380 return ok;
381 };
382
383 SimpleTest.typeOf = function (object) {
384 var c = Object.prototype.toString.apply(object);
385 var name = c.substring(8, c.length - 1);
386 if (name != 'Object') return name;
387 // It may be a non-core class. Try to extract the class name from
388 // the constructor function. This may not work in all implementations.
389 if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
390 return RegExp.$1;
391 }
392 // No idea. :-(
393 return name;
394 };
395
396 SimpleTest.isa = function (object, clas) {
397 return SimpleTest.typeOf(object) == clas;
398 };
399
400 // Global symbols:
401 var ok = SimpleTest.ok;
402 var is = SimpleTest.is;
403 var isnot = SimpleTest.isnot;
404 var todo = SimpleTest.todo;
405 var isDeeply = SimpleTest.isDeeply;
406 var oldOnError = window.onerror;
407 window.onerror = function (ev) {
408 is(0, 1, "Error thrown during test: " + ev);
409 if (oldOnError) {
410 try {
411 oldOnError(ev);
412 } catch (e) {
413 }
414 }
415 if (SimpleTest._stopOnLoad == false) {
416 // Need to finish() manually here
417 SimpleTest.finish();
418 }
419 }
This page was automatically generated by
LXR.