// Copyright 2010 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.testing.asserts'); goog.setTestOnly(); goog.require('goog.testing.JsUnitException'); // TODO(user): Copied from JsUnit with some small modifications, we should // reimplement the asserters. var DOUBLE_EQUALITY_PREDICATE = function(var1, var2) { return var1 == var2; }; var JSUNIT_UNDEFINED_VALUE = void 0; var TO_STRING_EQUALITY_PREDICATE = function(var1, var2) { return var1.toString() === var2.toString(); }; var OUTPUT_NEW_LINE_THRESHOLD = 40; /** @typedef {function(?, ?):boolean} */ var PredicateFunctionType; /** * @const {{ * String : PredicateFunctionType, * Number : PredicateFunctionType, * Boolean : PredicateFunctionType, * Date : PredicateFunctionType, * RegExp : PredicateFunctionType, * Function : PredicateFunctionType * }} */ var PRIMITIVE_EQUALITY_PREDICATES = { 'String': DOUBLE_EQUALITY_PREDICATE, 'Number': DOUBLE_EQUALITY_PREDICATE, 'Boolean': DOUBLE_EQUALITY_PREDICATE, 'Date': function(date1, date2) { return date1.getTime() == date2.getTime(); }, 'RegExp': TO_STRING_EQUALITY_PREDICATE, 'Function': TO_STRING_EQUALITY_PREDICATE }; /** * Compares equality of two numbers, allowing them to differ up to a given * tolerance. * @param {number} var1 A number. * @param {number} var2 A number. * @param {number} tolerance the maximum allowed difference. * @return {boolean} Whether the two variables are sufficiently close. * @private */ goog.testing.asserts.numberRoughEqualityPredicate_ = function( var1, var2, tolerance) { return Math.abs(var1 - var2) <= tolerance; }; /** * @type {Object} * @private */ goog.testing.asserts.primitiveRoughEqualityPredicates_ = { 'Number': goog.testing.asserts.numberRoughEqualityPredicate_ }; var _trueTypeOf = function(something) { var result = typeof something; try { switch (result) { case 'string': break; case 'boolean': break; case 'number': break; case 'object': if (something == null) { result = 'null'; break; } case 'function': switch (something.constructor) { case new String('').constructor: result = 'String'; break; case new Boolean(true).constructor: result = 'Boolean'; break; case new Number(0).constructor: result = 'Number'; break; case new Array().constructor: result = 'Array'; break; case new RegExp().constructor: result = 'RegExp'; break; case new Date().constructor: result = 'Date'; break; case Function: result = 'Function'; break; default: var m = something.constructor.toString().match(/function\s*([^( ]+)\(/); if (m) { result = m[1]; } else { break; } } break; } } catch (e) { } finally { result = result.substr(0, 1).toUpperCase() + result.substr(1); } return result; }; var _displayStringForValue = function(aVar) { var result; try { result = '<' + String(aVar) + '>'; } catch (ex) { result = ''; // toString does not work on this object :-( } if (!(aVar === null || aVar === JSUNIT_UNDEFINED_VALUE)) { result += ' (' + _trueTypeOf(aVar) + ')'; } return result; }; var fail = function(failureMessage) { goog.testing.asserts.raiseException('Call to fail()', failureMessage); }; var argumentsIncludeComments = function(expectedNumberOfNonCommentArgs, args) { return args.length == expectedNumberOfNonCommentArgs + 1; }; var commentArg = function(expectedNumberOfNonCommentArgs, args) { if (argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)) { return args[0]; } return null; }; var nonCommentArg = function( desiredNonCommentArgIndex, expectedNumberOfNonCommentArgs, args) { return argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) ? args[desiredNonCommentArgIndex] : args[desiredNonCommentArgIndex - 1]; }; var _validateArguments = function(expectedNumberOfNonCommentArgs, args) { var valid = args.length == expectedNumberOfNonCommentArgs || args.length == expectedNumberOfNonCommentArgs + 1 && goog.isString(args[0]); _assert(null, valid, 'Incorrect arguments passed to assert function'); }; var _getCurrentTestCase = function() { // We can't call goog.testing.TestCase.getActiveTestCase because there would // be a dependency cycle; this effectively does the same thing. var testRunner = goog.global['G_testRunner']; return testRunner ? testRunner.testCase : null; }; var _assert = function(comment, booleanValue, failureMessage) { if (!booleanValue) { goog.testing.asserts.raiseException(comment, failureMessage); } }; /** * @param {*} expected The expected value. * @param {*} actual The actual value. * @return {string} A failure message of the values don't match. * @private */ goog.testing.asserts.getDefaultErrorMsg_ = function(expected, actual) { var expectedDisplayString = _displayStringForValue(expected); var actualDisplayString = _displayStringForValue(actual); var shouldUseNewLines = expectedDisplayString.length > OUTPUT_NEW_LINE_THRESHOLD || actualDisplayString.length > OUTPUT_NEW_LINE_THRESHOLD; var msg = [ 'Expected', expectedDisplayString, 'but was', actualDisplayString ].join(shouldUseNewLines ? '\n' : ' '); if ((typeof expected == 'string') && (typeof actual == 'string')) { // Try to find a human-readable difference. var limit = Math.min(expected.length, actual.length); var commonPrefix = 0; while (commonPrefix < limit && expected.charAt(commonPrefix) == actual.charAt(commonPrefix)) { commonPrefix++; } var commonSuffix = 0; while (commonSuffix < limit && expected.charAt(expected.length - commonSuffix - 1) == actual.charAt(actual.length - commonSuffix - 1)) { commonSuffix++; } if (commonPrefix + commonSuffix > limit) { commonSuffix = 0; } if (commonPrefix > 2 || commonSuffix > 2) { var printString = function(str) { var startIndex = Math.max(0, commonPrefix - 2); var endIndex = Math.min(str.length, str.length - (commonSuffix - 2)); return (startIndex > 0 ? '...' : '') + str.substring(startIndex, endIndex) + (endIndex < str.length ? '...' : ''); }; var expectedPrinted = printString(expected); var expectedActual = printString(actual); var shouldUseNewLinesInDiff = expectedPrinted.length > OUTPUT_NEW_LINE_THRESHOLD || expectedActual.length > OUTPUT_NEW_LINE_THRESHOLD; msg += '\nDifference was at position ' + commonPrefix + '. ' + [ 'Expected', '[' + expectedPrinted + ']', 'vs. actual', '[' + expectedActual + ']' ].join(shouldUseNewLinesInDiff ? '\n' : ' '); } } return msg; }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assert = function(a, opt_b) { _validateArguments(1, arguments); var comment = commentArg(1, arguments); var booleanValue = nonCommentArg(1, 1, arguments); _assert( comment, goog.isBoolean(booleanValue), 'Bad argument to assert(boolean)'); _assert(comment, booleanValue, 'Call to assert(boolean) with false'); }; /** * Asserts that the function throws an error. * * @param {!(string|Function)} a The assertion comment or the function to call. * @param {!Function=} opt_b The function to call (if the first argument of * {@code assertThrows} was the comment). * @return {*} The error thrown by the function. * @throws {goog.testing.JsUnitException} If the assertion failed. */ var assertThrows = function(a, opt_b) { _validateArguments(1, arguments); var func = nonCommentArg(1, 1, arguments); var comment = commentArg(1, arguments); _assert( comment, typeof func == 'function', 'Argument passed to assertThrows is not a function'); try { func(); } catch (e) { if (e && goog.isString(e['stacktrace']) && goog.isString(e['message'])) { // Remove the stack trace appended to the error message by Opera 10.0 var startIndex = e['message'].length - e['stacktrace'].length; if (e['message'].indexOf(e['stacktrace'], startIndex) == startIndex) { e['message'] = e['message'].substr(0, startIndex - 14); } } var testCase = _getCurrentTestCase(); if (e && e['isJsUnitException'] && testCase && testCase.failOnUnreportedAsserts) { goog.testing.asserts.raiseException( comment, 'Function passed to assertThrows caught a JsUnitException (usually ' + 'from an assert or call to fail()). If this is expected, use ' + 'assertThrowsJsUnitException instead.'); } return e; } goog.testing.asserts.raiseException( comment, 'No exception thrown from function passed to assertThrows'); }; /** * Asserts that the function does not throw an error. * * @param {!(string|Function)} a The assertion comment or the function to call. * @param {!Function=} opt_b The function to call (if the first argument of * {@code assertNotThrows} was the comment). * @return {*} The return value of the function. * @throws {goog.testing.JsUnitException} If the assertion failed. */ var assertNotThrows = function(a, opt_b) { _validateArguments(1, arguments); var comment = commentArg(1, arguments); var func = nonCommentArg(1, 1, arguments); _assert( comment, typeof func == 'function', 'Argument passed to assertNotThrows is not a function'); try { return func(); } catch (e) { comment = comment ? (comment + '\n') : ''; comment += 'A non expected exception was thrown from function passed to ' + 'assertNotThrows'; // Some browsers don't have a stack trace so at least have the error // description. var stackTrace = e['stack'] || e['stacktrace'] || e.toString(); goog.testing.asserts.raiseException(comment, stackTrace); } }; /** * Asserts that the given callback function results in a JsUnitException when * called, and that the resulting failure message matches the given expected * message. * @param {function() : void} callback Function to be run expected to result * in a JsUnitException (usually contains a call to an assert). * @param {string=} opt_expectedMessage Failure message expected to be given * with the exception. * @return {!goog.testing.JsUnitException} The error thrown by the function. * @throws {goog.testing.JsUnitException} If the function did not throw a * JsUnitException. */ var assertThrowsJsUnitException = function(callback, opt_expectedMessage) { try { goog.testing.asserts.callWithoutLogging(callback); } catch (e) { var testCase = _getCurrentTestCase(); if (testCase) { testCase.invalidateAssertionException(e); } else { goog.global.console.error( 'Failed to remove expected exception: no test case is installed.'); } if (!e.isJsUnitException) { fail('Expected a JsUnitException'); } if (typeof opt_expectedMessage != 'undefined' && e.message != opt_expectedMessage) { fail( 'Expected message [' + opt_expectedMessage + '] but got [' + e.message + ']'); } return e; } var msg = 'Expected a failure'; if (typeof opt_expectedMessage != 'undefined') { msg += ': ' + opt_expectedMessage; } throw new goog.testing.JsUnitException(msg); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertTrue = function(a, opt_b) { _validateArguments(1, arguments); var comment = commentArg(1, arguments); var booleanValue = nonCommentArg(1, 1, arguments); _assert( comment, goog.isBoolean(booleanValue), 'Bad argument to assertTrue(boolean)'); _assert(comment, booleanValue, 'Call to assertTrue(boolean) with false'); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertFalse = function(a, opt_b) { _validateArguments(1, arguments); var comment = commentArg(1, arguments); var booleanValue = nonCommentArg(1, 1, arguments); _assert( comment, goog.isBoolean(booleanValue), 'Bad argument to assertFalse(boolean)'); _assert(comment, !booleanValue, 'Call to assertFalse(boolean) with true'); }; /** * @param {*} a The expected value (2 args) or the debug message (3 args). * @param {*} b The actual value (2 args) or the expected value (3 args). * @param {*=} opt_c The actual value (3 args only). */ var assertEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var var1 = nonCommentArg(1, 2, arguments); var var2 = nonCommentArg(2, 2, arguments); _assert( commentArg(2, arguments), var1 === var2, goog.testing.asserts.getDefaultErrorMsg_(var1, var2)); }; /** * @param {*} a The expected value (2 args) or the debug message (3 args). * @param {*} b The actual value (2 args) or the expected value (3 args). * @param {*=} opt_c The actual value (3 args only). */ var assertNotEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var var1 = nonCommentArg(1, 2, arguments); var var2 = nonCommentArg(2, 2, arguments); _assert( commentArg(2, arguments), var1 !== var2, 'Expected not to be ' + _displayStringForValue(var2)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNull = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert( commentArg(1, arguments), aVar === null, goog.testing.asserts.getDefaultErrorMsg_(null, aVar)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNotNull = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert( commentArg(1, arguments), aVar !== null, 'Expected not to be ' + _displayStringForValue(null)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertUndefined = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert( commentArg(1, arguments), aVar === JSUNIT_UNDEFINED_VALUE, goog.testing.asserts.getDefaultErrorMsg_(JSUNIT_UNDEFINED_VALUE, aVar)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNotUndefined = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert( commentArg(1, arguments), aVar !== JSUNIT_UNDEFINED_VALUE, 'Expected not to be ' + _displayStringForValue(JSUNIT_UNDEFINED_VALUE)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNotNullNorUndefined = function(a, opt_b) { _validateArguments(1, arguments); assertNotNull.apply(null, arguments); assertNotUndefined.apply(null, arguments); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNonEmptyString = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert( commentArg(1, arguments), aVar !== JSUNIT_UNDEFINED_VALUE && aVar !== null && typeof aVar == 'string' && aVar !== '', 'Expected non-empty string but was ' + _displayStringForValue(aVar)); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNaN = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), isNaN(aVar), 'Expected NaN'); }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertNotNaN = function(a, opt_b) { _validateArguments(1, arguments); var aVar = nonCommentArg(1, 1, arguments); _assert(commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN'); }; /** * Runs a function in an environment where test failures are not logged. This is * useful for testing test code, where failures can be a normal part of a test. * @param {function() : void} fn Function to run without logging failures. */ goog.testing.asserts.callWithoutLogging = function(fn) { var testRunner = goog.global['G_testRunner']; var oldLogTestFailure = testRunner['logTestFailure']; try { // Any failures in the callback shouldn't be recorded. testRunner['logTestFailure'] = undefined; fn(); } finally { testRunner['logTestFailure'] = oldLogTestFailure; } }; /** * The return value of the equality predicate passed to findDifferences below, * in cases where the predicate can't test the input variables for equality. * @type {?string} */ goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS = null; /** * The return value of the equality predicate passed to findDifferences below, * in cases where the input vriables are equal. * @type {?string} */ goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL = ''; /** * Determines if two items of any type match, and formulates an error message * if not. * @param {*} expected Expected argument to match. * @param {*} actual Argument as a result of performing the test. * @param {(function(string, *, *): ?string)=} opt_equalityPredicate An optional * function that can be used to check equality of variables. It accepts 3 * arguments: type-of-variables, var1, var2 (in that order) and returns an * error message if the variables are not equal, * goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL if the variables * are equal, or * goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS if the predicate * couldn't check the input variables. The function will be called only if * the types of var1 and var2 are identical. * @return {?string} Null on success, error message on failure. */ goog.testing.asserts.findDifferences = function( expected, actual, opt_equalityPredicate) { var failures = []; var seen1 = []; var seen2 = []; // To avoid infinite recursion when the two parameters are self-referential // along the same path of properties, keep track of the object pairs already // seen in this call subtree, and abort when a cycle is detected. function innerAssertWithCycleCheck(var1, var2, path) { // This is used for testing, so we can afford to be slow (but more // accurate). So we just check whether var1 is in seen1. If we // found var1 in index i, we simply need to check whether var2 is // in seen2[i]. If it is, we do not recurse to check var1/var2. If // it isn't, we know that the structures of the two objects must be // different. // // This is based on the fact that values at index i in seen1 and // seen2 will be checked for equality eventually (when // innerAssertImplementation(seen1[i], seen2[i], path) finishes). for (var i = 0; i < seen1.length; ++i) { var match1 = seen1[i] === var1; var match2 = seen2[i] === var2; if (match1 || match2) { if (!match1 || !match2) { // Asymmetric cycles, so the objects have different structure. failures.push('Asymmetric cycle detected at ' + path); } return; } } seen1.push(var1); seen2.push(var2); innerAssertImplementation(var1, var2, path); seen1.pop(); seen2.pop(); } var equalityPredicate = opt_equalityPredicate || function(type, var1, var2) { var typedPredicate = PRIMITIVE_EQUALITY_PREDICATES[type]; if (!typedPredicate) { return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS; } var equal = typedPredicate(var1, var2); return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL : goog.testing.asserts.getDefaultErrorMsg_(var1, var2); }; /** * @param {*} var1 An item in the expected object. * @param {*} var2 The corresponding item in the actual object. * @param {string} path Their path in the objects. * @suppress {missingProperties} The map_ property is unknown to the compiler * unless goog.structs.Map is loaded. */ function innerAssertImplementation(var1, var2, path) { if (var1 === var2) { return; } var typeOfVar1 = _trueTypeOf(var1); var typeOfVar2 = _trueTypeOf(var2); if (typeOfVar1 == typeOfVar2) { var isArray = typeOfVar1 == 'Array'; var errorMessage = equalityPredicate(typeOfVar1, var1, var2); if (errorMessage != goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS) { if (errorMessage != goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL) { failures.push(path + ': ' + errorMessage); } } else if (isArray && var1.length != var2.length) { failures.push( path + ': Expected ' + var1.length + '-element array ' + 'but got a ' + var2.length + '-element array'); } else if (typeOfVar1 == 'String') { if (var1 != var2) { failures.push( path + ': Expected String "' + var1 + '" ' + 'but got "' + var2 + '"'); } } else { var childPath = path + (isArray ? '[%s]' : (path ? '.%s' : '%s')); // These type checks do not use _trueTypeOf because that does not work // for polyfilled Map/Set. Note that these checks may potentially fail // if var1 comes from a different window. if ((typeof Map != 'undefined' && var1 instanceof Map) || (typeof Set != 'undefined' && var1 instanceof Set)) { var1.forEach(function(value, key) { if (var2.has(key)) { // For a map, the values must be compared, but with Set, checking // that the second set contains the first set's "keys" is // sufficient. if (var2.get) { innerAssertWithCycleCheck( value, var2.get(key), childPath.replace('%s', key)); } } else { failures.push( key + ' not present in actual ' + (path || typeOfVar2)); } }); var2.forEach(function(value, key) { if (!var1.has(key)) { failures.push( key + ' not present in expected ' + (path || typeOfVar1)); } }); } else if (!var1['__iterator__']) { // if an object has an __iterator__ property, we have no way of // actually inspecting its raw properties, and JS 1.7 doesn't // overload [] to make it possible for someone to generically // use what the iterator returns to compare the object-managed // properties. This gets us into deep poo with things like // goog.structs.Map, at least on systems that support iteration. for (var prop in var1) { if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) { // Skip array indices for now. We'll handle them later. continue; } if (prop in var2) { innerAssertWithCycleCheck( var1[prop], var2[prop], childPath.replace('%s', prop)); } else { failures.push( 'property ' + prop + ' not present in actual ' + (path || typeOfVar2)); } } // make sure there aren't properties in var2 that are missing // from var1. if there are, then by definition they don't // match. for (var prop in var2) { if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) { // Skip array indices for now. We'll handle them later. continue; } if (!(prop in var1)) { failures.push( 'property ' + prop + ' not present in expected ' + (path || typeOfVar1)); } } // Handle array indices by iterating from 0 to arr.length. // // Although all browsers allow holes in arrays, browsers // are inconsistent in what they consider a hole. For example, // "[0,undefined,2]" has a hole on IE but not on Firefox. // // Because our style guide bans for...in iteration over arrays, // we assume that most users don't care about holes in arrays, // and that it is ok to say that a hole is equivalent to a slot // populated with 'undefined'. if (isArray) { for (prop = 0; prop < var1.length; prop++) { innerAssertWithCycleCheck( var1[prop], var2[prop], childPath.replace('%s', String(prop))); } } } else { // special-case for closure objects that have iterators if (goog.isFunction(var1.equals)) { // use the object's own equals function, assuming it accepts an // object and returns a boolean if (!var1.equals(var2)) { failures.push( 'equals() returned false for ' + (path || typeOfVar1)); } } else if (var1.map_) { // assume goog.structs.Map or goog.structs.Set, where comparing // their private map_ field is sufficient innerAssertWithCycleCheck( var1.map_, var2.map_, childPath.replace('%s', 'map_')); } else { // else die, so user knows we can't do anything failures.push( 'unable to check ' + (path || typeOfVar1) + ' for equality: it has an iterator we do not ' + 'know how to handle. please add an equals method'); } } } } else { failures.push( path + ' ' + goog.testing.asserts.getDefaultErrorMsg_(var1, var2)); } } innerAssertWithCycleCheck(expected, actual, ''); return failures.length == 0 ? null : goog.testing.asserts.getDefaultErrorMsg_( expected, actual) + '\n ' + failures.join('\n '); }; /** * Notes: * Object equality has some nasty browser quirks, and this implementation is * not 100% correct. For example, * * * var a = [0, 1, 2]; * var b = [0, 1, 2]; * delete a[1]; * b[1] = undefined; * assertObjectEquals(a, b); // should fail, but currently passes * * * See asserts_test.html for more interesting edge cases. * * The first comparison object provided is the expected value, the second is * the actual. * * @param {*} a Assertion message or comparison object. * @param {*} b Comparison object. * @param {*=} opt_c Comparison object, if an assertion message was provided. */ var assertObjectEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var v1 = nonCommentArg(1, 2, arguments); var v2 = nonCommentArg(2, 2, arguments); var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : ''; var differences = goog.testing.asserts.findDifferences(v1, v2); _assert(failureMessage, !differences, differences); }; /** * Similar to assertObjectEquals above, but accepts a tolerance margin. * * @param {*} a Assertion message or comparison object. * @param {*} b Comparison object. * @param {*} c Comparison object or tolerance. * @param {*=} opt_d Tolerance, if an assertion message was provided. */ var assertObjectRoughlyEquals = function(a, b, c, opt_d) { _validateArguments(3, arguments); var v1 = nonCommentArg(1, 3, arguments); var v2 = nonCommentArg(2, 3, arguments); var tolerance = nonCommentArg(3, 3, arguments); var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : ''; var equalityPredicate = function(type, var1, var2) { var typedPredicate = goog.testing.asserts.primitiveRoughEqualityPredicates_[type]; if (!typedPredicate) { return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS; } var equal = typedPredicate(var1, var2, tolerance); return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL : goog.testing.asserts.getDefaultErrorMsg_(var1, var2) + ' which was more than ' + tolerance + ' away'; }; var differences = goog.testing.asserts.findDifferences(v1, v2, equalityPredicate); _assert(failureMessage, !differences, differences); }; /** * Compares two arbitrary objects for non-equalness. * * All the same caveats as for assertObjectEquals apply here: * Undefined values may be confused for missing values, or vice versa. * * @param {*} a Assertion message or comparison object. * @param {*} b Comparison object. * @param {*=} opt_c Comparison object, if an assertion message was provided. */ var assertObjectNotEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var v1 = nonCommentArg(1, 2, arguments); var v2 = nonCommentArg(2, 2, arguments); var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : ''; var differences = goog.testing.asserts.findDifferences(v1, v2); _assert(failureMessage, differences, 'Objects should not be equal'); }; /** * Compares two arrays ignoring negative indexes and extra properties on the * array objects. Use case: Internet Explorer adds the index, lastIndex and * input enumerable fields to the result of string.match(/regexp/g), which makes * assertObjectEquals fail. * @param {*} a The expected array (2 args) or the debug message (3 args). * @param {*} b The actual array (2 args) or the expected array (3 args). * @param {*=} opt_c The actual array (3 args only). */ var assertArrayEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var v1 = nonCommentArg(1, 2, arguments); var v2 = nonCommentArg(2, 2, arguments); var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : ''; var typeOfVar1 = _trueTypeOf(v1); _assert( failureMessage, typeOfVar1 == 'Array', 'Expected an array for assertArrayEquals but found a ' + typeOfVar1); var typeOfVar2 = _trueTypeOf(v2); _assert( failureMessage, typeOfVar2 == 'Array', 'Expected an array for assertArrayEquals but found a ' + typeOfVar2); assertObjectEquals( failureMessage, Array.prototype.concat.call(v1), Array.prototype.concat.call(v2)); }; /** * Compares two objects that can be accessed like an array and assert that * each element is equal. * @param {string|Object} a Failure message (3 arguments) * or object #1 (2 arguments). * @param {Object} b Object #1 (2 arguments) or object #2 (3 arguments). * @param {Object=} opt_c Object #2 (3 arguments). */ var assertElementsEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var v1 = nonCommentArg(1, 2, arguments); var v2 = nonCommentArg(2, 2, arguments); var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : ''; if (!v1) { assert(failureMessage, !v2); } else { assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length); for (var i = 0; i < v1.length; ++i) { assertEquals( 'mismatch at index ' + i + ': ' + failureMessage, v1[i], v2[i]); } } }; /** * Compares two objects that can be accessed like an array and assert that * each element is roughly equal. * @param {string|Object} a Failure message (4 arguments) * or object #1 (3 arguments). * @param {Object} b Object #1 (4 arguments) or object #2 (3 arguments). * @param {Object|number} c Object #2 (4 arguments) or tolerance (3 arguments). * @param {number=} opt_d tolerance (4 arguments). */ var assertElementsRoughlyEqual = function(a, b, c, opt_d) { _validateArguments(3, arguments); var v1 = nonCommentArg(1, 3, arguments); var v2 = nonCommentArg(2, 3, arguments); var tolerance = nonCommentArg(3, 3, arguments); var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : ''; if (!v1) { assert(failureMessage, !v2); } else { assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length); for (var i = 0; i < v1.length; ++i) { assertRoughlyEquals(failureMessage, v1[i], v2[i], tolerance); } } }; /** * Compares two array-like objects without taking their order into account. * @param {string|IArrayLike} a Assertion message or the * expected elements. * @param {IArrayLike} b Expected elements or the actual * elements. * @param {IArrayLike=} opt_c Actual elements. */ var assertSameElements = function(a, b, opt_c) { _validateArguments(2, arguments); var expected = nonCommentArg(1, 2, arguments); var actual = nonCommentArg(2, 2, arguments); var message = commentArg(2, arguments); assertTrue( 'Bad arguments to assertSameElements(opt_message, expected: ' + 'ArrayLike, actual: ArrayLike)', goog.isArrayLike(expected) && goog.isArrayLike(actual)); // Clones expected and actual and converts them to real arrays. expected = goog.testing.asserts.toArray_(expected); actual = goog.testing.asserts.toArray_(actual); // TODO(user): It would be great to show only the difference // between the expected and actual elements. _assert( message, expected.length == actual.length, 'Expected ' + expected.length + ' elements: [' + expected + '], ' + 'got ' + actual.length + ' elements: [' + actual + ']'); var toFind = goog.testing.asserts.toArray_(expected); for (var i = 0; i < actual.length; i++) { var index = goog.testing.asserts.indexOf_(toFind, actual[i]); _assert( message, index != -1, 'Expected [' + expected + '], got [' + actual + ']'); toFind.splice(index, 1); } }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertEvaluatesToTrue = function(a, opt_b) { _validateArguments(1, arguments); var value = nonCommentArg(1, 1, arguments); if (!value) { _assert(commentArg(1, arguments), false, 'Expected to evaluate to true'); } }; /** * @param {*} a The value to assert (1 arg) or debug message (2 args). * @param {*=} opt_b The value to assert (2 args only). */ var assertEvaluatesToFalse = function(a, opt_b) { _validateArguments(1, arguments); var value = nonCommentArg(1, 1, arguments); if (value) { _assert(commentArg(1, arguments), false, 'Expected to evaluate to false'); } }; /** * Compares two HTML snippets. * * Take extra care if attributes are involved. {@code assertHTMLEquals}'s * implementation isn't prepared for complex cases. For example, the following * comparisons erroneously fail: *
 * assertHTMLEquals('', '');
 * assertHTMLEquals('
', '
'); * assertHTMLEquals('', ''); *
* * When in doubt, use {@code goog.testing.dom.assertHtmlMatches}. * * @param {*} a The expected value (2 args) or the debug message (3 args). * @param {*} b The actual value (2 args) or the expected value (3 args). * @param {*=} opt_c The actual value (3 args only). */ var assertHTMLEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var var1 = nonCommentArg(1, 2, arguments); var var2 = nonCommentArg(2, 2, arguments); var var1Standardized = standardizeHTML(var1); var var2Standardized = standardizeHTML(var2); _assert( commentArg(2, arguments), var1Standardized === var2Standardized, goog.testing.asserts.getDefaultErrorMsg_( var1Standardized, var2Standardized)); }; /** * Compares two CSS property values to make sure that they represent the same * things. This will normalize values in the browser. For example, in Firefox, * this assertion will consider "rgb(0, 0, 255)" and "#0000ff" to be identical * values for the "color" property. This function won't normalize everything -- * for example, in most browsers, "blue" will not match "#0000ff". It is * intended only to compensate for unexpected normalizations performed by * the browser that should also affect your expected value. * @param {string} a Assertion message, or the CSS property name. * @param {string} b CSS property name, or the expected value. * @param {string} c The expected value, or the actual value. * @param {string=} opt_d The actual value. */ var assertCSSValueEquals = function(a, b, c, opt_d) { _validateArguments(3, arguments); var propertyName = nonCommentArg(1, 3, arguments); var expectedValue = nonCommentArg(2, 3, arguments); var actualValue = nonCommentArg(3, 3, arguments); var expectedValueStandardized = standardizeCSSValue(propertyName, expectedValue); var actualValueStandardized = standardizeCSSValue(propertyName, actualValue); _assert( commentArg(3, arguments), expectedValueStandardized == actualValueStandardized, goog.testing.asserts.getDefaultErrorMsg_( expectedValueStandardized, actualValueStandardized)); }; /** * @param {*} a The expected value (2 args) or the debug message (3 args). * @param {*} b The actual value (2 args) or the expected value (3 args). * @param {*=} opt_c The actual value (3 args only). */ var assertHashEquals = function(a, b, opt_c) { _validateArguments(2, arguments); var var1 = nonCommentArg(1, 2, arguments); var var2 = nonCommentArg(2, 2, arguments); var message = commentArg(2, arguments); for (var key in var1) { _assert( message, key in var2, 'Expected hash had key ' + key + ' that was not found'); _assert( message, var1[key] == var2[key], 'Value for key ' + key + ' mismatch - expected = ' + var1[key] + ', actual = ' + var2[key]); } for (var key in var2) { _assert( message, key in var1, 'Actual hash had key ' + key + ' that was not expected'); } }; /** * @param {*} a The expected value (3 args) or the debug message (4 args). * @param {*} b The actual value (3 args) or the expected value (4 args). * @param {*} c The tolerance (3 args) or the actual value (4 args). * @param {*=} opt_d The tolerance (4 args only). */ var assertRoughlyEquals = function(a, b, c, opt_d) { _validateArguments(3, arguments); var expected = nonCommentArg(1, 3, arguments); var actual = nonCommentArg(2, 3, arguments); var tolerance = nonCommentArg(3, 3, arguments); _assert( commentArg(3, arguments), goog.testing.asserts.numberRoughEqualityPredicate_( expected, actual, tolerance), 'Expected ' + expected + ', but got ' + actual + ' which was more than ' + tolerance + ' away'); }; /** * Checks if the test value is a member of the given container. Uses * container.indexOf as the underlying function, so this works for strings * and arrays. * @param {*} a Failure message (3 arguments) or the test value * (2 arguments). * @param {*} b The test value (3 arguments) or the container * (2 arguments). * @param {*=} opt_c The container. */ var assertContains = function(a, b, opt_c) { _validateArguments(2, arguments); var contained = nonCommentArg(1, 2, arguments); var container = nonCommentArg(2, 2, arguments); _assert( commentArg(2, arguments), goog.testing.asserts.contains_(container, contained), 'Expected \'' + container + '\' to contain \'' + contained + '\''); }; /** * Checks if the given element is not the member of the given container. * @param {*} a Failure message (3 arguments) or the contained element * (2 arguments). * @param {*} b The contained element (3 arguments) or the container * (2 arguments). * @param {*=} opt_c The container. */ var assertNotContains = function(a, b, opt_c) { _validateArguments(2, arguments); var contained = nonCommentArg(1, 2, arguments); var container = nonCommentArg(2, 2, arguments); _assert( commentArg(2, arguments), !goog.testing.asserts.contains_(container, contained), 'Expected \'' + container + '\' not to contain \'' + contained + '\''); }; /** * Checks if the given string matches the given regular expression. * @param {*} a Failure message (3 arguments) or the expected regular * expression as a string or RegExp (2 arguments). * @param {*} b The regular expression (3 arguments) or the string to test * (2 arguments). * @param {*=} opt_c The string to test. */ var assertRegExp = function(a, b, opt_c) { _validateArguments(2, arguments); var regexp = nonCommentArg(1, 2, arguments); var string = nonCommentArg(2, 2, arguments); if (typeof(regexp) == 'string') { regexp = new RegExp(regexp); } _assert( commentArg(2, arguments), regexp.test(string), 'Expected \'' + string + '\' to match RegExp ' + regexp.toString()); }; /** * Converts an array like object to array or clones it if it's already array. * @param {IArrayLike} arrayLike The collection. * @return {!Array} Copy of the collection as array. * @private */ goog.testing.asserts.toArray_ = function(arrayLike) { var ret = []; for (var i = 0; i < arrayLike.length; i++) { ret[i] = arrayLike[i]; } return ret; }; /** * Finds the position of the first occurrence of an element in a container. * @param {IArrayLike} container * The array to find the element in. * @param {*} contained Element to find. * @return {number} Index of the first occurrence or -1 if not found. * @private */ goog.testing.asserts.indexOf_ = function(container, contained) { if (container.indexOf) { return container.indexOf(contained); } else { // IE6/7 do not have indexOf so do a search. for (var i = 0; i < container.length; i++) { if (container[i] === contained) { return i; } } return -1; } }; /** * Tells whether the array contains the given element. * @param {IArrayLike} container The array to * find the element in. * @param {*} contained Element to find. * @return {boolean} Whether the element is in the array. * @private */ goog.testing.asserts.contains_ = function(container, contained) { // TODO(user): Can we check for container.contains as well? // That would give us support for most goog.structs (though weird results // with anything else with a contains method, like goog.math.Range). Falling // back with container.some would catch all iterables, too. return goog.testing.asserts.indexOf_(container, contained) != -1; }; var standardizeHTML = function(html) { var translator = document.createElement('DIV'); translator.innerHTML = html; // Trim whitespace from result (without relying on goog.string) return translator.innerHTML.replace(/^\s+|\s+$/g, ''); }; /** * Standardizes a CSS value for a given property by applying it to an element * and then reading it back. * @param {string} propertyName CSS property name. * @param {string} value CSS value. * @return {string} Normalized CSS value. */ var standardizeCSSValue = function(propertyName, value) { var styleDeclaration = document.createElement('DIV').style; styleDeclaration[propertyName] = value; return styleDeclaration[propertyName]; }; /** * Raises a JsUnit exception with the given comment. If the exception is * unexpectedly caught during a unit test, it will be rethrown so that it is * seen by the test framework. * @param {string} comment A summary for the exception. * @param {string=} opt_message A description of the exception. */ goog.testing.asserts.raiseException = function(comment, opt_message) { var e = new goog.testing.JsUnitException(comment, opt_message); var testCase = _getCurrentTestCase(); if (testCase) { testCase.raiseAssertionException(e); } else { goog.global.console.error( 'Failed to save thrown exception: no test case is installed.'); throw e; } }; /** * Helper function for assertObjectEquals. * @param {string} prop A property name. * @return {boolean} If the property name is an array index. * @private */ goog.testing.asserts.isArrayIndexProp_ = function(prop) { return (prop | 0) == prop; }; goog.exportSymbol('fail', fail); goog.exportSymbol('assert', assert); goog.exportSymbol('assertThrows', assertThrows); goog.exportSymbol('assertNotThrows', assertNotThrows); goog.exportSymbol('assertThrowsJsUnitException', assertThrowsJsUnitException); goog.exportSymbol('assertTrue', assertTrue); goog.exportSymbol('assertFalse', assertFalse); goog.exportSymbol('assertEquals', assertEquals); goog.exportSymbol('assertNotEquals', assertNotEquals); goog.exportSymbol('assertNull', assertNull); goog.exportSymbol('assertNotNull', assertNotNull); goog.exportSymbol('assertUndefined', assertUndefined); goog.exportSymbol('assertNotUndefined', assertNotUndefined); goog.exportSymbol('assertNotNullNorUndefined', assertNotNullNorUndefined); goog.exportSymbol('assertNonEmptyString', assertNonEmptyString); goog.exportSymbol('assertNaN', assertNaN); goog.exportSymbol('assertNotNaN', assertNotNaN); goog.exportSymbol('assertObjectEquals', assertObjectEquals); goog.exportSymbol('assertObjectRoughlyEquals', assertObjectRoughlyEquals); goog.exportSymbol('assertObjectNotEquals', assertObjectNotEquals); goog.exportSymbol('assertArrayEquals', assertArrayEquals); goog.exportSymbol('assertElementsEquals', assertElementsEquals); goog.exportSymbol('assertElementsRoughlyEqual', assertElementsRoughlyEqual); goog.exportSymbol('assertSameElements', assertSameElements); goog.exportSymbol('assertEvaluatesToTrue', assertEvaluatesToTrue); goog.exportSymbol('assertEvaluatesToFalse', assertEvaluatesToFalse); goog.exportSymbol('assertHTMLEquals', assertHTMLEquals); goog.exportSymbol('assertHashEquals', assertHashEquals); goog.exportSymbol('assertRoughlyEquals', assertRoughlyEquals); goog.exportSymbol('assertContains', assertContains); goog.exportSymbol('assertNotContains', assertNotContains); goog.exportSymbol('assertRegExp', assertRegExp);