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 }