'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.default = void 0;

var _jestGetType = require('jest-get-type');

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

var _jasmineUtils = require('./jasmineUtils');

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

/**
 * Copyright (c) Facebook, Inc. and its affiliates. 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.
 */
// The optional property of matcher context is true if undefined.
const isExpand = expand => expand !== false;

const PRINT_LIMIT = 3;
const NO_ARGUMENTS = 'called with 0 arguments';

const printExpectedArgs = expected =>
  expected.length === 0
    ? NO_ARGUMENTS
    : expected.map(arg => (0, _jestMatcherUtils.printExpected)(arg)).join(', ');

const printReceivedArgs = (received, expected) =>
  received.length === 0
    ? NO_ARGUMENTS
    : received
        .map((arg, i) =>
          Array.isArray(expected) &&
          i < expected.length &&
          isEqualValue(expected[i], arg)
            ? printCommon(arg)
            : (0, _jestMatcherUtils.printReceived)(arg)
        )
        .join(', ');

const printCommon = val =>
  (0, _jestMatcherUtils.DIM_COLOR)((0, _jestMatcherUtils.stringify)(val));

const isEqualValue = (expected, received) =>
  (0, _jasmineUtils.equals)(expected, received, [_utils.iterableEquality]);

const isEqualCall = (expected, received) => isEqualValue(expected, received);

const isEqualReturn = (expected, result) =>
  result.type === 'return' && isEqualValue(expected, result.value);

const countReturns = results =>
  results.reduce((n, result) => (result.type === 'return' ? n + 1 : n), 0);

const printNumberOfReturns = (countReturns, countCalls) =>
  `\nNumber of returns: ${(0, _jestMatcherUtils.printReceived)(countReturns)}` +
  (countCalls !== countReturns
    ? `\nNumber of calls:   ${(0, _jestMatcherUtils.printReceived)(countCalls)}`
    : '');

// Given a label, return a function which given a string,
// right-aligns it preceding the colon in the label.
const getRightAlignedPrinter = label => {
  // Assume that the label contains a colon.
  const index = label.indexOf(':');
  const suffix = label.slice(index);
  return (string, isExpectedCall) =>
    (isExpectedCall
      ? '->' + ' '.repeat(Math.max(0, index - 2 - string.length))
      : ' '.repeat(Math.max(index - string.length))) +
    string +
    suffix;
};

const printReceivedCallsNegative = (
  expected,
  indexedCalls,
  isOnlyCall,
  iExpectedCall
) => {
  if (indexedCalls.length === 0) {
    return '';
  }

  const label = 'Received:     ';

  if (isOnlyCall) {
    return label + printReceivedArgs(indexedCalls[0], expected) + '\n';
  }

  const printAligned = getRightAlignedPrinter(label);
  return (
    'Received\n' +
    indexedCalls.reduce(
      (printed, [i, args]) =>
        printed +
        printAligned(String(i + 1), i === iExpectedCall) +
        printReceivedArgs(args, expected) +
        '\n',
      ''
    )
  );
};

const printExpectedReceivedCallsPositive = (
  expected,
  indexedCalls,
  expand,
  isOnlyCall,
  iExpectedCall
) => {
  const expectedLine = `Expected: ${printExpectedArgs(expected)}\n`;

  if (indexedCalls.length === 0) {
    return expectedLine;
  }

  const label = 'Received: ';

  if (isOnlyCall && (iExpectedCall === 0 || iExpectedCall === undefined)) {
    const received = indexedCalls[0][1];

    if (isLineDiffableCall(expected, received)) {
      // Display diff without indentation.
      const lines = [
        (0, _jestMatcherUtils.EXPECTED_COLOR)('- Expected'),
        (0, _jestMatcherUtils.RECEIVED_COLOR)('+ Received'),
        ''
      ];
      const length = Math.max(expected.length, received.length);

      for (let i = 0; i < length; i += 1) {
        if (i < expected.length && i < received.length) {
          if (isEqualValue(expected[i], received[i])) {
            lines.push(`  ${printCommon(received[i])},`);
            continue;
          }

          if (isLineDiffableArg(expected[i], received[i])) {
            const difference = (0, _jestMatcherUtils.diff)(
              expected[i],
              received[i],
              {
                expand
              }
            );

            if (
              typeof difference === 'string' &&
              difference.includes('- Expected') &&
              difference.includes('+ Received')
            ) {
              // Omit annotation in case multiple args have diff.
              lines.push(difference.split('\n').slice(3).join('\n') + ',');
              continue;
            }
          }
        }

        if (i < expected.length) {
          lines.push(
            (0, _jestMatcherUtils.EXPECTED_COLOR)(
              '- ' + (0, _jestMatcherUtils.stringify)(expected[i])
            ) + ','
          );
        }

        if (i < received.length) {
          lines.push(
            (0, _jestMatcherUtils.RECEIVED_COLOR)(
              '+ ' + (0, _jestMatcherUtils.stringify)(received[i])
            ) + ','
          );
        }
      }

      return lines.join('\n') + '\n';
    }

    return expectedLine + label + printReceivedArgs(received, expected) + '\n';
  }

  const printAligned = getRightAlignedPrinter(label);
  return (
    expectedLine +
    'Received\n' +
    indexedCalls.reduce((printed, [i, received]) => {
      const aligned = printAligned(String(i + 1), i === iExpectedCall);
      return (
        printed +
        ((i === iExpectedCall || iExpectedCall === undefined) &&
        isLineDiffableCall(expected, received)
          ? aligned.replace(': ', '\n') +
            printDiffCall(expected, received, expand)
          : aligned + printReceivedArgs(received, expected)) +
        '\n'
      );
    }, '')
  );
};

const indentation = 'Received'.replace(/\w/g, ' ');

const printDiffCall = (expected, received, expand) =>
  received
    .map((arg, i) => {
      if (i < expected.length) {
        if (isEqualValue(expected[i], arg)) {
          return indentation + '  ' + printCommon(arg) + ',';
        }

        if (isLineDiffableArg(expected[i], arg)) {
          const difference = (0, _jestMatcherUtils.diff)(expected[i], arg, {
            expand
          });

          if (
            typeof difference === 'string' &&
            difference.includes('- Expected') &&
            difference.includes('+ Received')
          ) {
            // Display diff with indentation.
            // Omit annotation in case multiple args have diff.
            return (
              difference
                .split('\n')
                .slice(3)
                .map(line => indentation + line)
                .join('\n') + ','
            );
          }
        }
      } // Display + only if received arg has no corresponding expected arg.

      return (
        indentation +
        (i < expected.length
          ? '  ' + (0, _jestMatcherUtils.printReceived)(arg)
          : (0, _jestMatcherUtils.RECEIVED_COLOR)(
              '+ ' + (0, _jestMatcherUtils.stringify)(arg)
            )) +
        ','
      );
    })
    .join('\n');

const isLineDiffableCall = (expected, received) =>
  expected.some(
    (arg, i) => i < received.length && isLineDiffableArg(arg, received[i])
  ); // Almost redundant with function in jest-matcher-utils,
// except no line diff for any strings.

const isLineDiffableArg = (expected, received) => {
  const expectedType = (0, _jestGetType.getType)(expected);
  const receivedType = (0, _jestGetType.getType)(received);

  if (expectedType !== receivedType) {
    return false;
  }

  if ((0, _jestGetType.isPrimitive)(expected)) {
    return false;
  }

  if (
    expectedType === 'date' ||
    expectedType === 'function' ||
    expectedType === 'regexp'
  ) {
    return false;
  }

  if (expected instanceof Error && received instanceof Error) {
    return false;
  }

  if (
    expectedType === 'object' &&
    typeof expected.asymmetricMatch === 'function'
  ) {
    return false;
  }

  if (
    receivedType === 'object' &&
    typeof received.asymmetricMatch === 'function'
  ) {
    return false;
  }

  return true;
};

const printResult = (result, expected) =>
  result.type === 'throw'
    ? 'function call threw an error'
    : result.type === 'incomplete'
    ? 'function call has not returned yet'
    : isEqualValue(expected, result.value)
    ? printCommon(result.value)
    : (0, _jestMatcherUtils.printReceived)(result.value);

// Return either empty string or one line per indexed result,
// so additional empty line can separate from `Number of returns` which follows.
const printReceivedResults = (
  label,
  expected,
  indexedResults,
  isOnlyCall,
  iExpectedCall
) => {
  if (indexedResults.length === 0) {
    return '';
  }

  if (isOnlyCall && (iExpectedCall === 0 || iExpectedCall === undefined)) {
    return label + printResult(indexedResults[0][1], expected) + '\n';
  }

  const printAligned = getRightAlignedPrinter(label);
  return (
    label.replace(':', '').trim() +
    '\n' +
    indexedResults.reduce(
      (printed, [i, result]) =>
        printed +
        printAligned(String(i + 1), i === iExpectedCall) +
        printResult(result, expected) +
        '\n',
      ''
    )
  );
};

const createToBeCalledMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = '';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    (0, _jestMatcherUtils.ensureNoExpected)(expected, matcherName, options);
    ensureMockOrSpy(received, matcherName, expectedArgument, options);
    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    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)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of calls: ${(0, _jestMatcherUtils.printExpected)(
            0
          )}\n` +
          `Received number of calls: ${(0, _jestMatcherUtils.printReceived)(
            count
          )}\n\n` +
          calls
            .reduce((lines, args, i) => {
              if (lines.length < PRINT_LIMIT) {
                lines.push(`${i + 1}: ${printReceivedArgs(args)}`);
              }

              return lines;
            }, [])
            .join('\n')
      : () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of calls: >= ${(0, _jestMatcherUtils.printExpected)(
            1
          )}\n` +
          `Received number of calls:    ${(0, _jestMatcherUtils.printReceived)(
            count
          )}`;
    return {
      message,
      pass
    };
  };

const createToReturnMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = '';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    (0, _jestMatcherUtils.ensureNoExpected)(expected, matcherName, options);
    ensureMock(received, matcherName, expectedArgument, options);
    const receivedName = received.getMockName(); // Count return values that correspond only to calls that returned

    const count = received.mock.results.reduce(
      (n, result) => (result.type === 'return' ? n + 1 : n),
      0
    );
    const pass = count > 0;
    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of returns: ${(0, _jestMatcherUtils.printExpected)(
            0
          )}\n` +
          `Received number of returns: ${(0, _jestMatcherUtils.printReceived)(
            count
          )}\n\n` +
          received.mock.results
            .reduce((lines, result, i) => {
              if (result.type === 'return' && lines.length < PRINT_LIMIT) {
                lines.push(
                  `${i + 1}: ${(0, _jestMatcherUtils.printReceived)(
                    result.value
                  )}`
                );
              }

              return lines;
            }, [])
            .join('\n') +
          (received.mock.calls.length !== count
            ? `\n\nReceived number of calls:   ${(0,
              _jestMatcherUtils.printReceived)(received.mock.calls.length)}`
            : '')
      : () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of returns: >= ${(0,
          _jestMatcherUtils.printExpected)(1)}\n` +
          `Received number of returns:    ${(0,
          _jestMatcherUtils.printReceived)(count)}` +
          (received.mock.calls.length !== count
            ? `\nReceived number of calls:      ${(0,
              _jestMatcherUtils.printReceived)(received.mock.calls.length)}`
            : '');
    return {
      message,
      pass
    };
  };

const createToBeCalledTimesMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = 'expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    (0, _jestMatcherUtils.ensureExpectedIsNonNegativeInteger)(
      expected,
      matcherName,
      options
    );
    ensureMockOrSpy(received, matcherName, expectedArgument, options);
    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const count = receivedIsSpy
      ? received.calls.count()
      : received.mock.calls.length;
    const pass = count === expected;
    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of calls: not ${(0, _jestMatcherUtils.printExpected)(
            expected
          )}`
      : () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of calls: ${(0, _jestMatcherUtils.printExpected)(
            expected
          )}\n` +
          `Received number of calls: ${(0, _jestMatcherUtils.printReceived)(
            count
          )}`;
    return {
      message,
      pass
    };
  };

const createToReturnTimesMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = 'expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    (0, _jestMatcherUtils.ensureExpectedIsNonNegativeInteger)(
      expected,
      matcherName,
      options
    );
    ensureMock(received, matcherName, expectedArgument, options);
    const receivedName = received.getMockName(); // Count return values that correspond only to calls that returned

    const count = received.mock.results.reduce(
      (n, result) => (result.type === 'return' ? n + 1 : n),
      0
    );
    const pass = count === expected;
    const message = pass
      ? () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of returns: not ${(0,
          _jestMatcherUtils.printExpected)(expected)}` +
          (received.mock.calls.length !== count
            ? `\n\nReceived number of calls:       ${(0,
              _jestMatcherUtils.printReceived)(received.mock.calls.length)}`
            : '')
      : () =>
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            receivedName,
            expectedArgument,
            options
          ) +
          '\n\n' +
          `Expected number of returns: ${(0, _jestMatcherUtils.printExpected)(
            expected
          )}\n` +
          `Received number of returns: ${(0, _jestMatcherUtils.printReceived)(
            count
          )}` +
          (received.mock.calls.length !== count
            ? `\nReceived number of calls:   ${(0,
              _jestMatcherUtils.printReceived)(received.mock.calls.length)}`
            : '');
    return {
      message,
      pass
    };
  };

const createToBeCalledWithMatcher = matcherName =>
  function (received, ...expected) {
    const expectedArgument = '...expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    ensureMockOrSpy(received, matcherName, expectedArgument, options);
    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;
    const pass = calls.some(call => isEqualCall(expected, call));
    const message = pass
      ? () => {
          // Some examples of calls that are equal to expected value.
          const indexedCalls = [];
          let i = 0;

          while (i < calls.length && indexedCalls.length < PRINT_LIMIT) {
            if (isEqualCall(expected, calls[i])) {
              indexedCalls.push([i, calls[i]]);
            }

            i += 1;
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: not ${printExpectedArgs(expected)}\n` +
            (calls.length === 1 &&
            (0, _jestMatcherUtils.stringify)(calls[0]) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedCallsNegative(
                  expected,
                  indexedCalls,
                  calls.length === 1
                )) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        }
      : () => {
          // Some examples of calls that are not equal to expected value.
          const indexedCalls = [];
          let i = 0;

          while (i < calls.length && indexedCalls.length < PRINT_LIMIT) {
            indexedCalls.push([i, calls[i]]);
            i += 1;
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            printExpectedReceivedCallsPositive(
              expected,
              indexedCalls,
              isExpand(this.expand),
              calls.length === 1
            ) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        };
    return {
      message,
      pass
    };
  };

const createToReturnWithMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = 'expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    ensureMock(received, matcherName, expectedArgument, options);
    const receivedName = received.getMockName();
    const {calls, results} = received.mock;
    const pass = results.some(result => isEqualReturn(expected, result));
    const message = pass
      ? () => {
          // Some examples of results that are equal to expected value.
          const indexedResults = [];
          let i = 0;

          while (i < results.length && indexedResults.length < PRINT_LIMIT) {
            if (isEqualReturn(expected, results[i])) {
              indexedResults.push([i, results[i]]);
            }

            i += 1;
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: not ${(0, _jestMatcherUtils.printExpected)(
              expected
            )}\n` +
            (results.length === 1 &&
            results[0].type === 'return' &&
            (0, _jestMatcherUtils.stringify)(results[0].value) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedResults(
                  'Received:     ',
                  expected,
                  indexedResults,
                  results.length === 1
                )) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        }
      : () => {
          // Some examples of results that are not equal to expected value.
          const indexedResults = [];
          let i = 0;

          while (i < results.length && indexedResults.length < PRINT_LIMIT) {
            indexedResults.push([i, results[i]]);
            i += 1;
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
            printReceivedResults(
              'Received: ',
              expected,
              indexedResults,
              results.length === 1
            ) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        };
    return {
      message,
      pass
    };
  };

const createLastCalledWithMatcher = matcherName =>
  function (received, ...expected) {
    const expectedArgument = '...expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    ensureMockOrSpy(received, matcherName, expectedArgument, options);
    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;
    const iLast = calls.length - 1;
    const pass = iLast >= 0 && isEqualCall(expected, calls[iLast]);
    const message = pass
      ? () => {
          const indexedCalls = [];

          if (iLast > 0) {
            // Display preceding call as context.
            indexedCalls.push([iLast - 1, calls[iLast - 1]]);
          }

          indexedCalls.push([iLast, calls[iLast]]);
          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: not ${printExpectedArgs(expected)}\n` +
            (calls.length === 1 &&
            (0, _jestMatcherUtils.stringify)(calls[0]) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedCallsNegative(
                  expected,
                  indexedCalls,
                  calls.length === 1,
                  iLast
                )) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        }
      : () => {
          const indexedCalls = [];

          if (iLast >= 0) {
            if (iLast > 0) {
              let i = iLast - 1; // Is there a preceding call that is equal to expected args?

              while (i >= 0 && !isEqualCall(expected, calls[i])) {
                i -= 1;
              }

              if (i < 0) {
                i = iLast - 1; // otherwise, preceding call
              }

              indexedCalls.push([i, calls[i]]);
            }

            indexedCalls.push([iLast, calls[iLast]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            printExpectedReceivedCallsPositive(
              expected,
              indexedCalls,
              isExpand(this.expand),
              calls.length === 1,
              iLast
            ) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        };
    return {
      message,
      pass
    };
  };

const createLastReturnedMatcher = matcherName =>
  function (received, expected) {
    const expectedArgument = 'expected';
    const options = {
      isNot: this.isNot,
      promise: this.promise
    };
    ensureMock(received, matcherName, expectedArgument, options);
    const receivedName = received.getMockName();
    const {calls, results} = received.mock;
    const iLast = results.length - 1;
    const pass = iLast >= 0 && isEqualReturn(expected, results[iLast]);
    const message = pass
      ? () => {
          const indexedResults = [];

          if (iLast > 0) {
            // Display preceding result as context.
            indexedResults.push([iLast - 1, results[iLast - 1]]);
          }

          indexedResults.push([iLast, results[iLast]]);
          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: not ${(0, _jestMatcherUtils.printExpected)(
              expected
            )}\n` +
            (results.length === 1 &&
            results[0].type === 'return' &&
            (0, _jestMatcherUtils.stringify)(results[0].value) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedResults(
                  'Received:     ',
                  expected,
                  indexedResults,
                  results.length === 1,
                  iLast
                )) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        }
      : () => {
          const indexedResults = [];

          if (iLast >= 0) {
            if (iLast > 0) {
              let i = iLast - 1; // Is there a preceding result that is equal to expected value?

              while (i >= 0 && !isEqualReturn(expected, results[i])) {
                i -= 1;
              }

              if (i < 0) {
                i = iLast - 1; // otherwise, preceding result
              }

              indexedResults.push([i, results[i]]);
            }

            indexedResults.push([iLast, results[iLast]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `Expected: ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
            printReceivedResults(
              'Received: ',
              expected,
              indexedResults,
              results.length === 1,
              iLast
            ) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        };
    return {
      message,
      pass
    };
  };

const createNthCalledWithMatcher = matcherName =>
  function (received, nth, ...expected) {
    const expectedArgument = 'n';
    const options = {
      expectedColor: arg => arg,
      isNot: this.isNot,
      promise: this.promise,
      secondArgument: '...expected'
    };
    ensureMockOrSpy(received, matcherName, expectedArgument, options);

    if (!Number.isSafeInteger(nth) || nth < 1) {
      throw new Error(
        (0, _jestMatcherUtils.matcherErrorMessage)(
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            undefined,
            expectedArgument,
            options
          ),
          `${expectedArgument} must be a positive integer`,
          (0, _jestMatcherUtils.printWithType)(
            expectedArgument,
            nth,
            _jestMatcherUtils.stringify
          )
        )
      );
    }

    const receivedIsSpy = isSpy(received);
    const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
    const calls = receivedIsSpy
      ? received.calls.all().map(x => x.args)
      : received.mock.calls;
    const length = calls.length;
    const iNth = nth - 1;
    const pass = iNth < length && isEqualCall(expected, calls[iNth]);
    const message = pass
      ? () => {
          // Display preceding and following calls,
          // in case assertions fails because index is off by one.
          const indexedCalls = [];

          if (iNth - 1 >= 0) {
            indexedCalls.push([iNth - 1, calls[iNth - 1]]);
          }

          indexedCalls.push([iNth, calls[iNth]]);

          if (iNth + 1 < length) {
            indexedCalls.push([iNth + 1, calls[iNth + 1]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `n: ${nth}\n` +
            `Expected: not ${printExpectedArgs(expected)}\n` +
            (calls.length === 1 &&
            (0, _jestMatcherUtils.stringify)(calls[0]) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedCallsNegative(
                  expected,
                  indexedCalls,
                  calls.length === 1,
                  iNth
                )) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        }
      : () => {
          // Display preceding and following calls:
          // * nearest call that is equal to expected args
          // * otherwise, adjacent call
          // in case assertions fails because of index, especially off by one.
          const indexedCalls = [];

          if (iNth < length) {
            if (iNth - 1 >= 0) {
              let i = iNth - 1; // Is there a preceding call that is equal to expected args?

              while (i >= 0 && !isEqualCall(expected, calls[i])) {
                i -= 1;
              }

              if (i < 0) {
                i = iNth - 1; // otherwise, adjacent call
              }

              indexedCalls.push([i, calls[i]]);
            }

            indexedCalls.push([iNth, calls[iNth]]);

            if (iNth + 1 < length) {
              let i = iNth + 1; // Is there a following call that is equal to expected args?

              while (i < length && !isEqualCall(expected, calls[i])) {
                i += 1;
              }

              if (i >= length) {
                i = iNth + 1; // otherwise, adjacent call
              }

              indexedCalls.push([i, calls[i]]);
            }
          } else if (length > 0) {
            // The number of received calls is fewer than the expected number.
            let i = length - 1; // Is there a call that is equal to expected args?

            while (i >= 0 && !isEqualCall(expected, calls[i])) {
              i -= 1;
            }

            if (i < 0) {
              i = length - 1; // otherwise, last call
            }

            indexedCalls.push([i, calls[i]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `n: ${nth}\n` +
            printExpectedReceivedCallsPositive(
              expected,
              indexedCalls,
              isExpand(this.expand),
              calls.length === 1,
              iNth
            ) +
            `\nNumber of calls: ${(0, _jestMatcherUtils.printReceived)(
              calls.length
            )}`
          );
        };
    return {
      message,
      pass
    };
  };

const createNthReturnedWithMatcher = matcherName =>
  function (received, nth, expected) {
    const expectedArgument = 'n';
    const options = {
      expectedColor: arg => arg,
      isNot: this.isNot,
      promise: this.promise,
      secondArgument: 'expected'
    };
    ensureMock(received, matcherName, expectedArgument, options);

    if (!Number.isSafeInteger(nth) || nth < 1) {
      throw new Error(
        (0, _jestMatcherUtils.matcherErrorMessage)(
          (0, _jestMatcherUtils.matcherHint)(
            matcherName,
            undefined,
            expectedArgument,
            options
          ),
          `${expectedArgument} must be a positive integer`,
          (0, _jestMatcherUtils.printWithType)(
            expectedArgument,
            nth,
            _jestMatcherUtils.stringify
          )
        )
      );
    }

    const receivedName = received.getMockName();
    const {calls, results} = received.mock;
    const length = results.length;
    const iNth = nth - 1;
    const pass = iNth < length && isEqualReturn(expected, results[iNth]);
    const message = pass
      ? () => {
          // Display preceding and following results,
          // in case assertions fails because index is off by one.
          const indexedResults = [];

          if (iNth - 1 >= 0) {
            indexedResults.push([iNth - 1, results[iNth - 1]]);
          }

          indexedResults.push([iNth, results[iNth]]);

          if (iNth + 1 < length) {
            indexedResults.push([iNth + 1, results[iNth + 1]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `n: ${nth}\n` +
            `Expected: not ${(0, _jestMatcherUtils.printExpected)(
              expected
            )}\n` +
            (results.length === 1 &&
            results[0].type === 'return' &&
            (0, _jestMatcherUtils.stringify)(results[0].value) ===
              (0, _jestMatcherUtils.stringify)(expected)
              ? ''
              : printReceivedResults(
                  'Received:     ',
                  expected,
                  indexedResults,
                  results.length === 1,
                  iNth
                )) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        }
      : () => {
          // Display preceding and following results:
          // * nearest result that is equal to expected value
          // * otherwise, adjacent result
          // in case assertions fails because of index, especially off by one.
          const indexedResults = [];

          if (iNth < length) {
            if (iNth - 1 >= 0) {
              let i = iNth - 1; // Is there a preceding result that is equal to expected value?

              while (i >= 0 && !isEqualReturn(expected, results[i])) {
                i -= 1;
              }

              if (i < 0) {
                i = iNth - 1; // otherwise, adjacent result
              }

              indexedResults.push([i, results[i]]);
            }

            indexedResults.push([iNth, results[iNth]]);

            if (iNth + 1 < length) {
              let i = iNth + 1; // Is there a following result that is equal to expected value?

              while (i < length && !isEqualReturn(expected, results[i])) {
                i += 1;
              }

              if (i >= length) {
                i = iNth + 1; // otherwise, adjacent result
              }

              indexedResults.push([i, results[i]]);
            }
          } else if (length > 0) {
            // The number of received calls is fewer than the expected number.
            let i = length - 1; // Is there a result that is equal to expected value?

            while (i >= 0 && !isEqualReturn(expected, results[i])) {
              i -= 1;
            }

            if (i < 0) {
              i = length - 1; // otherwise, last result
            }

            indexedResults.push([i, results[i]]);
          }

          return (
            (0, _jestMatcherUtils.matcherHint)(
              matcherName,
              receivedName,
              expectedArgument,
              options
            ) +
            '\n\n' +
            `n: ${nth}\n` +
            `Expected: ${(0, _jestMatcherUtils.printExpected)(expected)}\n` +
            printReceivedResults(
              'Received: ',
              expected,
              indexedResults,
              results.length === 1,
              iNth
            ) +
            printNumberOfReturns(countReturns(results), calls.length)
          );
        };
    return {
      message,
      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 isMock = received =>
  received != null && received._isMockFunction === true;

const isSpy = received =>
  received != null &&
  received.calls != null &&
  typeof received.calls.all === 'function' &&
  typeof received.calls.count === 'function';

const ensureMockOrSpy = (received, matcherName, expectedArgument, options) => {
  if (!isMock(received) && !isSpy(received)) {
    throw new Error(
      (0, _jestMatcherUtils.matcherErrorMessage)(
        (0, _jestMatcherUtils.matcherHint)(
          matcherName,
          undefined,
          expectedArgument,
          options
        ),
        `${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          'received'
        )} value must be a mock or spy function`,
        (0, _jestMatcherUtils.printWithType)(
          'Received',
          received,
          _jestMatcherUtils.printReceived
        )
      )
    );
  }
};

const ensureMock = (received, matcherName, expectedArgument, options) => {
  if (!isMock(received)) {
    throw new Error(
      (0, _jestMatcherUtils.matcherErrorMessage)(
        (0, _jestMatcherUtils.matcherHint)(
          matcherName,
          undefined,
          expectedArgument,
          options
        ),
        `${(0, _jestMatcherUtils.RECEIVED_COLOR)(
          'received'
        )} value must be a mock function`,
        (0, _jestMatcherUtils.printWithType)(
          'Received',
          received,
          _jestMatcherUtils.printReceived
        )
      )
    );
  }
};

var _default = spyMatchers;
exports.default = _default;
