chromium/third_party/google-closure-library/closure/goog/testing/mockmatchers_test.js

/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.testing.mockmatchersTest');
goog.setTestOnly('goog.testing.mockmatchersTest');

const ArgumentMatcher = goog.require('goog.testing.mockmatchers.ArgumentMatcher');
const TagName = goog.require('goog.dom.TagName');
const dom = goog.require('goog.dom');
const matchers = goog.require('goog.testing.mockmatchers');
const testSuite = goog.require('goog.testing.testSuite');

// Simple classes to test the InstanceOf matcher.
const foo = function() {};
const bar = function() {};

// Simple class to test adding error messages to
// MockExpectation objects
function MockMock() {
  /** @suppress {globalThis} suppression added to enable type checking */
  this.errorMessages = [];
}

let mockExpect = null;

MockMock.prototype.addErrorMessage = function(msg) {
  this.errorMessages.push(msg);
};


MockMock.prototype.getErrorMessageCount = function() {
  return this.errorMessages.length;
};

testSuite({
  setUp() {
    /** @suppress {checkTypes} suppression added to enable type checking */
    mockExpect = new MockMock();
  },


  testNoMatchName() {
    // A matcher that does not fill in the match name
    const matcher = new ArgumentMatcher(x => typeof x === 'string');

    // Make sure the lack of match name doesn't affect the ability
    // to return True/False
    assertTrue(matcher.matches('hello'));
    assertFalse(matcher.matches(123));

    // Make sure we handle the lack of a match name
    assertFalse(matcher.matches(456, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: missing mockmatcher description ' +
            'but was: <456> (Number)',
        mockExpect.errorMessages[0]);
  },


  /** @suppress {checkTypes} suppression added to enable type checking */
  testInstanceOf() {
    const matcher = new matchers.InstanceOf(foo);
    assertTrue(matcher.matches(new foo()));
    assertFalse(matcher.matches(new bar()));

    assertFalse(matcher.matches(new bar(), mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: instanceOf() ' +
            'but was: <[object Object]> (Object)',
        mockExpect.errorMessages[0]);
  },


  testTypeOf() {
    const matcher = new matchers.TypeOf('number');
    assertTrue(matcher.matches(1));
    assertTrue(matcher.matches(2));
    assertFalse(matcher.matches('test'));

    assertFalse(matcher.matches(true, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: typeOf(number) but was: <true> (Boolean)',
        mockExpect.errorMessages[0]);
  },


  testRegexpMatch() {
    const matcher = new matchers.RegexpMatch(/^cho[dtp]/);
    assertTrue(matcher.matches('chodhop'));
    assertTrue(matcher.matches('chopper'));
    assertFalse(matcher.matches('chocolate'));
    assertFalse(matcher.matches(null));

    assertFalse(matcher.matches('an anger', mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: match(/^cho[dtp]/) but was: <an anger> (String)',
        mockExpect.errorMessages[0]);
  },


  testObjectEquals() {
    // Test a simple match.
    const simpleMatcher = new matchers.ObjectEquals({name: 'Bob', age: 42});
    assertTrue(simpleMatcher.matches({name: 'Bob', age: 42}, mockExpect));
    assertEquals(0, mockExpect.getErrorMessageCount());
    expectObjectEqualsFailure(
        simpleMatcher, {name: 'Bill', age: 42},
        'name: Expected <Bob> (String) but was <Bill> (String)');
    expectObjectEqualsFailure(
        simpleMatcher, {name: 'Bob', age: 21},
        'age: Expected <42> (Number) but was <21> (Number)');
    expectObjectEqualsFailure(
        simpleMatcher, {name: 'Bob'},
        'property age not present in actual Object');
    expectObjectEqualsFailure(
        simpleMatcher, {name: 'Bob', age: 42, country: 'USA'},
        'property country not present in expected Object');

    // Multiple mismatches should include multiple messages.
    expectObjectEqualsFailure(
        simpleMatcher, {name: 'Jim', age: 36},
        'name: Expected <Bob> (String) but was <Jim> (String)\n' +
            '   age: Expected <42> (Number) but was <36> (Number)');
  },

  testComplexObjectEquals() {
    const complexMatcher = new matchers.ObjectEquals(
        {a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'baz', sub2: -1}});
    assertTrue(complexMatcher.matches(
        {a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'baz', sub2: -1}}));
    expectObjectEqualsFailure(
        complexMatcher,
        {a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'zap', sub2: -1}},
        'sub1: Expected <baz> (String) but was <zap> (String)');
    expectObjectEqualsFailure(
        complexMatcher,
        {a: 'foo', b: 2, c: ['bar', 6], d: {sub1: 'baz', sub2: -1}},
        'c[1]: Expected <3> (Number) but was <6> (Number)');
  },


  /**
     @suppress {strictMissingProperties} suppression added to enable type
     checking
   */
  testSaveArgument() {
    let saveMatcher = new matchers.SaveArgument();
    assertTrue(saveMatcher.matches(42));
    assertEquals(42, saveMatcher.arg);

    saveMatcher = new matchers.SaveArgument(x => typeof x === 'string');
    assertTrue(saveMatcher.matches('test'));
    assertEquals('test', saveMatcher.arg);
    assertFalse(saveMatcher.matches(17));
    assertEquals(17, saveMatcher.arg);
    assertArrayEquals(['test', 17], saveMatcher.allArgs);

    saveMatcher =
        new matchers.SaveArgument(new matchers.ObjectEquals({value: 'value'}));
    assertTrue(saveMatcher.matches({value: 'value'}));
    assertEquals('value', saveMatcher.arg.value);
    assertFalse(saveMatcher.matches('test'));
    assertEquals('test', saveMatcher.arg);
    assertArrayEquals([{value: 'value'}, 'test'], saveMatcher.allArgs);
  },


  testIsArray() {
    assertTrue(matchers.isArray.matches([]));
    assertTrue(matchers.isArray.matches(new Array()));
    assertFalse(matchers.isArray.matches('test'));

    assertFalse(matchers.isArray.matches({}, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isArray but was: <[object Object]> (Object)',
        mockExpect.errorMessages[0]);
  },


  testIsArrayLike() {
    const nodeList = (function() {
      const div = dom.createElement(TagName.DIV);
      div.appendChild(dom.createElement(TagName.P));
      div.appendChild(dom.createElement(TagName.P));
      return dom.getElementsByTagName(TagName.DIV, div);
    })();

    assertTrue(matchers.isArrayLike.matches([]));
    assertTrue(matchers.isArrayLike.matches(new Array()));
    assertTrue(matchers.isArrayLike.matches(nodeList));
    assertFalse(matchers.isArrayLike.matches('test'));

    assertFalse(matchers.isArrayLike.matches(3, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isArrayLike but was: <3> (Number)',
        mockExpect.errorMessages[0]);
  },


  testIsDateLike() {
    assertTrue(matchers.isDateLike.matches(new Date()));
    assertFalse(matchers.isDateLike.matches('test'));

    assertFalse(matchers.isDateLike.matches('test', mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isDateLike but was: <test> (String)',
        mockExpect.errorMessages[0]);
  },


  testIsString() {
    assertTrue(matchers.isString.matches('a'));
    assertTrue(matchers.isString.matches('b'));
    assertFalse(matchers.isString.matches(null));

    assertFalse(matchers.isString.matches(null, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isString but was: <null>', mockExpect.errorMessages[0]);
  },


  testIsBoolean() {
    assertTrue(matchers.isBoolean.matches(true));
    assertTrue(matchers.isBoolean.matches(false));
    assertFalse(matchers.isBoolean.matches(null));

    assertFalse(matchers.isBoolean.matches([], mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isBoolean but was: <> (Array)', mockExpect.errorMessages[0]);
  },


  testIsNumber() {
    assertTrue(matchers.isNumber.matches(-1));
    assertTrue(matchers.isNumber.matches(1));
    assertTrue(matchers.isNumber.matches(1.25));
    assertFalse(matchers.isNumber.matches(null));

    assertFalse(matchers.isNumber.matches('hello', mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isNumber but was: <hello> (String)',
        mockExpect.errorMessages[0]);
  },


  testIsFunction() {
    assertTrue(matchers.isFunction.matches(function() {}));
    assertFalse(matchers.isFunction.matches('test'));

    assertFalse(matchers.isFunction.matches({}, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isFunction but was: <[object Object]> (Object)',
        mockExpect.errorMessages[0]);
  },


  /** @suppress {checkTypes} suppression added to enable type checking */
  testIsObject() {
    assertTrue(matchers.isObject.matches({}));
    assertTrue(matchers.isObject.matches(new Object()));
    assertTrue(matchers.isObject.matches(new function() {}));
    assertTrue(matchers.isObject.matches([]));
    assertTrue(matchers.isObject.matches(new Array()));
    assertTrue(matchers.isObject.matches(function() {}));
    assertFalse(matchers.isObject.matches(null));

    assertFalse(matchers.isObject.matches(1234, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isObject but was: <1234> (Number)',
        mockExpect.errorMessages[0]);
  },


  testIsNodeLike() {
    assertFalse(matchers.isNodeLike.matches({}));
    assertFalse(matchers.isNodeLike.matches(1));
    assertFalse(matchers.isNodeLike.matches(function() {}));
    assertFalse(matchers.isNodeLike.matches(false));
    assertTrue(matchers.isNodeLike.matches(document.body));
    assertTrue(matchers.isNodeLike.matches(dom.getElement('someDiv')));

    assertFalse(matchers.isNodeLike.matches('test', mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected: isNodeLike but was: <test> (String)',
        mockExpect.errorMessages[0]);
  },


  /** @suppress {checkTypes} suppression added to enable type checking */
  testIgnoreArgumentsMatcher() {
    // ignoreArgument always returns true:
    assertTrue(matchers.ignoreArgument.matches());
    assertTrue(matchers.ignoreArgument.matches(356));
    assertTrue(matchers.ignoreArgument.matches('str'));
    assertTrue(matchers.ignoreArgument.matches(['array', 123, false]));
    assertTrue(matchers.ignoreArgument.matches({'map': 1, key2: 'value2'}));
  },


  testFlexibleArrayMatcher() {
    // Test that basic lists are verified properly.
    let a1 = [1, 'test'];
    let a2 = [1, 'test'];
    let a3 = [1, 'test', 'extra'];
    assertTrue(matchers.flexibleArrayMatcher(a1, a2));
    assertFalse(matchers.flexibleArrayMatcher(a1, a3));

    // Test that basic lists with basic class instances are verified properly.
    /** @suppress {checkTypes} suppression added to enable type checking */
    const instance = new foo();
    a1 = [1, 'test', instance];
    a2 = [1, 'test', instance];
    /** @suppress {checkTypes} suppression added to enable type checking */
    a3 = [1, 'test', new foo()];
    assertTrue(matchers.flexibleArrayMatcher(a1, a2));
    assertTrue(matchers.flexibleArrayMatcher(a1, a3));

    // Create an argument verifier that returns a consistent value.
    let verifyValue = true;
    const argVerifier = function() {};
    goog.inherits(argVerifier, matchers.ArgumentMatcher);
    argVerifier.prototype.matches = function(arg) {
      return verifyValue;
    };

    // Test that the arguments are always verified when the verifier returns
    // true.
    /** @suppress {checkTypes} suppression added to enable type checking */
    a1 = [1, 'test', new argVerifier()];
    a2 = [1, 'test', 'anything'];
    a3 = [1, 'test', 12345];
    assertTrue(matchers.flexibleArrayMatcher(a1, a2));
    assertTrue(matchers.flexibleArrayMatcher(a1, a3));

    // Now test the case when then verifier returns false.
    verifyValue = false;
    assertFalse(matchers.flexibleArrayMatcher(a1, a2));
    assertFalse(matchers.flexibleArrayMatcher(a1, a3));

    // And test we report errors back up via the opt_expectation
    assertFalse(matchers.flexibleArrayMatcher(a2, a3, mockExpect));
    assertEquals(1, mockExpect.errorMessages.length);
    assertEquals(
        'Expected <anything> (String) but was <12345> (Number)',
        mockExpect.errorMessages[0]);

    // And test we report errors found via the matcher
    a1 = [1, matchers.isString];
    a2 = [1, 'test string'];
    a3 = [1, null];
    assertTrue(matchers.flexibleArrayMatcher(a1, a2, mockExpect));
    assertFalse(matchers.flexibleArrayMatcher(a1, a3, mockExpect));
    // Old error is still there
    assertEquals(2, mockExpect.errorMessages.length);
    assertEquals(
        'Expected <anything> (String) but was <12345> (Number)',
        mockExpect.errorMessages[0]);
    // plus the new error...
    assertEquals(
        'Expected: isString but was: <null>', mockExpect.errorMessages[1]);
  },

});


/**
 * Utility method for checking for an ObjectEquals match failure.  Checks that
 * the expected error message was included in the error messages appended to
 * the expectation object.
 * @param {?} matcher
 *     The matcher to test against.
 * @param {!Object} matchObject The object to compare.
 * @param {string=} opt_errorMsg The deep object comparison failure message
 *     to check for.
 */
function expectObjectEqualsFailure(matcher, matchObject, opt_errorMsg) {
  mockExpect.errorMessages = [];
  assertFalse(matcher.matches(matchObject, mockExpect));
  assertNotEquals(0, mockExpect.getErrorMessageCount());
  if (opt_errorMsg) {
    assertContains(opt_errorMsg, mockExpect.errorMessages[0]);
  }
}