jasmineAsyncInstall.js
  1  'use strict';
  2  
  3  Object.defineProperty(exports, '__esModule', {
  4    value: true
  5  });
  6  exports.default = jasmineAsyncInstall;
  7  
  8  var _co = _interopRequireDefault(require('co'));
  9  
 10  var _isGeneratorFn = _interopRequireDefault(require('is-generator-fn'));
 11  
 12  var _throat = _interopRequireDefault(require('throat'));
 13  
 14  var _isError = _interopRequireDefault(require('./isError'));
 15  
 16  function _interopRequireDefault(obj) {
 17    return obj && obj.__esModule ? obj : {default: obj};
 18  }
 19  
 20  var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
 21  var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
 22  var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
 23  
 24  function isPromise(obj) {
 25    return obj && typeof obj.then === 'function';
 26  }
 27  
 28  const doneFnNoop = () => {};
 29  
 30  doneFnNoop.fail = () => {};
 31  
 32  function promisifyLifeCycleFunction(originalFn, env) {
 33    return function (fn, timeout) {
 34      if (!fn) {
 35        // @ts-expect-error: missing fn arg is handled by originalFn
 36        return originalFn.call(env);
 37      }
 38  
 39      const hasDoneCallback = typeof fn === 'function' && fn.length > 0;
 40  
 41      if (hasDoneCallback) {
 42        // Jasmine will handle it
 43        return originalFn.call(env, fn, timeout);
 44      }
 45  
 46      const extraError = new Error(); // Without this line v8 stores references to all closures
 47      // in the stack in the Error object. This line stringifies the stack
 48      // property to allow garbage-collecting objects on the stack
 49      // https://crbug.com/v8/7142
 50  
 51      extraError.stack = extraError.stack; // We make *all* functions async and run `done` right away if they
 52      // didn't return a promise.
 53  
 54      const asyncJestLifecycle = function (done) {
 55        const wrappedFn = (0, _isGeneratorFn.default)(fn)
 56          ? _co.default.wrap(fn)
 57          : fn;
 58        const returnValue = wrappedFn.call({}, doneFnNoop);
 59  
 60        if (isPromise(returnValue)) {
 61          returnValue.then(done.bind(null, null), error => {
 62            const {isError: checkIsError, message} = (0, _isError.default)(error);
 63  
 64            if (message) {
 65              extraError.message = message;
 66            }
 67  
 68            done.fail(checkIsError ? error : extraError);
 69          });
 70        } else {
 71          done();
 72        }
 73      };
 74  
 75      return originalFn.call(env, asyncJestLifecycle, timeout);
 76    };
 77  } // Similar to promisifyLifeCycleFunction but throws an error
 78  // when the return value is neither a Promise nor `undefined`
 79  
 80  function promisifyIt(originalFn, env, jasmine) {
 81    return function (specName, fn, timeout) {
 82      if (!fn) {
 83        // @ts-expect-error: missing fn arg is handled by originalFn
 84        const spec = originalFn.call(env, specName);
 85        spec.pend('not implemented');
 86        return spec;
 87      }
 88  
 89      const hasDoneCallback = fn.length > 0;
 90  
 91      if (hasDoneCallback) {
 92        return originalFn.call(env, specName, fn, timeout);
 93      }
 94  
 95      const extraError = new Error(); // Without this line v8 stores references to all closures
 96      // in the stack in the Error object. This line stringifies the stack
 97      // property to allow garbage-collecting objects on the stack
 98      // https://crbug.com/v8/7142
 99  
100      extraError.stack = extraError.stack;
101  
102      const asyncJestTest = function (done) {
103        const wrappedFn = (0, _isGeneratorFn.default)(fn)
104          ? _co.default.wrap(fn)
105          : fn;
106        const returnValue = wrappedFn.call({}, doneFnNoop);
107  
108        if (isPromise(returnValue)) {
109          returnValue.then(done.bind(null, null), error => {
110            const {isError: checkIsError, message} = (0, _isError.default)(error);
111  
112            if (message) {
113              extraError.message = message;
114            }
115  
116            if (jasmine.Spec.isPendingSpecException(error)) {
117              env.pending(message);
118              done();
119            } else {
120              done.fail(checkIsError ? error : extraError);
121            }
122          });
123        } else if (returnValue === undefined) {
124          done();
125        } else {
126          done.fail(
127            new Error(
128              'Jest: `it` and `test` must return either a Promise or undefined.'
129            )
130          );
131        }
132      };
133  
134      return originalFn.call(env, specName, asyncJestTest, timeout);
135    };
136  }
137  
138  function makeConcurrent(originalFn, env, mutex) {
139    const concurrentFn = function (specName, fn, timeout) {
140      let promise = Promise.resolve();
141      const spec = originalFn.call(env, specName, () => promise, timeout);
142  
143      if (env != null && !env.specFilter(spec)) {
144        return spec;
145      }
146  
147      try {
148        promise = mutex(() => {
149          const promise = fn();
150  
151          if (isPromise(promise)) {
152            return promise;
153          }
154  
155          throw new Error(
156            `Jest: concurrent test "${spec.getFullName()}" must return a Promise.`
157          );
158        });
159      } catch (error) {
160        promise = Promise.reject(error);
161      }
162  
163      return spec;
164    }; // each is binded after the function is made concurrent, so for now it is made noop
165  
166    concurrentFn.each = () => () => {};
167  
168    return concurrentFn;
169  }
170  
171  function jasmineAsyncInstall(globalConfig, global) {
172    const jasmine = global.jasmine;
173    const mutex = (0, _throat.default)(globalConfig.maxConcurrency);
174    const env = jasmine.getEnv();
175    env.it = promisifyIt(env.it, env, jasmine);
176    env.fit = promisifyIt(env.fit, env, jasmine);
177  
178    global.it.concurrent = (env => {
179      const concurrent = makeConcurrent(env.it, env, mutex);
180      concurrent.only = makeConcurrent(env.fit, env, mutex);
181      concurrent.skip = makeConcurrent(env.xit, env, mutex);
182      return concurrent;
183    })(env);
184  
185    global.fit.concurrent = makeConcurrent(env.fit, env, mutex);
186    env.afterAll = promisifyLifeCycleFunction(env.afterAll, env);
187    env.afterEach = promisifyLifeCycleFunction(env.afterEach, env);
188    env.beforeAll = promisifyLifeCycleFunction(env.beforeAll, env);
189    env.beforeEach = promisifyLifeCycleFunction(env.beforeEach, env);
190  }