'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _slicedToArray = (function() {
  function sliceIterator(arr, i) {
    var _arr = [];
    var _n = true;
    var _d = false;
    var _e = undefined;
    try {
      for (
        var _i = arr[Symbol.iterator](), _s;
        !(_n = (_s = _i.next()).done);
        _n = true
      ) {
        _arr.push(_s.value);
        if (i && _arr.length === i) break;
      }
    } catch (err) {
      _d = true;
      _e = err;
    } finally {
      try {
        if (!_n && _i['return']) _i['return']();
      } finally {
        if (_d) throw _e;
      }
    }
    return _arr;
  }
  return function(arr, i) {
    if (Array.isArray(arr)) {
      return arr;
    } else if (Symbol.iterator in Object(arr)) {
      return sliceIterator(arr, i);
    } else {
      throw new TypeError(
        'Invalid attempt to destructure non-iterable instance'
      );
    }
  };
})();

var _jestMatcherUtils = require('jest-matcher-utils');

var _jasmine_utils = require('./jasmine_utils');

var _utils = require('./utils');

var _jestDiff = require('jest-diff');

var _jestDiff2 = _interopRequireDefault(_jestDiff);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : {default: obj};
}

const CALL_PRINT_LIMIT = 3;
/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 */

const RETURN_PRINT_LIMIT = 5;
const LAST_CALL_PRINT_LIMIT = 1;

const createToBeCalledMatcher = matcherName => (received, expected) => {
  (0, _jestMatcherUtils.ensureNoExpected)(expected, matcherName);
  ensureMock(received, matcherName);

  const receivedIsSpy = isSpy(received);
  const type = receivedIsSpy ? 'spy' : 'mock function';
  const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
  const identifier =
    receivedIsSpy || receivedName === 'jest.fn()'
      ? type
      : `${type} "${receivedName}"`;
  const count = receivedIsSpy
    ? received.calls.count()
    : received.mock.calls.length;
  const calls = receivedIsSpy
    ? received.calls.all().map(x => x.args)
    : received.mock.calls;
  const pass = count > 0;
  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)(
          '.not' + matcherName,
          receivedName,
          ''
        ) +
        '\n\n' +
        `Expected ${identifier} not to be called ` +
        formatReceivedCalls(calls, CALL_PRINT_LIMIT, {sameSentence: true})
    : () =>
        (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName, '') +
        '\n\n' +
        `Expected ${identifier} to have been called, but it was not called.`;

  return {message: message, pass: pass};
};

const createToReturnMatcher = matcherName => (received, expected) => {
  (0, _jestMatcherUtils.ensureNoExpected)(expected, matcherName);
  ensureMock(received, matcherName);

  const receivedName = received.getMockName();
  const identifier =
    receivedName === 'jest.fn()'
      ? 'mock function'
      : `mock function "${receivedName}"`;

  // List of return values that correspond only to calls that did not throw
  // an error
  const returnValues = received.mock.results
    .filter(result => !result.isThrow)
    .map(result => result.value);

  const count = returnValues.length;
  const pass = count > 0;

  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)(
          '.not' + matcherName,
          receivedName,
          ''
        ) +
        '\n\n' +
        `Expected ${identifier} not to have returned, but it returned:\n` +
        `  ${getPrintedReturnValues(returnValues, RETURN_PRINT_LIMIT)}`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName, '') +
        '\n\n' +
        `Expected ${identifier} to have returned.`;

  return {message: message, pass: pass};
};

const createToBeCalledTimesMatcher = matcherName => (received, expected) => {
  (0, _jestMatcherUtils.ensureExpectedIsNumber)(expected, matcherName);
  ensureMock(received, matcherName);

  const receivedIsSpy = isSpy(received);
  const type = receivedIsSpy ? 'spy' : 'mock function';
  const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
  const identifier =
    receivedIsSpy || receivedName === 'jest.fn()'
      ? type
      : `${type} "${receivedName}"`;
  const count = receivedIsSpy
    ? received.calls.count()
    : received.mock.calls.length;
  const pass = count === expected;
  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)(
          '.not' + matcherName,
          receivedName,
          String(expected)
        ) +
        `\n\n` +
        `Expected ${identifier} not to be called ` +
        `${(0, _jestMatcherUtils.EXPECTED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', expected)
        )}, but it was` +
        ` called exactly ${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', count)
        )}.`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(
          matcherName,
          receivedName,
          String(expected)
        ) +
        '\n\n' +
        `Expected ${identifier} to have been called ` +
        `${(0, _jestMatcherUtils.EXPECTED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', expected)
        )},` +
        ` but it was called ${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', count)
        )}.`;

  return {message: message, pass: pass};
};

const createToReturnTimesMatcher = matcherName => (received, expected) => {
  (0, _jestMatcherUtils.ensureExpectedIsNumber)(expected, matcherName);
  ensureMock(received, matcherName);

  const receivedName = received.getMockName();
  const identifier =
    receivedName === 'jest.fn()'
      ? 'mock function'
      : `mock function "${receivedName}"`;

  // List of return results that correspond only to calls that did not throw
  // an error
  const returnResults = received.mock.results.filter(result => !result.isThrow);

  const count = returnResults.length;
  const pass = count === expected;

  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)(
          '.not' + matcherName,
          receivedName,
          String(expected)
        ) +
        `\n\n` +
        `Expected ${identifier} not to have returned ` +
        `${(0, _jestMatcherUtils.EXPECTED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', expected)
        )}, but it` +
        ` returned exactly ${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', count)
        )}.`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(
          matcherName,
          receivedName,
          String(expected)
        ) +
        '\n\n' +
        `Expected ${identifier} to have returned ` +
        `${(0, _jestMatcherUtils.EXPECTED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', expected)
        )},` +
        ` but it returned ${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          (0, _jestMatcherUtils.pluralize)('time', count)
        )}.`;

  return {message: message, pass: pass};
};

const createToBeCalledWithMatcher = matcherName =>
  function(received) {
    for (
      var _len = arguments.length,
        expected = Array(_len > 1 ? _len - 1 : 0),
        _key = 1;
      _key < _len;
      _key++
    ) {
      expected[_key - 1] = arguments[_key];
    }

    ensureMock(received, matcherName);

    const receivedIsSpy = isSpy(received);
    const type = receivedIsSpy ? 'spy' : 'mock function';
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const identifier =
      receivedIsSpy || receivedName === 'jest.fn()'
        ? type
        : `${type} "${receivedName}"`;

    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;

    var _partition = (0, _utils.partition)(calls, call =>
        (0, _jasmine_utils.equals)(call, expected, [_utils.iterableEquality])
      ),
      _partition2 = _slicedToArray(_partition, 2);

    const match = _partition2[0],
      fail = _partition2[1];

    const pass = match.length > 0;

    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            '.not' + matcherName,
            receivedName
          ) +
          '\n\n' +
          `Expected ${identifier} not to have been called with:\n` +
          `  ${(0, _jestMatcherUtils.printExpected)(expected)}`
      : () =>
          (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
          '\n\n' +
          `Expected ${identifier} to have been called with:\n` +
          formatMismatchedCalls(fail, expected, CALL_PRINT_LIMIT);

    return {message: message, pass: pass};
  };

const createToReturnWithMatcher = matcherName => (received, expected) => {
  ensureMock(received, matcherName);

  const receivedName = received.getMockName();
  const identifier =
    receivedName === 'jest.fn()'
      ? 'mock function'
      : `mock function "${receivedName}"`;

  // List of return values that correspond only to calls that did not throw
  // an error
  const returnValues = received.mock.results
    .filter(result => !result.isThrow)
    .map(result => result.value);

  var _partition3 = (0, _utils.partition)(returnValues, value =>
      (0, _jasmine_utils.equals)(expected, value, [_utils.iterableEquality])
    ),
    _partition4 = _slicedToArray(_partition3, 1);

  const match = _partition4[0];

  const pass = match.length > 0;

  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)('.not' + matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} not to have returned:\n` +
        `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
        `But it returned exactly:\n` +
        `  ${(0, _jestMatcherUtils.printReceived)(expected)}`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} to have returned:\n` +
        formatMismatchedReturnValues(
          returnValues,
          expected,
          RETURN_PRINT_LIMIT
        );

  return {message: message, pass: pass};
};

const createLastCalledWithMatcher = matcherName =>
  function(received) {
    for (
      var _len2 = arguments.length,
        expected = Array(_len2 > 1 ? _len2 - 1 : 0),
        _key2 = 1;
      _key2 < _len2;
      _key2++
    ) {
      expected[_key2 - 1] = arguments[_key2];
    }

    ensureMock(received, matcherName);

    const receivedIsSpy = isSpy(received);
    const type = receivedIsSpy ? 'spy' : 'mock function';
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const identifier =
      receivedIsSpy || receivedName === 'jest.fn()'
        ? type
        : `${type} "${receivedName}"`;
    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;
    const pass = (0, _jasmine_utils.equals)(calls[calls.length - 1], expected, [
      _utils.iterableEquality
    ]);

    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            '.not' + matcherName,
            receivedName
          ) +
          '\n\n' +
          `Expected ${identifier} to not have been last called with:\n` +
          `  ${(0, _jestMatcherUtils.printExpected)(expected)}`
      : () =>
          (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
          '\n\n' +
          `Expected ${identifier} to have been last called with:\n` +
          formatMismatchedCalls(calls, expected, LAST_CALL_PRINT_LIMIT);

    return {message: message, pass: pass};
  };

const createLastReturnedMatcher = matcherName => (received, expected) => {
  ensureMock(received, matcherName);

  const receivedName = received.getMockName();
  const identifier =
    receivedName === 'jest.fn()'
      ? 'mock function'
      : `mock function "${receivedName}"`;

  const results = received.mock.results;
  const lastResult = results[results.length - 1];
  const pass =
    !!lastResult &&
    !lastResult.isThrow &&
    (0, _jasmine_utils.equals)(lastResult.value, expected, [
      _utils.iterableEquality
    ]);

  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)('.not' + matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} to not have last returned:\n` +
        `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
        `But it last returned exactly:\n` +
        `  ${(0, _jestMatcherUtils.printReceived)(lastResult.value)}`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} to have last returned:\n` +
        `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
        (!lastResult
          ? `But it was ${(0, _jestMatcherUtils.RECEIVED_COLOR)('not called')}`
          : lastResult.isThrow
            ? `But the last call ${(0, _jestMatcherUtils.RECEIVED_COLOR)(
                'threw an error'
              )}`
            : `But the last call returned:\n  ${(0,
              _jestMatcherUtils.printReceived)(lastResult.value)}`);

  return {message: message, pass: pass};
};

const createNthCalledWithMatcher = matcherName =>
  function(received, nth) {
    for (
      var _len3 = arguments.length,
        expected = Array(_len3 > 2 ? _len3 - 2 : 0),
        _key3 = 2;
      _key3 < _len3;
      _key3++
    ) {
      expected[_key3 - 2] = arguments[_key3];
    }

    ensureMock(received, matcherName);

    const receivedIsSpy = isSpy(received);
    const type = receivedIsSpy ? 'spy' : 'mock function';

    if (typeof nth !== 'number' || parseInt(nth, 10) !== nth || nth < 1) {
      const message = () =>
        `nth value ${(0, _jestMatcherUtils.printReceived)(
          nth
        )} must be a positive integer greater than ${(0,
        _jestMatcherUtils.printExpected)(0)}`;
      const pass = false;
      return {message: message, pass: pass};
    }

    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const identifier =
      receivedIsSpy || receivedName === 'jest.fn()'
        ? type
        : `${type} "${receivedName}"`;
    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;
    const pass = (0, _jasmine_utils.equals)(calls[nth - 1], expected, [
      _utils.iterableEquality
    ]);

    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            '.not' + matcherName,
            receivedName
          ) +
          '\n\n' +
          `Expected ${identifier} ${nthToString(
            nth
          )} call to not have been called with:\n` +
          `  ${(0, _jestMatcherUtils.printExpected)(expected)}`
      : () =>
          (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
          '\n\n' +
          `Expected ${identifier} ${nthToString(
            nth
          )} call to have been called with:\n` +
          formatMismatchedCalls(
            calls[nth - 1] ? [calls[nth - 1]] : [],
            expected,
            LAST_CALL_PRINT_LIMIT
          );

    return {message: message, pass: pass};
  };

const createNthReturnedWithMatcher = matcherName => (
  received,
  nth,
  expected
) => {
  ensureMock(received, matcherName);

  if (typeof nth !== 'number' || parseInt(nth, 10) !== nth || nth < 1) {
    const message = () =>
      `nth value ${(0, _jestMatcherUtils.printReceived)(
        nth
      )} must be a positive integer greater than ${(0,
      _jestMatcherUtils.printExpected)(0)}`;
    const pass = false;
    return {message: message, pass: pass};
  }

  const receivedName = received.getMockName();
  const identifier =
    receivedName === 'jest.fn()'
      ? 'mock function'
      : `mock function "${receivedName}"`;

  const results = received.mock.results;
  const nthResult = results[nth - 1];
  const pass =
    !!nthResult &&
    !nthResult.isThrow &&
    (0, _jasmine_utils.equals)(nthResult.value, expected, [
      _utils.iterableEquality
    ]);
  const nthString = nthToString(nth);
  const message = pass
    ? () =>
        (0, _jestMatcherUtils.matcherHint)('.not' + matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} ${nthString} call to not have returned with:\n` +
        `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
        `But the ${nthString} call returned exactly:\n` +
        `  ${(0, _jestMatcherUtils.printReceived)(nthResult.value)}`
    : () =>
        (0, _jestMatcherUtils.matcherHint)(matcherName, receivedName) +
        '\n\n' +
        `Expected ${identifier} ${nthString} call to have returned with:\n` +
        `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
        (results.length === 0
          ? `But it was ${(0, _jestMatcherUtils.RECEIVED_COLOR)('not called')}`
          : nth > results.length
            ? `But it was only called ${(0, _jestMatcherUtils.printReceived)(
                results.length
              )} times`
            : nthResult.isThrow
              ? `But the ${nthString} call ${(0,
                _jestMatcherUtils.RECEIVED_COLOR)('threw an error')}`
              : `But the ${nthString} call returned with:\n  ${(0,
                _jestMatcherUtils.printReceived)(nthResult.value)}`);

  return {message: message, pass: pass};
};

const spyMatchers = {
  lastCalledWith: createLastCalledWithMatcher('.lastCalledWith'),
  lastReturnedWith: createLastReturnedMatcher('.lastReturnedWith'),
  nthCalledWith: createNthCalledWithMatcher('.nthCalledWith'),
  nthReturnedWith: createNthReturnedWithMatcher('.nthReturnedWith'),
  toBeCalled: createToBeCalledMatcher('.toBeCalled'),
  toBeCalledTimes: createToBeCalledTimesMatcher('.toBeCalledTimes'),
  toBeCalledWith: createToBeCalledWithMatcher('.toBeCalledWith'),
  toHaveBeenCalled: createToBeCalledMatcher('.toHaveBeenCalled'),
  toHaveBeenCalledTimes: createToBeCalledTimesMatcher('.toHaveBeenCalledTimes'),
  toHaveBeenCalledWith: createToBeCalledWithMatcher('.toHaveBeenCalledWith'),
  toHaveBeenLastCalledWith: createLastCalledWithMatcher(
    '.toHaveBeenLastCalledWith'
  ),
  toHaveBeenNthCalledWith: createNthCalledWithMatcher(
    '.toHaveBeenNthCalledWith'
  ),
  toHaveLastReturnedWith: createLastReturnedMatcher('.toHaveLastReturnedWith'),
  toHaveNthReturnedWith: createNthReturnedWithMatcher('.toHaveNthReturnedWith'),
  toHaveReturned: createToReturnMatcher('.toHaveReturned'),
  toHaveReturnedTimes: createToReturnTimesMatcher('.toHaveReturnedTimes'),
  toHaveReturnedWith: createToReturnWithMatcher('.toHaveReturnedWith'),
  toReturn: createToReturnMatcher('.toReturn'),
  toReturnTimes: createToReturnTimesMatcher('.toReturnTimes'),
  toReturnWith: createToReturnWithMatcher('.toReturnWith')
};

const isSpy = spy => spy.calls && typeof spy.calls.count === 'function';

const ensureMock = (mockOrSpy, matcherName) => {
  if (
    !mockOrSpy ||
    ((mockOrSpy.calls === undefined || mockOrSpy.calls.all === undefined) &&
      mockOrSpy._isMockFunction !== true)
  ) {
    throw new Error(
      (0, _jestMatcherUtils.matcherHint)(
        '[.not]' + matcherName,
        'jest.fn()',
        ''
      ) +
        '\n\n' +
        `${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          'jest.fn()'
        )} value must be a mock function ` +
        `or spy.\n` +
        (0, _jestMatcherUtils.printWithType)(
          'Received',
          mockOrSpy,
          _jestMatcherUtils.printReceived
        )
    );
  }
};

const getPrintedCalls = (calls, limit, sep, fn) => {
  const result = [];
  let i = calls.length;

  while (--i >= 0 && --limit >= 0) {
    result.push(fn(calls[i]));
  }

  return result.join(sep);
};

const getPrintedReturnValues = (calls, limit) => {
  const result = [];

  for (let i = 0; i < calls.length && i < limit; i += 1) {
    result.push((0, _jestMatcherUtils.printReceived)(calls[i]));
  }

  if (calls.length > limit) {
    result.push(
      `...and ${(0, _jestMatcherUtils.printReceived)(
        calls.length - limit
      )} more`
    );
  }

  return result.join('\n\n  ');
};

const formatReceivedCalls = (calls, limit, options) => {
  if (calls.length) {
    const but = options && options.sameSentence ? 'but' : 'But';
    const count = calls.length - limit;
    const printedCalls = getPrintedCalls(
      calls,
      limit,
      ', ',
      _jestMatcherUtils.printReceived
    );
    return (
      `${but} it was called ` +
      `with:\n  ` +
      printedCalls +
      (count > 0
        ? '\nand ' +
          (0, _jestMatcherUtils.RECEIVED_COLOR)(
            (0, _jestMatcherUtils.pluralize)('more call', count)
          ) +
          '.'
        : '')
    );
  } else {
    return `But it was ${(0, _jestMatcherUtils.RECEIVED_COLOR)('not called')}.`;
  }
};

const formatMismatchedCalls = (calls, expected, limit) => {
  if (calls.length) {
    return getPrintedCalls(
      calls,
      limit,
      '\n\n',
      formatMismatchedArgs.bind(null, expected)
    );
  } else {
    return (
      `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
      `But it was ${(0, _jestMatcherUtils.RECEIVED_COLOR)('not called')}.`
    );
  }
};

const formatMismatchedReturnValues = (returnValues, expected, limit) => {
  if (returnValues.length) {
    return (
      `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
      `But it returned:\n` +
      `  ${getPrintedReturnValues(returnValues, limit)}`
    );
  } else {
    return (
      `  ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
      `But it did ${(0, _jestMatcherUtils.RECEIVED_COLOR)('not return')}.`
    );
  }
};

const formatMismatchedArgs = (expected, received) => {
  const length = Math.max(expected.length, received.length);

  const printedArgs = [];
  for (let i = 0; i < length; i++) {
    if (
      !(0, _jasmine_utils.equals)(expected[i], received[i], [
        _utils.iterableEquality
      ])
    ) {
      const oneline = (0, _utils.isOneline)(expected[i], received[i]);
      const diffString = (0, _jestDiff2.default)(expected[i], received[i]);
      printedArgs.push(
        `  ${(0, _jestMatcherUtils.printExpected)(expected[i])}\n` +
          `as argument ${i + 1}, but it was called with\n` +
          `  ${(0, _jestMatcherUtils.printReceived)(received[i])}.` +
          (diffString && !oneline ? `\n\nDifference:\n\n${diffString}` : '')
      );
    } else if (i >= expected.length) {
      printedArgs.push(
        `  Did not expect argument ${i + 1} ` +
          `but it was called with ${(0, _jestMatcherUtils.printReceived)(
            received[i]
          )}.`
      );
    }
  }

  return printedArgs.join('\n');
};

const nthToString = nth => {
  switch (nth) {
    case 1:
      return 'first';
    case 2:
      return 'second';
    case 3:
      return 'third';
  }
  return `${nth}th`;
};

exports.default = spyMatchers;
