From 66987ed0e00ace117b3900e5b0e9b5dbef4f071c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 13 Dec 2018 21:33:07 +0800 Subject: [PATCH] feat: Import jest-zone-patch (#214) This PR is addressed to https://github.com/thymikee/jest-zone-patch/pull/9#issuecomment-445772763. For the sake of complete git history, I merge the commits into this repository. There is another LICENSE issue: The `jest-zone-patch` is licensed under BSD-3-Clause while this repository under MIT. I keep the original BSD LICENSE on this PR and left it as-is. --- package.json | 1 - setupJest.js | 2 +- zone-patch/LICENSE | 29 ++++++++++++ zone-patch/README.md | 6 +++ zone-patch/index.js | 106 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 zone-patch/LICENSE create mode 100644 zone-patch/README.md create mode 100644 zone-patch/index.js diff --git a/package.json b/package.json index e6e2fb87f8..90800db957 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "license": "MIT", "dependencies": { "@types/jest": "^23.3.10", - "jest-zone-patch": ">=0.0.9 <1.0.0", "ts-jest": "~23.10.0" }, "devDependencies": { diff --git a/setupJest.js b/setupJest.js index 2f89f19dc5..f71d5194e7 100644 --- a/setupJest.js +++ b/setupJest.js @@ -7,7 +7,7 @@ require('zone.js/dist/proxy.js'); require('zone.js/dist/sync-test'); require('zone.js/dist/async-test'); require('zone.js/dist/fake-async-test'); -require('jest-zone-patch'); +require('./zone-patch'); const getTestBed = require('@angular/core/testing').getTestBed; const BrowserDynamicTestingModule = require('@angular/platform-browser-dynamic/testing').BrowserDynamicTestingModule; diff --git a/zone-patch/LICENSE b/zone-patch/LICENSE new file mode 100644 index 0000000000..5e11caef54 --- /dev/null +++ b/zone-patch/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Michał Pierzchała +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/zone-patch/README.md b/zone-patch/README.md new file mode 100644 index 0000000000..55a8fa7eec --- /dev/null +++ b/zone-patch/README.md @@ -0,0 +1,6 @@ +# zone-patch +Enables Jest functions to be run within Zone.js context, specifically for [Angular](https://angular.io) apps. + +It's crucial to run this patch here, because at this point patched functions like `test` or `describe` are available in global scope. + +`zone-patch` has been included in `setupJest.js` by default. diff --git a/zone-patch/index.js b/zone-patch/index.js new file mode 100644 index 0000000000..905ab6906f --- /dev/null +++ b/zone-patch/index.js @@ -0,0 +1,106 @@ +/** + * Patch Jest's describe/test/beforeEach/afterEach functions so test code + * always runs in a testZone (ProxyZone). +*/ + +if (Zone === undefined) { + throw new Error('Missing: Zone (zone.js)'); +} +if (jest === undefined) { + throw new Error( + 'Missing: jest.\n' + + 'This patch must be included in a script called with ' + + '`setupTestFrameworkScriptFile` in Jest config.' + ); +} +if (jest['__zone_patch__'] === true) { + throw new Error("'jest' has already been patched with 'Zone'."); +} + +jest['__zone_patch__'] = true; +const SyncTestZoneSpec = Zone['SyncTestZoneSpec']; +const ProxyZoneSpec = Zone['ProxyZoneSpec']; + +if (SyncTestZoneSpec === undefined) { + throw new Error('Missing: SyncTestZoneSpec (zone.js/dist/sync-test)'); +} +if (ProxyZoneSpec === undefined) { + throw new Error('Missing: ProxyZoneSpec (zone.js/dist/proxy.js)'); +} + +const env = global; +const ambientZone = Zone.current; + +// Create a synchronous-only zone in which to run `describe` blocks in order to +// raise an error if any asynchronous operations are attempted +// inside of a `describe` but outside of a `beforeEach` or `it`. +const syncZone = ambientZone.fork(new SyncTestZoneSpec('jest.describe')); +function wrapDescribeInZone(describeBody) { + return function () { return syncZone.run(describeBody, null, arguments); } +} + +// Create a proxy zone in which to run `test` blocks so that the tests function +// can retroactively install different zones. +const testProxyZone = ambientZone.fork(new ProxyZoneSpec()); +function wrapTestInZone(testBody) { + if (testBody === undefined) { + return; + } + return testBody.length === 0 + ? () => testProxyZone.run(testBody, null) + : done => testProxyZone.run(testBody, null, [done]); +} + +const bindDescribe = (originalJestFn) => function () { + const eachArguments = arguments; + return function (description, specDefinitions, timeout) { + arguments[1] = wrapDescribeInZone(specDefinitions) + return originalJestFn.apply(this, eachArguments).apply( + this, + arguments + ) + } +}; + +['xdescribe', 'fdescribe', 'describe'].forEach(methodName => { + const originaljestFn = env[methodName]; + env[methodName] = function(description, specDefinitions, timeout) { + arguments[1] = wrapDescribeInZone(specDefinitions) + return originaljestFn.apply( + this, + arguments + ); + }; + env[methodName].each = bindDescribe(originaljestFn.each); + if (methodName === 'describe') { + env[methodName].only = env['fdescribe']; + env[methodName].skip = env['xdescribe']; + env[methodName].only.each = bindDescribe(originaljestFn.only.each); + env[methodName].skip.each = bindDescribe(originaljestFn.skip.each); + } +}); + +['xit', 'fit', 'xtest', 'test', 'it'].forEach(methodName => { + const originaljestFn = env[methodName]; + env[methodName] = function(description, specDefinitions, timeout) { + arguments[1] = wrapTestInZone(specDefinitions); + return originaljestFn.apply(this, arguments); + }; + // The revised method will be populated to the final each method, so we only declare the method that in the new globals + env[methodName].each = originaljestFn.each; + if (methodName === 'test' || methodName === 'it') { + env[methodName].only = env['fit']; + env[methodName].only.each = originaljestFn.only.each; + + env[methodName].skip = env['xit']; + env[methodName].skip.each = originaljestFn.skip.each; + } +}); + +['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => { + const originaljestFn = env[methodName]; + env[methodName] = function(specDefinitions, timeout) { + arguments[0] = wrapTestInZone(specDefinitions); + return originaljestFn.apply(this, arguments); + }; +});