feat: add basic jest support (#35080)

PR Close #35080
This commit is contained in:
JiaLiPassion 2020-02-01 00:08:40 +09:00 committed by Kara Erickson
parent f7e1c21596
commit daac33cdc8
8 changed files with 180 additions and 3 deletions

View File

@ -742,6 +742,7 @@ jobs:
cp dist/bin/packages/zone.js/npm_package/dist/zone-mix.js ./packages/zone.js/test/extra/ &&
cp dist/bin/packages/zone.js/npm_package/dist/zone-patch-electron.js ./packages/zone.js/test/extra/ &&
yarn --cwd packages/zone.js electrontest
- run: yarn --cwd packages/zone.js jesttest
# Windows jobs
# Docs: https://circleci.com/docs/2.0/hello-world-windows/

View File

@ -9,6 +9,7 @@
/// <reference types="jasmine"/>
'use strict';
declare let jest: any;
((_global: any) => {
const __extends = function(d: any, b: any) {
for (const p in b)
@ -19,6 +20,11 @@
// Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs
// in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503)
if (!Zone) throw new Error('Missing: zone.js');
if (typeof jest !== 'undefined') {
// return if jasmine is a light implementation inside jest
// in this case, we are running inside jest not jasmine
return;
}
if (typeof jasmine == 'undefined') throw new Error('Missing: jasmine.js');
if ((jasmine as any)['__zone_patch__'])
throw new Error(`'jasmine' has already been patched with 'Zone'.`);
@ -285,7 +291,6 @@
// This is the zone which will be used for running individual tests.
// It will be a proxy zone, so that the tests function can retroactively install
// different zones.
// Example:
// - In beforeEach() do childZone = Zone.current.fork(...);
// - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the
// zone outside of fakeAsync it will be able to escape the fakeAsync rules.

View File

@ -0,0 +1,124 @@
/**
* @license
* Copyright Google Inc. 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) => {
if (typeof jest === 'undefined' || jest['__zone_patch__']) {
return;
}
jest['__zone_patch__'] = true;
if (typeof Zone === 'undefined') {
throw new Error('Missing Zone.js');
}
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 proxyZone = rootZone.fork(new 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[]) {
const testFn = originalJestFn.apply(this, tableArgs);
return function(this: unknown, ...args: any[]) {
args[1] = wrapTestInZone(args[1]);
return testFn.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 `testProxyZone`.
*/
function wrapTestInZone(testBody: Function): Function {
if (typeof testBody !== 'function') {
return testBody;
}
// The `done` callback is only passed through if the function expects at least one argument.
// Note we have to make a function with correct number of arguments, otherwise jest will
// think that all functions are sync or async.
return function(this: unknown, ...args: any[]) { return proxyZone.run(testBody, this, args); };
}
['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]);
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);
};
});
});

View File

@ -11,6 +11,7 @@ import '../zone-spec/long-stack-trace';
import '../zone-spec/proxy';
import '../zone-spec/sync-test';
import '../jasmine/jasmine';
import '../jest/jest';
import './async-testing';
import './fake-async';
import './promise-testing';

View File

@ -17,6 +17,7 @@
"devDependencies": {
"@types/node": "^10.9.4",
"domino": "2.1.2",
"jest": "^25.1.0",
"mocha": "^3.1.2",
"mock-require": "3.0.3",
"promises-aplus-tests": "^2.1.2",
@ -25,7 +26,8 @@
"scripts": {
"promisetest": "tsc -p . && node ./promise-test.js",
"promisefinallytest": "tsc -p . && mocha promise.finally.spec.js",
"electrontest": "cd test/extra && node electron.js"
"electrontest": "cd test/extra && node electron.js",
"jesttest": "jest --config ./test/jest/jest.config.js ./test/jest/jest.spec.js"
},
"repository": {
"type": "git",

View File

@ -0,0 +1,2 @@
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone');
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone-testing');

View File

@ -0,0 +1,3 @@
module.exports = {
setupFilesAfterEnv: ['./jest-zone.js']
};

View File

@ -0,0 +1,39 @@
function assertInsideProxyZone() {
expect(Zone.current.name).toEqual('ProxyZone');
}
function assertInsideSyncDescribeZone() {
expect(Zone.current.name).toEqual('syncTestZone for jest.describe');
}
describe('describe', () => {
assertInsideSyncDescribeZone();
beforeEach(() => { assertInsideProxyZone(); });
beforeAll(() => { assertInsideProxyZone(); });
afterEach(() => { assertInsideProxyZone(); });
afterAll(() => { assertInsideProxyZone(); });
});
describe.each([[1, 2]])('describe.each', (arg1, arg2) => {
assertInsideSyncDescribeZone();
expect(arg1).toBe(1);
expect(arg2).toBe(2);
});
describe('test', () => {
it('it', () => { assertInsideProxyZone(); });
it.each([[1, 2]])('it.each', (arg1, arg2) => {
assertInsideProxyZone();
expect(arg1).toBe(1);
expect(arg2).toBe(2);
});
test('test', () => { assertInsideProxyZone(); });
test.each([[]])('test.each', () => { assertInsideProxyZone(); });
});
it('it', () => { assertInsideProxyZone(); });
it.each([[1, 2]])('it.each', (arg1, arg2) => {
assertInsideProxyZone();
expect(arg1).toBe(1);
expect(arg2).toBe(2);
});
test('test', () => { assertInsideProxyZone(); });
test.each([[]])('test.each', () => { assertInsideProxyZone(); });
test.todo('todo');