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