da94800c130d6dfcec759c9268828fe5be8608d1
[jquery.git] / test / lib / Test / More.js
1 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
2 // Create a namespace for ourselves.
3
4 // Set up package.
5 if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
6 else {
7     if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
8         throw new Error(
9             "You must load either JSAN or Test.Builder "
10             + "before loading Test.More"
11         );
12 }
13
14 Test.More = {};
15 Test.More.EXPORT = [
16     'plan',
17     'ok', 'is', 'isnt',
18     'like', 'unlike',
19     'cmpOK', 'canOK', 'isaOK',
20     'pass', 'fail', 'diag', 'loadOK',
21     'skip', 'todo', 'todoSkip', 'skipRest',
22     'isDeeply', 'isSet', 'isa'
23 ];
24 Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
25 Test.More.VERSION     = '0.11';
26
27 Test.More.ShowDiag = true;
28 Test.Builder.DNE = { dne: 'Does not exist' };
29 Test.More.Test = new Test.Builder();
30 Test.More.builder = function () { return Test.More.Test; };
31
32 Test.More.plan = function (cmds) {
33     if (cmds.noDiag) {
34         Test.More.ShowDiag = false;
35         delete cmds.noDiag;
36     }
37     return Test.More.Test.plan.apply(Test.More.Test, [cmds]);
38 };
39
40 Test.More.ok = function (test, desc) {
41     return Test.More.Test.ok(test, desc);
42 };
43
44 Test.More.is = function (got, expect, desc) {
45     return Test.More.Test.isEq(got, expect, desc);
46 };
47
48 Test.More.isnt = function (got, expect, desc) {
49     return Test.More.Test.isntEq(got, expect, desc);
50 };
51
52 Test.More.like = function (val, regex, desc) {
53     return Test.More.Test.like(val, regex, desc);
54 };
55
56 Test.More.unlike = function (val, regex, desc) {
57     return Test.More.Test.unlike(val, regex, desc);
58 };
59
60 Test.More.cmpOK = function (got, op, expect, desc) {
61     return Test.More.Test.cmpOK(got, op, expect, desc);
62 };
63
64 Test.More.canOK = function (proto) {
65     var ok;
66     // Make sure they passed some method names for us to check.
67     if (!arguments.length > 1) {
68         ok = Test.More.Test.ok(false, clas + '.can(...)');
69         Test.More.Test.diag('    canOK() called with no methods');
70         return ok;
71     }
72
73     // Get the class name and the prototype.
74     var clas;
75     if (typeof proto == 'string') {
76         // We just have a class name.
77         clas = proto;
78         proto = eval(clas + '.prototype');
79     } else {
80         // We have an object or something that can be converted to an object.
81         clas = Test.Builder.typeOf(proto);
82         proto = proto.constructor.prototype;
83     }
84
85     var nok = [];
86     for (var i = 1; i < arguments.length; i++) {
87         var method = arguments[i];
88         if (typeof proto[method] != 'function') nok.push(method);
89     }
90
91     // There'es no can() method in JavaScript, but what the hell!
92     var desc = clas + ".can('" + (arguments.length == 2 ? arguments[1] : '...') + "')";
93     ok = Test.More.Test.ok(!nok.length, desc);
94     for (var i = 0; i < nok.length; i++) {
95         Test.More.Test.diag('    ' + clas + ".can('" + nok[i] + "') failed");
96     }
97     return ok;
98 };
99
100 Test.More.isaOK = function (object, clas, objName) {
101     var mesg;
102     if (objName == null) objName = 'The object';
103     var name = objName + ' isa ' + clas;
104     if (object == null) {
105         mesg = objName + " isn't defined";
106     } else if (!Test.More._isRef(object)) {
107         mesg = objName + " isn't a reference";
108     } else {
109         var ctor = eval(clas);
110         if (Object.isPrototypeOf) {
111             // With JavaScript 1.5, we can determine inheritance.
112             if (!ctor.prototype.isPrototypeOf(object)) {
113                 mesg = objName + " isn't a '" + clas + "' it's a '"
114                   + Test.Builder.typeOf(object) + "'";
115             }
116         } else {
117             // We can just determine what constructor was used. This will
118             // not work for inherited constructors.
119             if (object.constructor != ctor)
120                 mesg = objName + " isn't a '" + clas + "' it's a '"
121                   + Test.Builder.typeOf(object) + '"';
122         }
123     }
124     
125     var ok;
126     if (mesg) {
127         ok = Test.More.Test.ok(false, name);
128         Test.More.Test.diag('    ' + mesg);
129     } else {
130         ok = Test.More.Test.ok(true, name);
131     }
132
133     return ok;
134 };
135
136 Test.More.pass = function (name) {
137     return Test.More.Test.ok(true, name);
138 };
139
140 Test.More.fail = function (name) {
141     return Test.More.Test.ok(false, name);
142 };
143
144 Test.More.diag = function () {
145     if (!Test.More.ShowDiag) return;
146     return Test.More.Test.diag.apply(Test.More.Test, arguments);
147 };
148
149 // Use this instead of use_ok and require_ok.
150 Test.More.loadOK = function () {
151     // XXX What do I do here? Eval?
152     // XXX Just always fail for now, to keep people from using it just yet.
153     return false;
154 };
155
156 Test.More.skip = function (why, howMany) {
157     if (howMany == null) {
158         if (!Test.Builder.NoPlan)
159             Test.More.Test.warn("skip() needs to know howMany tests are in the block");
160         howMany = 1;
161     }
162     for (i = 0; i < howMany; i++) {
163         Test.More.Test.skip(why);
164     }
165 };
166
167 Test.More.todo = function (why, howMany) {
168     if (howMany == null) {
169         if (!Test.Builder.NoPlan)
170             Test.More.Test.warn("todo() needs to know howMany tests are in the block");
171         howMany = 1;
172     }
173     return Test.More.Test.todo(why, howMany);
174 };
175
176 Test.More.todoSkip = function (why, howMany) {
177     if (howMany == null) {
178         if (!Test.Builder.NoPlan)
179             Test.More.Test.warn("todoSkip() needs to know howMany tests are in the block");
180         howMany = 1;
181     }
182
183     for (i = 0; i < howMany; i++) {
184         Test.More.Test.todoSkip(why);
185     }
186 };
187
188 Test.More.skipRest = function (why) {
189     Test.More.Test.skipRest(why);
190 };
191
192 Test.More.isDeeply = function (it, as, name) {
193     if (arguments.length != 2 && arguments.length != 3) {
194         Test.More.Test.warn(
195             'isDeeply() takes two or three args, you gave '
196             + arguments.length + "."
197         );
198     }
199
200     var ok;
201     // ^ is the XOR operator.
202     if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
203         // One's a reference, one isn't.
204         ok = false;
205     } else if (!Test.More._isRef(it) && !Test.More._isRef(as)) {
206         // Neither is an object.
207         ok = Test.More.Test.isEq(it, as, name);
208     } else {
209         // We have two objects. Do a deep comparison.
210         var stack = [], seen = [];
211         if ( Test.More._deepCheck(it, as, stack, seen)) {
212             ok = Test.More.Test.ok(true, name);
213         } else {
214             ok = Test.More.Test.ok(false, name);
215             Test.More.Test.diag(Test.More._formatStack(stack));
216         }
217     }
218     return ok;
219 };
220
221 Test.More._deepCheck = function (e1, e2, stack, seen) {
222     var ok = false;
223     // Either they're both references or both not.
224     var sameRef = !(!Test.More._isRef(e1) ^ !Test.More._isRef(e2));
225     if (e1 == null && e2 == null) {
226         ok = true;
227     } else if (e1 != null ^ e2 != null) {
228         ok = false;
229     } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
230         ok = false;
231     } else if (sameRef && e1 == e2) {
232         // Handles primitives and any variables that reference the same
233         // object, including functions.
234         ok = true;
235     } else if (isa(e1, 'Array') && isa(e2, 'Array')) {
236         ok = Test.More._eqArray(e1, e2, stack, seen);
237     } else if (typeof e1 == "object" && typeof e2 == "object") {
238         ok = Test.More._eqAssoc(e1, e2, stack, seen);
239     } else {
240         // If we get here, they're not the same (function references must
241         // always simply rererence the same function).
242         stack.push({ vals: [e1, e2] });
243         ok = false;
244     }
245     return ok;
246 };
247
248 Test.More._isRef = function (object) {
249     var type = typeof object;
250     return type == 'object' || type == 'function';
251 };
252
253 Test.More._formatStack = function (stack) {
254     var variable = '$Foo';
255     for (var i = 0; i < stack.length; i++) {
256         var entry = stack[i];
257         var type = entry['type'];
258         var idx = entry['idx'];
259         if (idx != null) {
260             if (/^\d+$/.test(idx)) {
261                 // Numeric array index.
262                 variable += '[' + idx + ']';
263             } else {
264                 // Associative array index.
265                 idx = idx.replace("'", "\\'");
266                 variable += "['" + idx + "']";
267             }
268         }
269     }
270
271     var vals = stack[stack.length-1]['vals'].slice(0, 2);
272     var vars = [
273         variable.replace('$Foo',     'got'),
274         variable.replace('$Foo',     'expected')
275     ];
276
277     var out = "Structures begin differing at:" + Test.Builder.LF;
278     for (var i = 0; i < vals.length; i++) {
279         var val = vals[i];
280         if (val == null) {
281             val = 'undefined';
282         } else {
283              val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
284         }
285     }
286
287     out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
288     out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
289     
290     return '    ' + out;
291 };
292
293 /* Commented out per suggestion from Michael Schwern. It turned out to be
294    confusing to Test::More users because it isn't atually a test. Use
295    isDeeply() instead and don't worry about it.
296
297 Test.More.eqArray = function (a1, a2) {
298     if (!isa(a1, 'Array') || !isa(a2, 'Array')) {
299         Test.More.Test.warn("Non-array passed to eqArray()");
300         return false;
301     }
302     return Test.More._eqArray(a1, a2, [], []);
303 };
304
305 */
306
307 Test.More._eqArray = function (a1, a2, stack, seen) {
308     // Return if they're the same object.
309     if (a1 == a2) return true;
310
311     // JavaScript objects have no unique identifiers, so we have to store
312     // references to them all in an array, and then compare the references
313     // directly. It's slow, but probably won't be much of an issue in
314     // practice. Start by making a local copy of the array to as to avoid
315     // confusing a reference seen more than once (such as [a, a]) for a
316     // circular reference.
317     for (var j = 0; j < seen.length; j++) {
318         if (seen[j][0] == a1) {
319             return seen[j][1] == a2;
320         }
321     }
322
323     // If we get here, we haven't seen a1 before, so store it with reference
324     // to a2.
325     seen.push([ a1, a2 ]);
326
327     var ok = true;
328     // Only examines enumerable attributes. Only works for numeric arrays!
329     // Associative arrays return 0. So call _eqAssoc() for them, instead.
330     var max = a1.length > a2.length ? a1.length : a2.length;
331     if (max == 0) return Test.More._eqAssoc(a1, a2, stack, seen);
332     for (var i = 0; i < max; i++) {
333         var e1 = i > a1.length - 1 ? Test.More.DNE : a1[i];
334         var e2 = i > a2.length - 1 ? Test.More.DNE : a2[i];
335         stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
336         if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
337             stack.pop();
338         } else {
339             break;
340         }
341     }
342     return ok;
343 };
344
345 /* Commented out per suggestion from Michael Schwern. It turned out to be
346    confusing to Test::More users because it isn't atually a test. Use
347    isDeeply() instead and don't worry about it.
348
349 Test.More.eqHash = function () {
350     return eqAssoc.apply(this, arguments);
351 };
352
353 Test.More.eqAssoc = function (o1, o2) {
354     if (typeof o1 != "object" || typeof o2 != "object") {
355         Test.More.Test.warn("Non-object passed to eqAssoc()");
356         return false;
357     } else if (   (isa(o1, 'Array') && o1.length > 0)
358                || (isa(o2, 'Array') && o2.length > 0))
359     {
360         Test.More.Test.warn("Ordered array passed to eqAssoc()");
361         return false;
362     }
363     return Test.More._eqAssoc(o1, o2, [], []);
364 };
365
366 */
367
368 Test.More._eqAssoc = function (o1, o2, stack, seen) {
369     // Return if they're the same object.
370     if (o1 == o2) return true;
371
372     // JavaScript objects have no unique identifiers, so we have to store
373     // references to them all in an array, and then compare the references
374     // directly. It's slow, but probably won't be much of an issue in
375     // practice. Start by making a local copy of the array to as to avoid
376     // confusing a reference seen more than once (such as [a, a]) for a
377     // circular reference.
378     seen = seen.slice(0);
379     for (var j = 0; j < seen.length; j++) {
380         if (seen[j][0] == o1) {
381             return seen[j][1] == o2;
382         }
383     }
384
385     // If we get here, we haven't seen o1 before, so store it with reference
386     // to o2.
387     seen.push([ o1, o2 ]);
388
389     // They should be of the same class.
390
391     var ok = true;
392     // Only examines enumerable attributes.
393     var o1Size = 0; for (var i in o1) o1Size++;
394     var o2Size = 0; for (var i in o2) o2Size++;
395     var bigger = o1Size > o2Size ? o1 : o2;
396     for (var i in bigger) {
397         var e1 = o1[i] == undefined ? Test.More.DNE : o1[i];
398         var e2 = o2[i] == undefined ? Test.More.DNE : o2[i];
399         stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
400         if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
401             stack.pop();
402         } else {
403             break;
404         }
405     }
406     return ok;
407 };
408
409 Test.More._eqSet = function (a1, a2, stack, seen) {
410     return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
411 };
412
413 Test.More.isSet = function (a1, a2, desc) {
414     var stack = [], seen = [], ok = true;
415     if (Test.More._eqSet(a1, a2, stack, seen)) {
416         ok = Test.More.Test.ok(true, desc);
417     } else {
418         ok = Test.More.Test.ok(false, desc);
419         Test.More.Test.diag(Test.More._formatStack(stack));
420     }
421     return ok;
422 };
423
424 Test.More.isa = function (object, clas) {
425     return Test.Builder.typeOf(object) == clas;
426 };
427
428 // Handle exporting.
429 if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.More);