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