/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ 'use strict'; Zone.__load_patch('jest', (context: any, Zone: ZoneType, api: _ZonePrivate) => { if (typeof jest === 'undefined' || jest['__zone_patch__']) { return; } jest['__zone_patch__'] = true; const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec']; const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec']; if (!ProxyZoneSpec) { throw new Error('Missing ProxyZoneSpec'); } const rootZone = Zone.current; const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe')); const proxyZoneSpec = new ProxyZoneSpec(); const proxyZone = rootZone.fork(proxyZoneSpec); function wrapDescribeFactoryInZone(originalJestFn: Function) { return function(this: unknown, ...tableArgs: any[]) { const originalDescribeFn = originalJestFn.apply(this, tableArgs); return function(this: unknown, ...args: any[]) { args[1] = wrapDescribeInZone(args[1]); return originalDescribeFn.apply(this, args); }; }; } function wrapTestFactoryInZone(originalJestFn: Function) { return function(this: unknown, ...tableArgs: any[]) { return function(this: unknown, ...args: any[]) { args[1] = wrapTestInZone(args[1]); return originalJestFn.apply(this, tableArgs).apply(this, args); }; }; } /** * Gets a function wrapping the body of a jest `describe` block to execute in a * synchronous-only zone. */ function wrapDescribeInZone(describeBody: Function): Function { return function(this: unknown, ...args: any[]) { return syncZone.run(describeBody, this, args); }; } /** * Gets a function wrapping the body of a jest `it/beforeEach/afterEach` block to * execute in a ProxyZone zone. * This will run in the `proxyZone`. */ function wrapTestInZone(testBody: Function, isTestFunc = false): Function { if (typeof testBody !== 'function') { return testBody; } const wrappedFunc = function() { if ((Zone as any)[api.symbol('useFakeTimersCalled')] === true && testBody && !(testBody as any).isFakeAsync) { // jest.useFakeTimers is called, run into fakeAsyncTest automatically. const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') { testBody = fakeAsyncModule.fakeAsync(testBody); } } proxyZoneSpec.isTestFunc = isTestFunc; return proxyZone.run(testBody, null, arguments as any); }; // Update the length of wrappedFunc to be the same as the length of the testBody // So jest core can handle whether the test function has `done()` or not correctly Object.defineProperty( wrappedFunc, 'length', {configurable: true, writable: true, enumerable: false}); wrappedFunc.length = testBody.length; return wrappedFunc; } ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => { let originalJestFn: Function = context[methodName]; if (context[Zone.__symbol__(methodName)]) { return; } context[Zone.__symbol__(methodName)] = originalJestFn; context[methodName] = function(this: unknown, ...args: any[]) { args[1] = wrapDescribeInZone(args[1]); return originalJestFn.apply(this, args); }; context[methodName].each = wrapDescribeFactoryInZone((originalJestFn as any).each); }); context.describe.only = context.fdescribe; context.describe.skip = context.xdescribe; ['it', 'xit', 'fit', 'test', 'xtest'].forEach(methodName => { let originalJestFn: Function = context[methodName]; if (context[Zone.__symbol__(methodName)]) { return; } context[Zone.__symbol__(methodName)] = originalJestFn; context[methodName] = function(this: unknown, ...args: any[]) { args[1] = wrapTestInZone(args[1], true); return originalJestFn.apply(this, args); }; context[methodName].each = wrapTestFactoryInZone((originalJestFn as any).each); context[methodName].todo = (originalJestFn as any).todo; }); context.it.only = context.fit; context.it.skip = context.xit; context.test.only = context.fit; context.test.skip = context.xit; ['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => { let originalJestFn: Function = context[methodName]; if (context[Zone.__symbol__(methodName)]) { return; } context[Zone.__symbol__(methodName)] = originalJestFn; context[methodName] = function(this: unknown, ...args: any[]) { args[0] = wrapTestInZone(args[0]); return originalJestFn.apply(this, args); }; }); (Zone as any).patchJestObject = function patchJestObject(Timer: any, isModern = false) { // check whether currently the test is inside fakeAsync() function isPatchingFakeTimer() { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); return !!fakeAsyncZoneSpec; } // check whether the current function is inside `test/it` or other methods // such as `describe/beforeEach` function isInTestFunc() { const proxyZoneSpec = Zone.current.get('ProxyZoneSpec'); return proxyZoneSpec && proxyZoneSpec.isTestFunc; } if (Timer[api.symbol('fakeTimers')]) { return; } Timer[api.symbol('fakeTimers')] = true; // patch jest fakeTimer internal method to make sure no console.warn print out api.patchMethod(Timer, '_checkFakeTimers', delegate => { return function(self: any, args: any[]) { if (isPatchingFakeTimer()) { return true; } else { return delegate.apply(self, args); } } }); // patch useFakeTimers(), set useFakeTimersCalled flag, and make test auto run into fakeAsync api.patchMethod(Timer, 'useFakeTimers', delegate => { return function(self: any, args: any[]) { (Zone as any)[api.symbol('useFakeTimersCalled')] = true; if (isModern || isInTestFunc()) { return delegate.apply(self, args); } return self; } }); // patch useRealTimers(), unset useFakeTimers flag api.patchMethod(Timer, 'useRealTimers', delegate => { return function(self: any, args: any[]) { (Zone as any)[api.symbol('useFakeTimersCalled')] = false; if (isModern || isInTestFunc()) { return delegate.apply(self, args); } return self; } }); // patch setSystemTime(), call setCurrentRealTime() in the fakeAsyncTest api.patchMethod(Timer, 'setSystemTime', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec && isPatchingFakeTimer()) { fakeAsyncZoneSpec.setFakeBaseSystemTime(args[0]); } else { return delegate.apply(self, args); } } }); // patch getSystemTime(), call getCurrentRealTime() in the fakeAsyncTest api.patchMethod(Timer, 'getRealSystemTime', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec && isPatchingFakeTimer()) { return fakeAsyncZoneSpec.getRealSystemTime(); } else { return delegate.apply(self, args); } } }); // patch runAllTicks(), run all microTasks inside fakeAsync api.patchMethod(Timer, 'runAllTicks', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.flushMicrotasks(); } else { return delegate.apply(self, args); } } }); // patch runAllTimers(), run all macroTasks inside fakeAsync api.patchMethod(Timer, 'runAllTimers', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.flush(100, true); } else { return delegate.apply(self, args); } } }); // patch advanceTimersByTime(), call tick() in the fakeAsyncTest api.patchMethod(Timer, 'advanceTimersByTime', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.tick(args[0]); } else { return delegate.apply(self, args); } } }); // patch runOnlyPendingTimers(), call flushOnlyPendingTimers() in the fakeAsyncTest api.patchMethod(Timer, 'runOnlyPendingTimers', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.flushOnlyPendingTimers(); } else { return delegate.apply(self, args); } } }); // patch advanceTimersToNextTimer(), call tickToNext() in the fakeAsyncTest api.patchMethod(Timer, 'advanceTimersToNextTimer', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.tickToNext(args[0]); } else { return delegate.apply(self, args); } } }); // patch clearAllTimers(), call removeAllTimers() in the fakeAsyncTest api.patchMethod(Timer, 'clearAllTimers', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { fakeAsyncZoneSpec.removeAllTimers(); } else { return delegate.apply(self, args); } } }); // patch getTimerCount(), call getTimerCount() in the fakeAsyncTest api.patchMethod(Timer, 'getTimerCount', delegate => { return function(self: any, args: any[]) { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); if (fakeAsyncZoneSpec) { return fakeAsyncZoneSpec.getTimerCount(); } else { return delegate.apply(self, args); } } }); } });