'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.invariant = exports.addErrorToEachTestUnderDescribe = exports.getTestID = exports.makeRunResult = exports.getTestDuration = exports.callAsyncCircusFn = exports.describeBlockHasTests = exports.getEachHooksForTest = exports.getAllHooksForDescribe = exports.makeTest = exports.makeDescribe = void 0;

var _jestUtil = require('jest-util');

var _isGeneratorFn = _interopRequireDefault(require('is-generator-fn'));

var _co = _interopRequireDefault(require('co'));

var _stackUtils = _interopRequireDefault(require('stack-utils'));

var _prettyFormat = _interopRequireDefault(require('pretty-format'));

var _state = require('./state');

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

var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
var jestNow = global[Symbol.for('jest-native-now')] || global.Date.now;
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
const stackUtils = new _stackUtils.default({
  cwd: 'A path that does not exist'
});

const makeDescribe = (name, parent, mode) => {
  let _mode = mode;

  if (parent && !mode) {
    // If not set explicitly, inherit from the parent describe.
    _mode = parent.mode;
  }

  return {
    children: [],
    hooks: [],
    mode: _mode,
    name: (0, _jestUtil.convertDescriptorToString)(name),
    parent,
    tests: []
  };
};

exports.makeDescribe = makeDescribe;

const makeTest = (fn, mode, name, parent, timeout, asyncError) => ({
  asyncError,
  duration: null,
  errors: [],
  fn,
  invocations: 0,
  mode,
  name: (0, _jestUtil.convertDescriptorToString)(name),
  parent,
  startedAt: null,
  status: null,
  timeout
}); // Traverse the tree of describe blocks and return true if at least one describe
// block has an enabled test.

exports.makeTest = makeTest;

const hasEnabledTest = describeBlock => {
  const _getState = (0, _state.getState)(),
    hasFocusedTests = _getState.hasFocusedTests,
    testNamePattern = _getState.testNamePattern;

  const hasOwnEnabledTests = describeBlock.tests.some(
    test =>
      !(
        test.mode === 'skip' ||
        (hasFocusedTests && test.mode !== 'only') ||
        (testNamePattern && !testNamePattern.test(getTestID(test)))
      )
  );
  return hasOwnEnabledTests || describeBlock.children.some(hasEnabledTest);
};

const getAllHooksForDescribe = describe => {
  const result = {
    afterAll: [],
    beforeAll: []
  };

  if (hasEnabledTest(describe)) {
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

    try {
      for (
        var _iterator = describe.hooks[Symbol.iterator](), _step;
        !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
        _iteratorNormalCompletion = true
      ) {
        const hook = _step.value;

        switch (hook.type) {
          case 'beforeAll':
            result.beforeAll.push(hook);
            break;

          case 'afterAll':
            result.afterAll.push(hook);
            break;
        }
      }
    } catch (err) {
      _didIteratorError = true;
      _iteratorError = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator.return != null) {
          _iterator.return();
        }
      } finally {
        if (_didIteratorError) {
          throw _iteratorError;
        }
      }
    }
  }

  return result;
};

exports.getAllHooksForDescribe = getAllHooksForDescribe;

const getEachHooksForTest = test => {
  const result = {
    afterEach: [],
    beforeEach: []
  };
  let block = test.parent;

  do {
    const beforeEachForCurrentBlock = [];
    var _iteratorNormalCompletion2 = true;
    var _didIteratorError2 = false;
    var _iteratorError2 = undefined;

    try {
      for (
        var _iterator2 = block.hooks[Symbol.iterator](), _step2;
        !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done);
        _iteratorNormalCompletion2 = true
      ) {
        const hook = _step2.value;

        switch (hook.type) {
          case 'beforeEach':
            beforeEachForCurrentBlock.push(hook);
            break;

          case 'afterEach':
            result.afterEach.push(hook);
            break;
        }
      } // 'beforeEach' hooks are executed from top to bottom, the opposite of the
      // way we traversed it.
    } catch (err) {
      _didIteratorError2 = true;
      _iteratorError2 = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
          _iterator2.return();
        }
      } finally {
        if (_didIteratorError2) {
          throw _iteratorError2;
        }
      }
    }

    result.beforeEach = [...beforeEachForCurrentBlock, ...result.beforeEach];
  } while ((block = block.parent));

  return result;
};

exports.getEachHooksForTest = getEachHooksForTest;

const describeBlockHasTests = describe =>
  describe.tests.length > 0 || describe.children.some(describeBlockHasTests);

exports.describeBlockHasTests = describeBlockHasTests;

const _makeTimeoutMessage = (timeout, isHook) =>
  `Exceeded timeout of ${timeout}ms for a ${
    isHook ? 'hook' : 'test'
  }.\nUse jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test.`; // Global values can be overwritten by mocks or tests. We'll capture
// the original values in the variables before we require any files.

const _global = global,
  setTimeout = _global.setTimeout,
  clearTimeout = _global.clearTimeout;

function checkIsError(error) {
  return !!(error && error.message && error.stack);
}

const callAsyncCircusFn = (fn, testContext, {isHook, timeout}) => {
  let timeoutID;
  let completed = false;
  return new Promise((resolve, reject) => {
    timeoutID = setTimeout(
      () => reject(_makeTimeoutMessage(timeout, !!isHook)),
      timeout
    ); // If this fn accepts `done` callback we return a promise that fulfills as
    // soon as `done` called.

    if (fn.length) {
      const done = reason => {
        const errorAsErrorObject = checkIsError(reason)
          ? reason
          : new Error(
              `Failed: ${(0, _prettyFormat.default)(reason, {
                maxDepth: 3
              })}`
            ); // Consider always throwing, regardless if `reason` is set or not

        if (completed && reason) {
          errorAsErrorObject.message =
            'Caught error after test environment was torn down\n\n' +
            errorAsErrorObject.message;
          throw errorAsErrorObject;
        }

        return reason ? reject(errorAsErrorObject) : resolve();
      };

      return fn.call(testContext, done);
    }

    let returnedValue;

    if ((0, _isGeneratorFn.default)(fn)) {
      returnedValue = _co.default.wrap(fn).call({});
    } else {
      try {
        returnedValue = fn.call(testContext);
      } catch (error) {
        return reject(error);
      }
    } // If it's a Promise, return it. Test for an object with a `then` function
    // to support custom Promise implementations.

    if (
      typeof returnedValue === 'object' &&
      returnedValue !== null &&
      typeof returnedValue.then === 'function'
    ) {
      return returnedValue.then(resolve, reject);
    }

    if (!isHook && returnedValue !== void 0) {
      return reject(
        new Error(`
      test functions can only return Promise or undefined.
      Returned value: ${String(returnedValue)}
      `)
      );
    } // Otherwise this test is synchronous, and if it didn't throw it means
    // it passed.

    return resolve();
  })
    .then(() => {
      completed = true; // If timeout is not cleared/unrefed the node process won't exit until
      // it's resolved.

      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
    })
    .catch(error => {
      completed = true;
      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
      throw error;
    });
};

exports.callAsyncCircusFn = callAsyncCircusFn;

const getTestDuration = test => {
  const startedAt = test.startedAt;
  return typeof startedAt === 'number' ? jestNow() - startedAt : null;
};

exports.getTestDuration = getTestDuration;

const makeRunResult = (describeBlock, unhandledErrors) => ({
  testResults: makeTestResults(describeBlock),
  unhandledErrors: unhandledErrors.map(_formatError)
});

exports.makeRunResult = makeRunResult;

const makeTestResults = describeBlock => {
  const _getState2 = (0, _state.getState)(),
    includeTestLocationInResult = _getState2.includeTestLocationInResult;

  let testResults = [];
  var _iteratorNormalCompletion3 = true;
  var _didIteratorError3 = false;
  var _iteratorError3 = undefined;

  try {
    for (
      var _iterator3 = describeBlock.tests[Symbol.iterator](), _step3;
      !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done);
      _iteratorNormalCompletion3 = true
    ) {
      const test = _step3.value;
      const testPath = [];
      let parent = test;

      do {
        testPath.unshift(parent.name);
      } while ((parent = parent.parent));

      const status = test.status;

      if (!status) {
        throw new Error('Status should be present after tests are run.');
      }

      let location = null;

      if (includeTestLocationInResult) {
        const stackLine = test.asyncError.stack.split('\n')[1];
        const parsedLine = stackUtils.parseLine(stackLine);

        if (
          parsedLine &&
          typeof parsedLine.column === 'number' &&
          typeof parsedLine.line === 'number'
        ) {
          location = {
            column: parsedLine.column,
            line: parsedLine.line
          };
        }
      }

      testResults.push({
        duration: test.duration,
        errors: test.errors.map(_formatError),
        invocations: test.invocations,
        location,
        status,
        testPath
      });
    }
  } catch (err) {
    _didIteratorError3 = true;
    _iteratorError3 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
        _iterator3.return();
      }
    } finally {
      if (_didIteratorError3) {
        throw _iteratorError3;
      }
    }
  }

  var _iteratorNormalCompletion4 = true;
  var _didIteratorError4 = false;
  var _iteratorError4 = undefined;

  try {
    for (
      var _iterator4 = describeBlock.children[Symbol.iterator](), _step4;
      !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done);
      _iteratorNormalCompletion4 = true
    ) {
      const child = _step4.value;
      testResults = testResults.concat(makeTestResults(child));
    }
  } catch (err) {
    _didIteratorError4 = true;
    _iteratorError4 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
        _iterator4.return();
      }
    } finally {
      if (_didIteratorError4) {
        throw _iteratorError4;
      }
    }
  }

  return testResults;
}; // Return a string that identifies the test (concat of parent describe block
// names + test title)

const getTestID = test => {
  const titles = [];
  let parent = test;

  do {
    titles.unshift(parent.name);
  } while ((parent = parent.parent));

  titles.shift(); // remove TOP_DESCRIBE_BLOCK_NAME

  return titles.join(' ');
};

exports.getTestID = getTestID;

const _formatError = errors => {
  let error;
  let asyncError;

  if (Array.isArray(errors)) {
    error = errors[0];
    asyncError = errors[1];
  } else {
    error = errors;
    asyncError = new Error();
  }

  if (error) {
    if (error.stack) {
      return error.stack;
    }

    if (error.message) {
      return error.message;
    }
  }

  asyncError.message = `thrown: ${(0, _prettyFormat.default)(error, {
    maxDepth: 3
  })}`;
  return asyncError.stack;
};

const addErrorToEachTestUnderDescribe = (describeBlock, error, asyncError) => {
  var _iteratorNormalCompletion5 = true;
  var _didIteratorError5 = false;
  var _iteratorError5 = undefined;

  try {
    for (
      var _iterator5 = describeBlock.tests[Symbol.iterator](), _step5;
      !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done);
      _iteratorNormalCompletion5 = true
    ) {
      const test = _step5.value;
      test.errors.push([error, asyncError]);
    }
  } catch (err) {
    _didIteratorError5 = true;
    _iteratorError5 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
        _iterator5.return();
      }
    } finally {
      if (_didIteratorError5) {
        throw _iteratorError5;
      }
    }
  }

  var _iteratorNormalCompletion6 = true;
  var _didIteratorError6 = false;
  var _iteratorError6 = undefined;

  try {
    for (
      var _iterator6 = describeBlock.children[Symbol.iterator](), _step6;
      !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done);
      _iteratorNormalCompletion6 = true
    ) {
      const child = _step6.value;
      addErrorToEachTestUnderDescribe(child, error, asyncError);
    }
  } catch (err) {
    _didIteratorError6 = true;
    _iteratorError6 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
        _iterator6.return();
      }
    } finally {
      if (_didIteratorError6) {
        throw _iteratorError6;
      }
    }
  }
};

exports.addErrorToEachTestUnderDescribe = addErrorToEachTestUnderDescribe;

const invariant = (condition, message) => {
  if (!condition) {
    throw new Error(message);
  }
};

exports.invariant = invariant;
