jasmineAsyncInstall.js 5.26 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
'use strict';

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

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

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

var _throat = _interopRequireDefault(require('throat'));

var _isError = _interopRequireDefault(require('./isError'));

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

var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

function isPromise(obj) {
  return obj && typeof obj.then === 'function';
}

function promisifyLifeCycleFunction(originalFn, env) {
  return function (fn, timeout) {
    if (!fn) {
      return originalFn.call(env);
    }

    const hasDoneCallback = typeof fn === 'function' && fn.length > 0;

    if (hasDoneCallback) {
      // Jasmine will handle it
      return originalFn.call(env, fn, timeout);
    }

    const extraError = new Error(); // Without this line v8 stores references to all closures
    // in the stack in the Error object. This line stringifies the stack
    // property to allow garbage-collecting objects on the stack
    // https://crbug.com/v8/7142

    extraError.stack = extraError.stack; // We make *all* functions async and run `done` right away if they
    // didn't return a promise.

    const asyncJestLifecycle = function (done) {
      const wrappedFn = (0, _isGeneratorFn.default)(fn)
        ? _co.default.wrap(fn)
        : fn;
      const returnValue = wrappedFn.call({});

      if (isPromise(returnValue)) {
        returnValue.then(done.bind(null, null), error => {
          const {isError: checkIsError, message} = (0, _isError.default)(error);

          if (message) {
            extraError.message = message;
          }

          done.fail(checkIsError ? error : extraError);
        });
      } else {
        done();
      }
    };

    return originalFn.call(env, asyncJestLifecycle, timeout);
  };
} // Similar to promisifyLifeCycleFunction but throws an error
// when the return value is neither a Promise nor `undefined`

function promisifyIt(originalFn, env, jasmine) {
  return function (specName, fn, timeout) {
    if (!fn) {
      const spec = originalFn.call(env, specName);
      spec.pend('not implemented');
      return spec;
    }

    const hasDoneCallback = fn.length > 0;

    if (hasDoneCallback) {
      return originalFn.call(env, specName, fn, timeout);
    }

    const extraError = new Error(); // Without this line v8 stores references to all closures
    // in the stack in the Error object. This line stringifies the stack
    // property to allow garbage-collecting objects on the stack
    // https://crbug.com/v8/7142

    extraError.stack = extraError.stack;

    const asyncJestTest = function (done) {
      const wrappedFn = (0, _isGeneratorFn.default)(fn)
        ? _co.default.wrap(fn)
        : fn;
      const returnValue = wrappedFn.call({});

      if (isPromise(returnValue)) {
        returnValue.then(done.bind(null, null), error => {
          const {isError: checkIsError, message} = (0, _isError.default)(error);

          if (message) {
            extraError.message = message;
          }

          if (jasmine.Spec.isPendingSpecException(error)) {
            env.pending(message);
            done();
          } else {
            done.fail(checkIsError ? error : extraError);
          }
        });
      } else if (returnValue === undefined) {
        done();
      } else {
        done.fail(
          new Error(
            'Jest: `it` and `test` must return either a Promise or undefined.'
          )
        );
      }
    };

    return originalFn.call(env, specName, asyncJestTest, timeout);
  };
}

function makeConcurrent(originalFn, env, mutex) {
  return function (specName, fn, timeout) {
    let promise = Promise.resolve();
    const spec = originalFn.call(env, specName, () => promise, timeout);

    if (env != null && !env.specFilter(spec)) {
      return spec;
    }

    try {
      promise = mutex(() => {
        const promise = fn();

        if (isPromise(promise)) {
          return promise;
        }

        throw new Error(
          `Jest: concurrent test "${spec.getFullName()}" must return a Promise.`
        );
      });
    } catch (error) {
      promise = Promise.reject(error);
    }

    return spec;
  };
}

function jasmineAsyncInstall(globalConfig, global) {
  const jasmine = global.jasmine;
  const mutex = (0, _throat.default)(globalConfig.maxConcurrency);
  const env = jasmine.getEnv();
  env.it = promisifyIt(env.it, env, jasmine);
  env.fit = promisifyIt(env.fit, env, jasmine);

  global.it.concurrent = (env => {
    const concurrent = makeConcurrent(env.it, env, mutex);
    concurrent.only = makeConcurrent(env.fit, env, mutex);
    concurrent.skip = makeConcurrent(env.xit, env, mutex);
    return concurrent;
  })(env);

  global.fit.concurrent = makeConcurrent(env.fit, env, mutex);
  env.afterAll = promisifyLifeCycleFunction(env.afterAll, env);
  env.afterEach = promisifyLifeCycleFunction(env.afterEach, env);
  env.beforeAll = promisifyLifeCycleFunction(env.beforeAll, env);
  env.beforeEach = promisifyLifeCycleFunction(env.beforeEach, env);
}