diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..b39fd4acd6 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,77 @@ +version: 2.1 +executors: + node-10: + docker: + - image: circleci/node:10 + +jobs: + install-dependencies: + executor: node-10 + steps: + - checkout + - restore_cache: + keys: + # if lock file changes, we still use increasingly general patterns to restore cache + - yarn-cache-lib-{{ .Branch }}-{{ checksum "yarn.lock" }} + - yarn-cache-lib-{{ .Branch }}- + - run: + name: Install dependencies + command: | + yarn --version + yarn install --frozen-lockfile + - save_cache: + key: yarn-cache-lib-{{ .Branch }}-{{ checksum "yarn.lock" }} + paths: + - ~/.yarn + - ~/.cache/yarn + - ./node_modules + - persist_to_workspace: + root: . + paths: . + + test-library: + executor: node-10 + steps: + - attach_workspace: + at: . + - run: + name: Test Library + command: yarn test:ci + + install-test-example: + executor: node-10 + steps: + - attach_workspace: + at: . + - restore_cache: + keys: + - yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} + - yarn-cache-example-{{ .Branch }}- + - run: + name: Install Example Dependencies + command: | + cd example + yarn install --frozen-lockfile + - save_cache: + key: yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} + paths: + - ./example/node_modules + - run: + name: Test Example + command: | + cd example + yarn test:ci + yarn test:coverage + +workflows: + version: 2 + install-and-test: + jobs: + - install-dependencies + - test-library: + requires: + - install-dependencies + - install-test-example: + requires: + - install-dependencies + diff --git a/README.md b/README.md index b6ad41422a..e17a8d8833 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,10 @@ import './jestGlobalMocks'; // browser mocks globally available for every test "globals": { "ts-jest": { "tsConfig": "/src/tsconfig.spec.json", - "stringifyContentPathRegex": "\\.html$" + "stringifyContentPathRegex": "\\.html$", + "astTransformers": [ + "jest-preset-angular/InlineHtmlStripStylesTransformer" + ] } }, "transform": { diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 2fad34f39d..0000000000 --- a/circle.yml +++ /dev/null @@ -1,23 +0,0 @@ -machine: - environment: - YARN_VERSION: 1.3.2 - PATH: "${PATH}:${HOME}/.yarn/bin:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" - node: - version: 8.9.3 -dependencies: - pre: - - | - if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version $YARN_VERSION - fi - - cache_directories: - - ~/.yarn - - ~/.cache/yarn - override: - - yarn install --no-progress - - cd example && yarn install --no-progress -test: - override: - - yarn run test:ci - - cd example && yarn run test:ci && yarn run test:coverage diff --git a/package.json b/package.json index ca2eff1122..88dd3835ba 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "license": "MIT", "dependencies": { "@types/jest": "^23.3.10", - "jest-zone-patch": ">=0.0.9 <1.0.0", - "ts-jest": "~23.10.0" + "ts-jest": "~23.10.5" }, "devDependencies": { "@types/node": "^10.12.12", 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/yarn.lock b/yarn.lock index 7edb666639..45d94924d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2188,11 +2188,6 @@ jest-worker@^23.2.0: dependencies: merge-stream "^1.0.1" -"jest-zone-patch@>=0.0.9 <1.0.0": - version "0.0.9" - resolved "https://registry.yarnpkg.com/jest-zone-patch/-/jest-zone-patch-0.0.9.tgz#ee6823317798a3ae967e82be963f0b1f80b03d3f" - integrity sha512-QmlUxg2NIvLHSqexS3nqQ+Kid6o+5cxYnlcTi204XwsRcBm+mVYodpe+9ddcooKR9ATBIUl4zY247Uky/PgXhw== - jest@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d" @@ -3688,7 +3683,7 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= -ts-jest@~23.10.0: +ts-jest@~23.10.5: version "23.10.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.10.5.tgz#cdb550df4466a30489bf70ba867615799f388dd5" integrity sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A== 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); + }; +});