lib/goog/testing/asserts.js

1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14goog.provide('goog.testing.JsUnitException');
15goog.provide('goog.testing.asserts');
16goog.provide('goog.testing.asserts.ArrayLike');
17
18goog.require('goog.testing.stacktrace');
19
20// TODO(user): Copied from JsUnit with some small modifications, we should
21// reimplement the asserters.
22
23
24/**
25 * @typedef {Array|NodeList|Arguments|{length: number}}
26 */
27goog.testing.asserts.ArrayLike;
28
29var DOUBLE_EQUALITY_PREDICATE = function(var1, var2) {
30 return var1 == var2;
31};
32var JSUNIT_UNDEFINED_VALUE;
33var TO_STRING_EQUALITY_PREDICATE = function(var1, var2) {
34 return var1.toString() === var2.toString();
35};
36
37var PRIMITIVE_EQUALITY_PREDICATES = {
38 'String': DOUBLE_EQUALITY_PREDICATE,
39 'Number': DOUBLE_EQUALITY_PREDICATE,
40 'Boolean': DOUBLE_EQUALITY_PREDICATE,
41 'Date': function(date1, date2) {
42 return date1.getTime() == date2.getTime();
43 },
44 'RegExp': TO_STRING_EQUALITY_PREDICATE,
45 'Function': TO_STRING_EQUALITY_PREDICATE
46};
47
48
49/**
50 * Compares equality of two numbers, allowing them to differ up to a given
51 * tolerance.
52 * @param {number} var1 A number.
53 * @param {number} var2 A number.
54 * @param {number} tolerance the maximum allowed difference.
55 * @return {boolean} Whether the two variables are sufficiently close.
56 * @private
57 */
58goog.testing.asserts.numberRoughEqualityPredicate_ = function(
59 var1, var2, tolerance) {
60 return Math.abs(var1 - var2) <= tolerance;
61};
62
63
64/**
65 * @type {Object<string, function(*, *, number): boolean>}
66 * @private
67 */
68goog.testing.asserts.primitiveRoughEqualityPredicates_ = {
69 'Number': goog.testing.asserts.numberRoughEqualityPredicate_
70};
71
72
73var _trueTypeOf = function(something) {
74 var result = typeof something;
75 try {
76 switch (result) {
77 case 'string':
78 break;
79 case 'boolean':
80 break;
81 case 'number':
82 break;
83 case 'object':
84 if (something == null) {
85 result = 'null';
86 break;
87 }
88 case 'function':
89 switch (something.constructor) {
90 case new String('').constructor:
91 result = 'String';
92 break;
93 case new Boolean(true).constructor:
94 result = 'Boolean';
95 break;
96 case new Number(0).constructor:
97 result = 'Number';
98 break;
99 case new Array().constructor:
100 result = 'Array';
101 break;
102 case new RegExp().constructor:
103 result = 'RegExp';
104 break;
105 case new Date().constructor:
106 result = 'Date';
107 break;
108 case Function:
109 result = 'Function';
110 break;
111 default:
112 var m = something.constructor.toString().match(
113 /function\s*([^( ]+)\(/);
114 if (m) {
115 result = m[1];
116 } else {
117 break;
118 }
119 }
120 break;
121 }
122 } catch (e) {
123
124 } finally {
125 result = result.substr(0, 1).toUpperCase() + result.substr(1);
126 }
127 return result;
128};
129
130var _displayStringForValue = function(aVar) {
131 var result;
132 try {
133 result = '<' + String(aVar) + '>';
134 } catch (ex) {
135 result = '<toString failed: ' + ex.message + '>';
136 // toString does not work on this object :-(
137 }
138 if (!(aVar === null || aVar === JSUNIT_UNDEFINED_VALUE)) {
139 result += ' (' + _trueTypeOf(aVar) + ')';
140 }
141 return result;
142};
143
144var fail = function(failureMessage) {
145 goog.testing.asserts.raiseException('Call to fail()', failureMessage);
146};
147
148var argumentsIncludeComments = function(expectedNumberOfNonCommentArgs, args) {
149 return args.length == expectedNumberOfNonCommentArgs + 1;
150};
151
152var commentArg = function(expectedNumberOfNonCommentArgs, args) {
153 if (argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)) {
154 return args[0];
155 }
156
157 return null;
158};
159
160var nonCommentArg = function(desiredNonCommentArgIndex,
161 expectedNumberOfNonCommentArgs, args) {
162 return argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) ?
163 args[desiredNonCommentArgIndex] :
164 args[desiredNonCommentArgIndex - 1];
165};
166
167var _validateArguments = function(expectedNumberOfNonCommentArgs, args) {
168 var valid = args.length == expectedNumberOfNonCommentArgs ||
169 args.length == expectedNumberOfNonCommentArgs + 1 &&
170 goog.isString(args[0]);
171 _assert(null, valid, 'Incorrect arguments passed to assert function');
172};
173
174var _assert = function(comment, booleanValue, failureMessage) {
175 if (!booleanValue) {
176 goog.testing.asserts.raiseException(comment, failureMessage);
177 }
178};
179
180
181/**
182 * @param {*} expected The expected value.
183 * @param {*} actual The actual value.
184 * @return {string} A failure message of the values don't match.
185 * @private
186 */
187goog.testing.asserts.getDefaultErrorMsg_ = function(expected, actual) {
188 var msg = 'Expected ' + _displayStringForValue(expected) + ' but was ' +
189 _displayStringForValue(actual);
190 if ((typeof expected == 'string') && (typeof actual == 'string')) {
191 // Try to find a human-readable difference.
192 var limit = Math.min(expected.length, actual.length);
193 var commonPrefix = 0;
194 while (commonPrefix < limit &&
195 expected.charAt(commonPrefix) == actual.charAt(commonPrefix)) {
196 commonPrefix++;
197 }
198
199 var commonSuffix = 0;
200 while (commonSuffix < limit &&
201 expected.charAt(expected.length - commonSuffix - 1) ==
202 actual.charAt(actual.length - commonSuffix - 1)) {
203 commonSuffix++;
204 }
205
206 if (commonPrefix + commonSuffix > limit) {
207 commonSuffix = 0;
208 }
209
210 if (commonPrefix > 2 || commonSuffix > 2) {
211 var printString = function(str) {
212 var startIndex = Math.max(0, commonPrefix - 2);
213 var endIndex = Math.min(str.length, str.length - (commonSuffix - 2));
214 return (startIndex > 0 ? '...' : '') +
215 str.substring(startIndex, endIndex) +
216 (endIndex < str.length ? '...' : '');
217 };
218
219 msg += '\nDifference was at position ' + commonPrefix +
220 '. Expected [' + printString(expected) +
221 '] vs. actual [' + printString(actual) + ']';
222 }
223 }
224 return msg;
225};
226
227
228/**
229 * @param {*} a The value to assert (1 arg) or debug message (2 args).
230 * @param {*=} opt_b The value to assert (2 args only).
231 */
232var assert = function(a, opt_b) {
233 _validateArguments(1, arguments);
234 var comment = commentArg(1, arguments);
235 var booleanValue = nonCommentArg(1, 1, arguments);
236
237 _assert(comment, goog.isBoolean(booleanValue),
238 'Bad argument to assert(boolean)');
239 _assert(comment, booleanValue, 'Call to assert(boolean) with false');
240};
241
242
243/**
244 * Asserts that the function throws an error.
245 *
246 * @param {!(string|Function)} a The assertion comment or the function to call.
247 * @param {!Function=} opt_b The function to call (if the first argument of
248 * {@code assertThrows} was the comment).
249 * @return {*} The error thrown by the function.
250 * @throws {goog.testing.JsUnitException} If the assertion failed.
251 */
252var assertThrows = function(a, opt_b) {
253 _validateArguments(1, arguments);
254 var func = nonCommentArg(1, 1, arguments);
255 var comment = commentArg(1, arguments);
256 _assert(comment, typeof func == 'function',
257 'Argument passed to assertThrows is not a function');
258
259 try {
260 func();
261 } catch (e) {
262 if (e && goog.isString(e['stacktrace']) && goog.isString(e['message'])) {
263 // Remove the stack trace appended to the error message by Opera 10.0
264 var startIndex = e['message'].length - e['stacktrace'].length;
265 if (e['message'].indexOf(e['stacktrace'], startIndex) == startIndex) {
266 e['message'] = e['message'].substr(0, startIndex - 14);
267 }
268 }
269 return e;
270 }
271 goog.testing.asserts.raiseException(comment,
272 'No exception thrown from function passed to assertThrows');
273};
274
275
276/**
277 * Asserts that the function does not throw an error.
278 *
279 * @param {!(string|Function)} a The assertion comment or the function to call.
280 * @param {!Function=} opt_b The function to call (if the first argument of
281 * {@code assertNotThrows} was the comment).
282 * @return {*} The return value of the function.
283 * @throws {goog.testing.JsUnitException} If the assertion failed.
284 */
285var assertNotThrows = function(a, opt_b) {
286 _validateArguments(1, arguments);
287 var comment = commentArg(1, arguments);
288 var func = nonCommentArg(1, 1, arguments);
289 _assert(comment, typeof func == 'function',
290 'Argument passed to assertNotThrows is not a function');
291
292 try {
293 return func();
294 } catch (e) {
295 comment = comment ? (comment + '\n') : '';
296 comment += 'A non expected exception was thrown from function passed to ' +
297 'assertNotThrows';
298 // Some browsers don't have a stack trace so at least have the error
299 // description.
300 var stackTrace = e['stack'] || e['stacktrace'] || e.toString();
301 goog.testing.asserts.raiseException(comment, stackTrace);
302 }
303};
304
305
306/**
307 * Asserts that the given callback function results in a JsUnitException when
308 * called, and that the resulting failure message matches the given expected
309 * message.
310 * @param {function() : void} callback Function to be run expected to result
311 * in a JsUnitException (usually contains a call to an assert).
312 * @param {string=} opt_expectedMessage Failure message expected to be given
313 * with the exception.
314 */
315var assertThrowsJsUnitException = function(callback, opt_expectedMessage) {
316 var failed = false;
317 try {
318 goog.testing.asserts.callWithoutLogging(callback);
319 } catch (ex) {
320 if (!ex.isJsUnitException) {
321 fail('Expected a JsUnitException');
322 }
323 if (typeof opt_expectedMessage != 'undefined' &&
324 ex.message != opt_expectedMessage) {
325 fail('Expected message [' + opt_expectedMessage + '] but got [' +
326 ex.message + ']');
327 }
328 failed = true;
329 }
330 if (!failed) {
331 fail('Expected a failure: ' + opt_expectedMessage);
332 }
333};
334
335
336/**
337 * @param {*} a The value to assert (1 arg) or debug message (2 args).
338 * @param {*=} opt_b The value to assert (2 args only).
339 */
340var assertTrue = function(a, opt_b) {
341 _validateArguments(1, arguments);
342 var comment = commentArg(1, arguments);
343 var booleanValue = nonCommentArg(1, 1, arguments);
344
345 _assert(comment, goog.isBoolean(booleanValue),
346 'Bad argument to assertTrue(boolean)');
347 _assert(comment, booleanValue, 'Call to assertTrue(boolean) with false');
348};
349
350
351/**
352 * @param {*} a The value to assert (1 arg) or debug message (2 args).
353 * @param {*=} opt_b The value to assert (2 args only).
354 */
355var assertFalse = function(a, opt_b) {
356 _validateArguments(1, arguments);
357 var comment = commentArg(1, arguments);
358 var booleanValue = nonCommentArg(1, 1, arguments);
359
360 _assert(comment, goog.isBoolean(booleanValue),
361 'Bad argument to assertFalse(boolean)');
362 _assert(comment, !booleanValue, 'Call to assertFalse(boolean) with true');
363};
364
365
366/**
367 * @param {*} a The expected value (2 args) or the debug message (3 args).
368 * @param {*} b The actual value (2 args) or the expected value (3 args).
369 * @param {*=} opt_c The actual value (3 args only).
370 */
371var assertEquals = function(a, b, opt_c) {
372 _validateArguments(2, arguments);
373 var var1 = nonCommentArg(1, 2, arguments);
374 var var2 = nonCommentArg(2, 2, arguments);
375 _assert(commentArg(2, arguments), var1 === var2,
376 goog.testing.asserts.getDefaultErrorMsg_(var1, var2));
377};
378
379
380/**
381 * @param {*} a The expected value (2 args) or the debug message (3 args).
382 * @param {*} b The actual value (2 args) or the expected value (3 args).
383 * @param {*=} opt_c The actual value (3 args only).
384 */
385var assertNotEquals = function(a, b, opt_c) {
386 _validateArguments(2, arguments);
387 var var1 = nonCommentArg(1, 2, arguments);
388 var var2 = nonCommentArg(2, 2, arguments);
389 _assert(commentArg(2, arguments), var1 !== var2,
390 'Expected not to be ' + _displayStringForValue(var2));
391};
392
393
394/**
395 * @param {*} a The value to assert (1 arg) or debug message (2 args).
396 * @param {*=} opt_b The value to assert (2 args only).
397 */
398var assertNull = function(a, opt_b) {
399 _validateArguments(1, arguments);
400 var aVar = nonCommentArg(1, 1, arguments);
401 _assert(commentArg(1, arguments), aVar === null,
402 goog.testing.asserts.getDefaultErrorMsg_(null, aVar));
403};
404
405
406/**
407 * @param {*} a The value to assert (1 arg) or debug message (2 args).
408 * @param {*=} opt_b The value to assert (2 args only).
409 */
410var assertNotNull = function(a, opt_b) {
411 _validateArguments(1, arguments);
412 var aVar = nonCommentArg(1, 1, arguments);
413 _assert(commentArg(1, arguments), aVar !== null,
414 'Expected not to be ' + _displayStringForValue(null));
415};
416
417
418/**
419 * @param {*} a The value to assert (1 arg) or debug message (2 args).
420 * @param {*=} opt_b The value to assert (2 args only).
421 */
422var assertUndefined = function(a, opt_b) {
423 _validateArguments(1, arguments);
424 var aVar = nonCommentArg(1, 1, arguments);
425 _assert(commentArg(1, arguments), aVar === JSUNIT_UNDEFINED_VALUE,
426 goog.testing.asserts.getDefaultErrorMsg_(JSUNIT_UNDEFINED_VALUE, aVar));
427};
428
429
430/**
431 * @param {*} a The value to assert (1 arg) or debug message (2 args).
432 * @param {*=} opt_b The value to assert (2 args only).
433 */
434var assertNotUndefined = function(a, opt_b) {
435 _validateArguments(1, arguments);
436 var aVar = nonCommentArg(1, 1, arguments);
437 _assert(commentArg(1, arguments), aVar !== JSUNIT_UNDEFINED_VALUE,
438 'Expected not to be ' + _displayStringForValue(JSUNIT_UNDEFINED_VALUE));
439};
440
441
442/**
443 * @param {*} a The value to assert (1 arg) or debug message (2 args).
444 * @param {*=} opt_b The value to assert (2 args only).
445 */
446var assertNotNullNorUndefined = function(a, opt_b) {
447 _validateArguments(1, arguments);
448 assertNotNull.apply(null, arguments);
449 assertNotUndefined.apply(null, arguments);
450};
451
452
453/**
454 * @param {*} a The value to assert (1 arg) or debug message (2 args).
455 * @param {*=} opt_b The value to assert (2 args only).
456 */
457var assertNonEmptyString = function(a, opt_b) {
458 _validateArguments(1, arguments);
459 var aVar = nonCommentArg(1, 1, arguments);
460 _assert(commentArg(1, arguments),
461 aVar !== JSUNIT_UNDEFINED_VALUE && aVar !== null &&
462 typeof aVar == 'string' && aVar !== '',
463 'Expected non-empty string but was ' + _displayStringForValue(aVar));
464};
465
466
467/**
468 * @param {*} a The value to assert (1 arg) or debug message (2 args).
469 * @param {*=} opt_b The value to assert (2 args only).
470 */
471var assertNaN = function(a, opt_b) {
472 _validateArguments(1, arguments);
473 var aVar = nonCommentArg(1, 1, arguments);
474 _assert(commentArg(1, arguments), isNaN(aVar), 'Expected NaN');
475};
476
477
478/**
479 * @param {*} a The value to assert (1 arg) or debug message (2 args).
480 * @param {*=} opt_b The value to assert (2 args only).
481 */
482var assertNotNaN = function(a, opt_b) {
483 _validateArguments(1, arguments);
484 var aVar = nonCommentArg(1, 1, arguments);
485 _assert(commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN');
486};
487
488
489/**
490 * Runs a function in an environment where test failures are not logged. This is
491 * useful for testing test code, where failures can be a normal part of a test.
492 * @param {function() : void} fn Function to run without logging failures.
493 */
494goog.testing.asserts.callWithoutLogging = function(fn) {
495 var testRunner = goog.global['G_testRunner'];
496 var oldLogTestFailure = testRunner['logTestFailure'];
497 try {
498 // Any failures in the callback shouldn't be recorded.
499 testRunner['logTestFailure'] = undefined;
500 fn();
501 } finally {
502 testRunner['logTestFailure'] = oldLogTestFailure;
503 }
504};
505
506
507/**
508 * The return value of the equality predicate passed to findDifferences below,
509 * in cases where the predicate can't test the input variables for equality.
510 * @type {?string}
511 */
512goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS = null;
513
514
515/**
516 * The return value of the equality predicate passed to findDifferences below,
517 * in cases where the input vriables are equal.
518 * @type {?string}
519 */
520goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL = '';
521
522
523/**
524 * Determines if two items of any type match, and formulates an error message
525 * if not.
526 * @param {*} expected Expected argument to match.
527 * @param {*} actual Argument as a result of performing the test.
528 * @param {(function(string, *, *): ?string)=} opt_equalityPredicate An optional
529 * function that can be used to check equality of variables. It accepts 3
530 * arguments: type-of-variables, var1, var2 (in that order) and returns an
531 * error message if the variables are not equal,
532 * goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL if the variables
533 * are equal, or
534 * goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS if the predicate
535 * couldn't check the input variables. The function will be called only if
536 * the types of var1 and var2 are identical.
537 * @return {?string} Null on success, error message on failure.
538 */
539goog.testing.asserts.findDifferences = function(expected, actual,
540 opt_equalityPredicate) {
541 var failures = [];
542 var seen1 = [];
543 var seen2 = [];
544
545 // To avoid infinite recursion when the two parameters are self-referential
546 // along the same path of properties, keep track of the object pairs already
547 // seen in this call subtree, and abort when a cycle is detected.
548 function innerAssertWithCycleCheck(var1, var2, path) {
549 // This is used for testing, so we can afford to be slow (but more
550 // accurate). So we just check whether var1 is in seen1. If we
551 // found var1 in index i, we simply need to check whether var2 is
552 // in seen2[i]. If it is, we do not recurse to check var1/var2. If
553 // it isn't, we know that the structures of the two objects must be
554 // different.
555 //
556 // This is based on the fact that values at index i in seen1 and
557 // seen2 will be checked for equality eventually (when
558 // innerAssertImplementation(seen1[i], seen2[i], path) finishes).
559 for (var i = 0; i < seen1.length; ++i) {
560 var match1 = seen1[i] === var1;
561 var match2 = seen2[i] === var2;
562 if (match1 || match2) {
563 if (!match1 || !match2) {
564 // Asymmetric cycles, so the objects have different structure.
565 failures.push('Asymmetric cycle detected at ' + path);
566 }
567 return;
568 }
569 }
570
571 seen1.push(var1);
572 seen2.push(var2);
573 innerAssertImplementation(var1, var2, path);
574 seen1.pop();
575 seen2.pop();
576 }
577
578 var equalityPredicate = opt_equalityPredicate || function(type, var1, var2) {
579 var typedPredicate = PRIMITIVE_EQUALITY_PREDICATES[type];
580 if (!typedPredicate) {
581 return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS;
582 }
583 var equal = typedPredicate(var1, var2);
584 return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL :
585 goog.testing.asserts.getDefaultErrorMsg_(var1, var2);
586 };
587
588 /**
589 * @param {*} var1 An item in the expected object.
590 * @param {*} var2 The corresponding item in the actual object.
591 * @param {string} path Their path in the objects.
592 * @suppress {missingProperties} The map_ property is unknown to the compiler
593 * unless goog.structs.Map is loaded.
594 */
595 function innerAssertImplementation(var1, var2, path) {
596 if (var1 === var2) {
597 return;
598 }
599
600 var typeOfVar1 = _trueTypeOf(var1);
601 var typeOfVar2 = _trueTypeOf(var2);
602
603 if (typeOfVar1 == typeOfVar2) {
604 var isArray = typeOfVar1 == 'Array';
605 var errorMessage = equalityPredicate(typeOfVar1, var1, var2);
606 if (errorMessage !=
607 goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS) {
608 if (errorMessage !=
609 goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL) {
610 failures.push(path + ': ' + errorMessage);
611 }
612 } else if (isArray && var1.length != var2.length) {
613 failures.push(path + ': Expected ' + var1.length + '-element array ' +
614 'but got a ' + var2.length + '-element array');
615 } else {
616 var childPath = path + (isArray ? '[%s]' : (path ? '.%s' : '%s'));
617
618 // if an object has an __iterator__ property, we have no way of
619 // actually inspecting its raw properties, and JS 1.7 doesn't
620 // overload [] to make it possible for someone to generically
621 // use what the iterator returns to compare the object-managed
622 // properties. This gets us into deep poo with things like
623 // goog.structs.Map, at least on systems that support iteration.
624 if (!var1['__iterator__']) {
625 for (var prop in var1) {
626 if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) {
627 // Skip array indices for now. We'll handle them later.
628 continue;
629 }
630
631 if (prop in var2) {
632 innerAssertWithCycleCheck(var1[prop], var2[prop],
633 childPath.replace('%s', prop));
634 } else {
635 failures.push('property ' + prop +
636 ' not present in actual ' + (path || typeOfVar2));
637 }
638 }
639 // make sure there aren't properties in var2 that are missing
640 // from var1. if there are, then by definition they don't
641 // match.
642 for (var prop in var2) {
643 if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) {
644 // Skip array indices for now. We'll handle them later.
645 continue;
646 }
647
648 if (!(prop in var1)) {
649 failures.push('property ' + prop +
650 ' not present in expected ' +
651 (path || typeOfVar1));
652 }
653 }
654
655 // Handle array indices by iterating from 0 to arr.length.
656 //
657 // Although all browsers allow holes in arrays, browsers
658 // are inconsistent in what they consider a hole. For example,
659 // "[0,undefined,2]" has a hole on IE but not on Firefox.
660 //
661 // Because our style guide bans for...in iteration over arrays,
662 // we assume that most users don't care about holes in arrays,
663 // and that it is ok to say that a hole is equivalent to a slot
664 // populated with 'undefined'.
665 if (isArray) {
666 for (prop = 0; prop < var1.length; prop++) {
667 innerAssertWithCycleCheck(var1[prop], var2[prop],
668 childPath.replace('%s', String(prop)));
669 }
670 }
671 } else {
672 // special-case for closure objects that have iterators
673 if (goog.isFunction(var1.equals)) {
674 // use the object's own equals function, assuming it accepts an
675 // object and returns a boolean
676 if (!var1.equals(var2)) {
677 failures.push('equals() returned false for ' +
678 (path || typeOfVar1));
679 }
680 } else if (var1.map_) {
681 // assume goog.structs.Map or goog.structs.Set, where comparing
682 // their private map_ field is sufficient
683 innerAssertWithCycleCheck(var1.map_, var2.map_,
684 childPath.replace('%s', 'map_'));
685 } else {
686 // else die, so user knows we can't do anything
687 failures.push('unable to check ' + (path || typeOfVar1) +
688 ' for equality: it has an iterator we do not ' +
689 'know how to handle. please add an equals method');
690 }
691 }
692 }
693 } else {
694 failures.push(path + ' ' +
695 goog.testing.asserts.getDefaultErrorMsg_(var1, var2));
696 }
697 }
698
699 innerAssertWithCycleCheck(expected, actual, '');
700 return failures.length == 0 ? null :
701 goog.testing.asserts.getDefaultErrorMsg_(expected, actual) +
702 '\n ' + failures.join('\n ');
703};
704
705
706/**
707 * Notes:
708 * Object equality has some nasty browser quirks, and this implementation is
709 * not 100% correct. For example,
710 *
711 * <code>
712 * var a = [0, 1, 2];
713 * var b = [0, 1, 2];
714 * delete a[1];
715 * b[1] = undefined;
716 * assertObjectEquals(a, b); // should fail, but currently passes
717 * </code>
718 *
719 * See asserts_test.html for more interesting edge cases.
720 *
721 * The first comparison object provided is the expected value, the second is
722 * the actual.
723 *
724 * @param {*} a Assertion message or comparison object.
725 * @param {*} b Comparison object.
726 * @param {*=} opt_c Comparison object, if an assertion message was provided.
727 */
728var assertObjectEquals = function(a, b, opt_c) {
729 _validateArguments(2, arguments);
730 var v1 = nonCommentArg(1, 2, arguments);
731 var v2 = nonCommentArg(2, 2, arguments);
732 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
733 var differences = goog.testing.asserts.findDifferences(v1, v2);
734
735 _assert(failureMessage, !differences, differences);
736};
737
738
739/**
740 * Similar to assertObjectEquals above, but accepts a tolerance margin.
741 *
742 * @param {*} a Assertion message or comparison object.
743 * @param {*} b Comparison object.
744 * @param {*} c Comparison object or tolerance.
745 * @param {*=} opt_d Tolerance, if an assertion message was provided.
746 */
747var assertObjectRoughlyEquals = function(a, b, c, opt_d) {
748 _validateArguments(3, arguments);
749 var v1 = nonCommentArg(1, 3, arguments);
750 var v2 = nonCommentArg(2, 3, arguments);
751 var tolerance = nonCommentArg(3, 3, arguments);
752 var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : '';
753 var equalityPredicate = function(type, var1, var2) {
754 var typedPredicate =
755 goog.testing.asserts.primitiveRoughEqualityPredicates_[type];
756 if (!typedPredicate) {
757 return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS;
758 }
759 var equal = typedPredicate(var1, var2, tolerance);
760 return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL :
761 goog.testing.asserts.getDefaultErrorMsg_(var1, var2) +
762 ' which was more than ' + tolerance + ' away';
763 };
764 var differences = goog.testing.asserts.findDifferences(
765 v1, v2, equalityPredicate);
766
767 _assert(failureMessage, !differences, differences);
768};
769
770
771/**
772 * Compares two arbitrary objects for non-equalness.
773 *
774 * All the same caveats as for assertObjectEquals apply here:
775 * Undefined values may be confused for missing values, or vice versa.
776 *
777 * @param {*} a Assertion message or comparison object.
778 * @param {*} b Comparison object.
779 * @param {*=} opt_c Comparison object, if an assertion message was provided.
780 */
781var assertObjectNotEquals = function(a, b, opt_c) {
782 _validateArguments(2, arguments);
783 var v1 = nonCommentArg(1, 2, arguments);
784 var v2 = nonCommentArg(2, 2, arguments);
785 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
786 var differences = goog.testing.asserts.findDifferences(v1, v2);
787
788 _assert(failureMessage, differences, 'Objects should not be equal');
789};
790
791
792/**
793 * Compares two arrays ignoring negative indexes and extra properties on the
794 * array objects. Use case: Internet Explorer adds the index, lastIndex and
795 * input enumerable fields to the result of string.match(/regexp/g), which makes
796 * assertObjectEquals fail.
797 * @param {*} a The expected array (2 args) or the debug message (3 args).
798 * @param {*} b The actual array (2 args) or the expected array (3 args).
799 * @param {*=} opt_c The actual array (3 args only).
800 */
801var assertArrayEquals = function(a, b, opt_c) {
802 _validateArguments(2, arguments);
803 var v1 = nonCommentArg(1, 2, arguments);
804 var v2 = nonCommentArg(2, 2, arguments);
805 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
806
807 var typeOfVar1 = _trueTypeOf(v1);
808 _assert(failureMessage,
809 typeOfVar1 == 'Array',
810 'Expected an array for assertArrayEquals but found a ' + typeOfVar1);
811
812 var typeOfVar2 = _trueTypeOf(v2);
813 _assert(failureMessage,
814 typeOfVar2 == 'Array',
815 'Expected an array for assertArrayEquals but found a ' + typeOfVar2);
816
817 assertObjectEquals(failureMessage,
818 Array.prototype.concat.call(v1), Array.prototype.concat.call(v2));
819};
820
821
822/**
823 * Compares two objects that can be accessed like an array and assert that
824 * each element is equal.
825 * @param {string|Object} a Failure message (3 arguments)
826 * or object #1 (2 arguments).
827 * @param {Object} b Object #1 (2 arguments) or object #2 (3 arguments).
828 * @param {Object=} opt_c Object #2 (3 arguments).
829 */
830var assertElementsEquals = function(a, b, opt_c) {
831 _validateArguments(2, arguments);
832
833 var v1 = nonCommentArg(1, 2, arguments);
834 var v2 = nonCommentArg(2, 2, arguments);
835 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
836
837 if (!v1) {
838 assert(failureMessage, !v2);
839 } else {
840 assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length);
841 for (var i = 0; i < v1.length; ++i) {
842 assertEquals(
843 'mismatch at index ' + i + ': ' + failureMessage, v1[i], v2[i]);
844 }
845 }
846};
847
848
849/**
850 * Compares two objects that can be accessed like an array and assert that
851 * each element is roughly equal.
852 * @param {string|Object} a Failure message (4 arguments)
853 * or object #1 (3 arguments).
854 * @param {Object} b Object #1 (4 arguments) or object #2 (3 arguments).
855 * @param {Object|number} c Object #2 (4 arguments) or tolerance (3 arguments).
856 * @param {number=} opt_d tolerance (4 arguments).
857 */
858var assertElementsRoughlyEqual = function(a, b, c, opt_d) {
859 _validateArguments(3, arguments);
860
861 var v1 = nonCommentArg(1, 3, arguments);
862 var v2 = nonCommentArg(2, 3, arguments);
863 var tolerance = nonCommentArg(3, 3, arguments);
864 var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : '';
865
866 if (!v1) {
867 assert(failureMessage, !v2);
868 } else {
869 assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length);
870 for (var i = 0; i < v1.length; ++i) {
871 assertRoughlyEquals(failureMessage, v1[i], v2[i], tolerance);
872 }
873 }
874};
875
876
877/**
878 * Compares two array-like objects without taking their order into account.
879 * @param {string|goog.testing.asserts.ArrayLike} a Assertion message or the
880 * expected elements.
881 * @param {goog.testing.asserts.ArrayLike} b Expected elements or the actual
882 * elements.
883 * @param {goog.testing.asserts.ArrayLike=} opt_c Actual elements.
884 */
885var assertSameElements = function(a, b, opt_c) {
886 _validateArguments(2, arguments);
887 var expected = nonCommentArg(1, 2, arguments);
888 var actual = nonCommentArg(2, 2, arguments);
889 var message = commentArg(2, arguments);
890
891 assertTrue('Bad arguments to assertSameElements(opt_message, expected: ' +
892 'ArrayLike, actual: ArrayLike)',
893 goog.isArrayLike(expected) && goog.isArrayLike(actual));
894
895 // Clones expected and actual and converts them to real arrays.
896 expected = goog.testing.asserts.toArray_(expected);
897 actual = goog.testing.asserts.toArray_(actual);
898 // TODO(user): It would be great to show only the difference
899 // between the expected and actual elements.
900 _assert(message, expected.length == actual.length,
901 'Expected ' + expected.length + ' elements: [' + expected + '], ' +
902 'got ' + actual.length + ' elements: [' + actual + ']');
903
904 var toFind = goog.testing.asserts.toArray_(expected);
905 for (var i = 0; i < actual.length; i++) {
906 var index = goog.testing.asserts.indexOf_(toFind, actual[i]);
907 _assert(message, index != -1, 'Expected [' + expected + '], got [' +
908 actual + ']');
909 toFind.splice(index, 1);
910 }
911};
912
913
914/**
915 * @param {*} a The value to assert (1 arg) or debug message (2 args).
916 * @param {*=} opt_b The value to assert (2 args only).
917 */
918var assertEvaluatesToTrue = function(a, opt_b) {
919 _validateArguments(1, arguments);
920 var value = nonCommentArg(1, 1, arguments);
921 if (!value) {
922 _assert(commentArg(1, arguments), false, 'Expected to evaluate to true');
923 }
924};
925
926
927/**
928 * @param {*} a The value to assert (1 arg) or debug message (2 args).
929 * @param {*=} opt_b The value to assert (2 args only).
930 */
931var assertEvaluatesToFalse = function(a, opt_b) {
932 _validateArguments(1, arguments);
933 var value = nonCommentArg(1, 1, arguments);
934 if (value) {
935 _assert(commentArg(1, arguments), false, 'Expected to evaluate to false');
936 }
937};
938
939
940/**
941 * Compares two HTML snippets.
942 *
943 * Take extra care if attributes are involved. {@code assertHTMLEquals}'s
944 * implementation isn't prepared for complex cases. For example, the following
945 * comparisons erroneously fail:
946 * <pre>
947 * assertHTMLEquals('<a href="x" target="y">', '<a target="y" href="x">');
948 * assertHTMLEquals('<div classname="a b">', '<div classname="b a">');
949 * assertHTMLEquals('<input disabled>', '<input disabled="disabled">');
950 * </pre>
951 *
952 * When in doubt, use {@code goog.testing.dom.assertHtmlMatches}.
953 *
954 * @param {*} a The expected value (2 args) or the debug message (3 args).
955 * @param {*} b The actual value (2 args) or the expected value (3 args).
956 * @param {*=} opt_c The actual value (3 args only).
957 */
958var assertHTMLEquals = function(a, b, opt_c) {
959 _validateArguments(2, arguments);
960 var var1 = nonCommentArg(1, 2, arguments);
961 var var2 = nonCommentArg(2, 2, arguments);
962 var var1Standardized = standardizeHTML(var1);
963 var var2Standardized = standardizeHTML(var2);
964
965 _assert(commentArg(2, arguments), var1Standardized === var2Standardized,
966 goog.testing.asserts.getDefaultErrorMsg_(
967 var1Standardized, var2Standardized));
968};
969
970
971/**
972 * Compares two CSS property values to make sure that they represent the same
973 * things. This will normalize values in the browser. For example, in Firefox,
974 * this assertion will consider "rgb(0, 0, 255)" and "#0000ff" to be identical
975 * values for the "color" property. This function won't normalize everything --
976 * for example, in most browsers, "blue" will not match "#0000ff". It is
977 * intended only to compensate for unexpected normalizations performed by
978 * the browser that should also affect your expected value.
979 * @param {string} a Assertion message, or the CSS property name.
980 * @param {string} b CSS property name, or the expected value.
981 * @param {string} c The expected value, or the actual value.
982 * @param {string=} opt_d The actual value.
983 */
984var assertCSSValueEquals = function(a, b, c, opt_d) {
985 _validateArguments(3, arguments);
986 var propertyName = nonCommentArg(1, 3, arguments);
987 var expectedValue = nonCommentArg(2, 3, arguments);
988 var actualValue = nonCommentArg(3, 3, arguments);
989 var expectedValueStandardized =
990 standardizeCSSValue(propertyName, expectedValue);
991 var actualValueStandardized =
992 standardizeCSSValue(propertyName, actualValue);
993
994 _assert(commentArg(3, arguments),
995 expectedValueStandardized == actualValueStandardized,
996 goog.testing.asserts.getDefaultErrorMsg_(
997 expectedValueStandardized, actualValueStandardized));
998};
999
1000
1001/**
1002 * @param {*} a The expected value (2 args) or the debug message (3 args).
1003 * @param {*} b The actual value (2 args) or the expected value (3 args).
1004 * @param {*=} opt_c The actual value (3 args only).
1005 */
1006var assertHashEquals = function(a, b, opt_c) {
1007 _validateArguments(2, arguments);
1008 var var1 = nonCommentArg(1, 2, arguments);
1009 var var2 = nonCommentArg(2, 2, arguments);
1010 var message = commentArg(2, arguments);
1011 for (var key in var1) {
1012 _assert(message,
1013 key in var2, 'Expected hash had key ' + key + ' that was not found');
1014 _assert(message, var1[key] == var2[key], 'Value for key ' + key +
1015 ' mismatch - expected = ' + var1[key] + ', actual = ' + var2[key]);
1016 }
1017
1018 for (var key in var2) {
1019 _assert(message, key in var1, 'Actual hash had key ' + key +
1020 ' that was not expected');
1021 }
1022};
1023
1024
1025/**
1026 * @param {*} a The expected value (3 args) or the debug message (4 args).
1027 * @param {*} b The actual value (3 args) or the expected value (4 args).
1028 * @param {*} c The tolerance (3 args) or the actual value (4 args).
1029 * @param {*=} opt_d The tolerance (4 args only).
1030 */
1031var assertRoughlyEquals = function(a, b, c, opt_d) {
1032 _validateArguments(3, arguments);
1033 var expected = nonCommentArg(1, 3, arguments);
1034 var actual = nonCommentArg(2, 3, arguments);
1035 var tolerance = nonCommentArg(3, 3, arguments);
1036 _assert(commentArg(3, arguments),
1037 goog.testing.asserts.numberRoughEqualityPredicate_(
1038 expected, actual, tolerance),
1039 'Expected ' + expected + ', but got ' + actual +
1040 ' which was more than ' + tolerance + ' away');
1041};
1042
1043
1044/**
1045 * Checks if the given element is the member of the given container.
1046 * @param {*} a Failure message (3 arguments) or the contained element
1047 * (2 arguments).
1048 * @param {*} b The contained element (3 arguments) or the container
1049 * (2 arguments).
1050 * @param {*=} opt_c The container.
1051 */
1052var assertContains = function(a, b, opt_c) {
1053 _validateArguments(2, arguments);
1054 var contained = nonCommentArg(1, 2, arguments);
1055 var container = nonCommentArg(2, 2, arguments);
1056 _assert(commentArg(2, arguments),
1057 goog.testing.asserts.contains_(container, contained),
1058 'Expected \'' + container + '\' to contain \'' + contained + '\'');
1059};
1060
1061
1062/**
1063 * Checks if the given element is not the member of the given container.
1064 * @param {*} a Failure message (3 arguments) or the contained element
1065 * (2 arguments).
1066 * @param {*} b The contained element (3 arguments) or the container
1067 * (2 arguments).
1068 * @param {*=} opt_c The container.
1069 */
1070var assertNotContains = function(a, b, opt_c) {
1071 _validateArguments(2, arguments);
1072 var contained = nonCommentArg(1, 2, arguments);
1073 var container = nonCommentArg(2, 2, arguments);
1074 _assert(commentArg(2, arguments),
1075 !goog.testing.asserts.contains_(container, contained),
1076 'Expected \'' + container + '\' not to contain \'' + contained + '\'');
1077};
1078
1079
1080/**
1081 * Checks if the given string matches the given regular expression.
1082 * @param {*} a Failure message (3 arguments) or the expected regular
1083 * expression as a string or RegExp (2 arguments).
1084 * @param {*} b The regular expression (3 arguments) or the string to test
1085 * (2 arguments).
1086 * @param {*=} opt_c The string to test.
1087 */
1088var assertRegExp = function(a, b, opt_c) {
1089 _validateArguments(2, arguments);
1090 var regexp = nonCommentArg(1, 2, arguments);
1091 var string = nonCommentArg(2, 2, arguments);
1092 if (typeof(regexp) == 'string') {
1093 regexp = new RegExp(regexp);
1094 }
1095 _assert(commentArg(2, arguments),
1096 regexp.test(string),
1097 'Expected \'' + string + '\' to match RegExp ' + regexp.toString());
1098};
1099
1100
1101/**
1102 * Converts an array like object to array or clones it if it's already array.
1103 * @param {goog.testing.asserts.ArrayLike} arrayLike The collection.
1104 * @return {!Array<?>} Copy of the collection as array.
1105 * @private
1106 */
1107goog.testing.asserts.toArray_ = function(arrayLike) {
1108 var ret = [];
1109 for (var i = 0; i < arrayLike.length; i++) {
1110 ret[i] = arrayLike[i];
1111 }
1112 return ret;
1113};
1114
1115
1116/**
1117 * Finds the position of the first occurrence of an element in a container.
1118 * @param {goog.testing.asserts.ArrayLike} container
1119 * The array to find the element in.
1120 * @param {*} contained Element to find.
1121 * @return {number} Index of the first occurrence or -1 if not found.
1122 * @private
1123 */
1124goog.testing.asserts.indexOf_ = function(container, contained) {
1125 if (container.indexOf) {
1126 return container.indexOf(contained);
1127 } else {
1128 // IE6/7 do not have indexOf so do a search.
1129 for (var i = 0; i < container.length; i++) {
1130 if (container[i] === contained) {
1131 return i;
1132 }
1133 }
1134 return -1;
1135 }
1136};
1137
1138
1139/**
1140 * Tells whether the array contains the given element.
1141 * @param {goog.testing.asserts.ArrayLike} container The array to
1142 * find the element in.
1143 * @param {*} contained Element to find.
1144 * @return {boolean} Whether the element is in the array.
1145 * @private
1146 */
1147goog.testing.asserts.contains_ = function(container, contained) {
1148 // TODO(user): Can we check for container.contains as well?
1149 // That would give us support for most goog.structs (though weird results
1150 // with anything else with a contains method, like goog.math.Range). Falling
1151 // back with container.some would catch all iterables, too.
1152 return goog.testing.asserts.indexOf_(container, contained) != -1;
1153};
1154
1155var standardizeHTML = function(html) {
1156 var translator = document.createElement('DIV');
1157 translator.innerHTML = html;
1158
1159 // Trim whitespace from result (without relying on goog.string)
1160 return translator.innerHTML.replace(/^\s+|\s+$/g, '');
1161};
1162
1163
1164/**
1165 * Standardizes a CSS value for a given property by applying it to an element
1166 * and then reading it back.
1167 * @param {string} propertyName CSS property name.
1168 * @param {string} value CSS value.
1169 * @return {string} Normalized CSS value.
1170 */
1171var standardizeCSSValue = function(propertyName, value) {
1172 var styleDeclaration = document.createElement('DIV').style;
1173 styleDeclaration[propertyName] = value;
1174 return styleDeclaration[propertyName];
1175};
1176
1177
1178/**
1179 * Raises a JsUnit exception with the given comment.
1180 * @param {string} comment A summary for the exception.
1181 * @param {string=} opt_message A description of the exception.
1182 */
1183goog.testing.asserts.raiseException = function(comment, opt_message) {
1184 throw new goog.testing.JsUnitException(comment, opt_message);
1185};
1186
1187
1188/**
1189 * Helper function for assertObjectEquals.
1190 * @param {string} prop A property name.
1191 * @return {boolean} If the property name is an array index.
1192 * @private
1193 */
1194goog.testing.asserts.isArrayIndexProp_ = function(prop) {
1195 return (prop | 0) == prop;
1196};
1197
1198
1199
1200/**
1201 * @param {string} comment A summary for the exception.
1202 * @param {?string=} opt_message A description of the exception.
1203 * @constructor
1204 * @extends {Error}
1205 * @final
1206 */
1207goog.testing.JsUnitException = function(comment, opt_message) {
1208 this.isJsUnitException = true;
1209 this.message = (comment ? comment : '') +
1210 (comment && opt_message ? '\n' : '') +
1211 (opt_message ? opt_message : '');
1212 this.stackTrace = goog.testing.stacktrace.get();
1213 // These fields are for compatibility with jsUnitTestManager.
1214 this.comment = comment || null;
1215 this.jsUnitMessage = opt_message || '';
1216
1217 // Ensure there is a stack trace.
1218 if (Error.captureStackTrace) {
1219 Error.captureStackTrace(this, goog.testing.JsUnitException);
1220 } else {
1221 this.stack = new Error().stack || '';
1222 }
1223};
1224goog.inherits(goog.testing.JsUnitException, Error);
1225
1226
1227/** @override */
1228goog.testing.JsUnitException.prototype.toString = function() {
1229 return this.message;
1230};
1231
1232
1233goog.exportSymbol('fail', fail);
1234goog.exportSymbol('assert', assert);
1235goog.exportSymbol('assertThrows', assertThrows);
1236goog.exportSymbol('assertNotThrows', assertNotThrows);
1237goog.exportSymbol('assertTrue', assertTrue);
1238goog.exportSymbol('assertFalse', assertFalse);
1239goog.exportSymbol('assertEquals', assertEquals);
1240goog.exportSymbol('assertNotEquals', assertNotEquals);
1241goog.exportSymbol('assertNull', assertNull);
1242goog.exportSymbol('assertNotNull', assertNotNull);
1243goog.exportSymbol('assertUndefined', assertUndefined);
1244goog.exportSymbol('assertNotUndefined', assertNotUndefined);
1245goog.exportSymbol('assertNotNullNorUndefined', assertNotNullNorUndefined);
1246goog.exportSymbol('assertNonEmptyString', assertNonEmptyString);
1247goog.exportSymbol('assertNaN', assertNaN);
1248goog.exportSymbol('assertNotNaN', assertNotNaN);
1249goog.exportSymbol('assertObjectEquals', assertObjectEquals);
1250goog.exportSymbol('assertObjectRoughlyEquals', assertObjectRoughlyEquals);
1251goog.exportSymbol('assertObjectNotEquals', assertObjectNotEquals);
1252goog.exportSymbol('assertArrayEquals', assertArrayEquals);
1253goog.exportSymbol('assertElementsEquals', assertElementsEquals);
1254goog.exportSymbol('assertElementsRoughlyEqual', assertElementsRoughlyEqual);
1255goog.exportSymbol('assertSameElements', assertSameElements);
1256goog.exportSymbol('assertEvaluatesToTrue', assertEvaluatesToTrue);
1257goog.exportSymbol('assertEvaluatesToFalse', assertEvaluatesToFalse);
1258goog.exportSymbol('assertHTMLEquals', assertHTMLEquals);
1259goog.exportSymbol('assertHashEquals', assertHashEquals);
1260goog.exportSymbol('assertRoughlyEquals', assertRoughlyEquals);
1261goog.exportSymbol('assertContains', assertContains);
1262goog.exportSymbol('assertNotContains', assertNotContains);
1263goog.exportSymbol('assertRegExp', assertRegExp);