Skip to content

Commit daac33c

Browse files
JiaLiPassionkara
authored andcommitted
feat: add basic jest support (#35080)
PR Close #35080
1 parent f7e1c21 commit daac33c

8 files changed

Lines changed: 180 additions & 3 deletions

File tree

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ jobs:
742742
cp dist/bin/packages/zone.js/npm_package/dist/zone-mix.js ./packages/zone.js/test/extra/ &&
743743
cp dist/bin/packages/zone.js/npm_package/dist/zone-patch-electron.js ./packages/zone.js/test/extra/ &&
744744
yarn --cwd packages/zone.js electrontest
745+
- run: yarn --cwd packages/zone.js jesttest
745746

746747
# Windows jobs
747748
# Docs: https://circleci.com/docs/2.0/hello-world-windows/

packages/zone.js/lib/jasmine/jasmine.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/// <reference types="jasmine"/>
1010

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

packages/zone.js/lib/jest/jest.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
'use strict';
10+
11+
Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
12+
if (typeof jest === 'undefined' || jest['__zone_patch__']) {
13+
return;
14+
}
15+
16+
jest['__zone_patch__'] = true;
17+
18+
19+
if (typeof Zone === 'undefined') {
20+
throw new Error('Missing Zone.js');
21+
}
22+
23+
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'];
24+
const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec'];
25+
26+
if (!ProxyZoneSpec) {
27+
throw new Error('Missing ProxyZoneSpec');
28+
}
29+
30+
const rootZone = Zone.current;
31+
const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe'));
32+
const proxyZone = rootZone.fork(new ProxyZoneSpec());
33+
34+
function wrapDescribeFactoryInZone(originalJestFn: Function) {
35+
return function(this: unknown, ...tableArgs: any[]) {
36+
const originalDescribeFn = originalJestFn.apply(this, tableArgs);
37+
return function(this: unknown, ...args: any[]) {
38+
args[1] = wrapDescribeInZone(args[1]);
39+
return originalDescribeFn.apply(this, args);
40+
};
41+
};
42+
}
43+
44+
function wrapTestFactoryInZone(originalJestFn: Function) {
45+
return function(this: unknown, ...tableArgs: any[]) {
46+
const testFn = originalJestFn.apply(this, tableArgs);
47+
return function(this: unknown, ...args: any[]) {
48+
args[1] = wrapTestInZone(args[1]);
49+
return testFn.apply(this, args);
50+
};
51+
};
52+
}
53+
54+
/**
55+
* Gets a function wrapping the body of a jest `describe` block to execute in a
56+
* synchronous-only zone.
57+
*/
58+
function wrapDescribeInZone(describeBody: Function): Function {
59+
return function(this: unknown, ...args: any[]) {
60+
return syncZone.run(describeBody, this, args);
61+
};
62+
}
63+
64+
/**
65+
* Gets a function wrapping the body of a jest `it/beforeEach/afterEach` block to
66+
* execute in a ProxyZone zone.
67+
* This will run in the `testProxyZone`.
68+
*/
69+
function wrapTestInZone(testBody: Function): Function {
70+
if (typeof testBody !== 'function') {
71+
return testBody;
72+
}
73+
// The `done` callback is only passed through if the function expects at least one argument.
74+
// Note we have to make a function with correct number of arguments, otherwise jest will
75+
// think that all functions are sync or async.
76+
return function(this: unknown, ...args: any[]) { return proxyZone.run(testBody, this, args); };
77+
}
78+
79+
['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
80+
let originalJestFn: Function = context[methodName];
81+
if (context[Zone.__symbol__(methodName)]) {
82+
return;
83+
}
84+
context[Zone.__symbol__(methodName)] = originalJestFn;
85+
context[methodName] = function(this: unknown, ...args: any[]) {
86+
args[1] = wrapDescribeInZone(args[1]);
87+
return originalJestFn.apply(this, args);
88+
};
89+
context[methodName].each = wrapDescribeFactoryInZone((originalJestFn as any).each);
90+
});
91+
context.describe.only = context.fdescribe;
92+
context.describe.skip = context.xdescribe;
93+
94+
['it', 'xit', 'fit', 'test', 'xtest'].forEach(methodName => {
95+
let originalJestFn: Function = context[methodName];
96+
if (context[Zone.__symbol__(methodName)]) {
97+
return;
98+
}
99+
context[Zone.__symbol__(methodName)] = originalJestFn;
100+
context[methodName] = function(this: unknown, ...args: any[]) {
101+
args[1] = wrapTestInZone(args[1]);
102+
return originalJestFn.apply(this, args);
103+
};
104+
context[methodName].each = wrapTestFactoryInZone((originalJestFn as any).each);
105+
context[methodName].todo = (originalJestFn as any).todo;
106+
});
107+
108+
context.it.only = context.fit;
109+
context.it.skip = context.xit;
110+
context.test.only = context.fit;
111+
context.test.skip = context.xit;
112+
113+
['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
114+
let originalJestFn: Function = context[methodName];
115+
if (context[Zone.__symbol__(methodName)]) {
116+
return;
117+
}
118+
context[Zone.__symbol__(methodName)] = originalJestFn;
119+
context[methodName] = function(this: unknown, ...args: any[]) {
120+
args[0] = wrapTestInZone(args[0]);
121+
return originalJestFn.apply(this, args);
122+
};
123+
});
124+
});

packages/zone.js/lib/testing/zone-testing.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../zone-spec/long-stack-trace';
1111
import '../zone-spec/proxy';
1212
import '../zone-spec/sync-test';
1313
import '../jasmine/jasmine';
14+
import '../jest/jest';
1415
import './async-testing';
1516
import './fake-async';
16-
import './promise-testing';
17+
import './promise-testing';

packages/zone.js/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"devDependencies": {
1818
"@types/node": "^10.9.4",
1919
"domino": "2.1.2",
20+
"jest": "^25.1.0",
2021
"mocha": "^3.1.2",
2122
"mock-require": "3.0.3",
2223
"promises-aplus-tests": "^2.1.2",
@@ -25,7 +26,8 @@
2526
"scripts": {
2627
"promisetest": "tsc -p . && node ./promise-test.js",
2728
"promisefinallytest": "tsc -p . && mocha promise.finally.spec.js",
28-
"electrontest": "cd test/extra && node electron.js"
29+
"electrontest": "cd test/extra && node electron.js",
30+
"jesttest": "jest --config ./test/jest/jest.config.js ./test/jest/jest.spec.js"
2931
},
3032
"repository": {
3133
"type": "git",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone');
2+
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone-testing');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
setupFilesAfterEnv: ['./jest-zone.js']
3+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
function assertInsideProxyZone() {
2+
expect(Zone.current.name).toEqual('ProxyZone');
3+
}
4+
function assertInsideSyncDescribeZone() {
5+
expect(Zone.current.name).toEqual('syncTestZone for jest.describe');
6+
}
7+
describe('describe', () => {
8+
assertInsideSyncDescribeZone();
9+
beforeEach(() => { assertInsideProxyZone(); });
10+
beforeAll(() => { assertInsideProxyZone(); });
11+
afterEach(() => { assertInsideProxyZone(); });
12+
afterAll(() => { assertInsideProxyZone(); });
13+
});
14+
describe.each([[1, 2]])('describe.each', (arg1, arg2) => {
15+
assertInsideSyncDescribeZone();
16+
expect(arg1).toBe(1);
17+
expect(arg2).toBe(2);
18+
});
19+
describe('test', () => {
20+
it('it', () => { assertInsideProxyZone(); });
21+
it.each([[1, 2]])('it.each', (arg1, arg2) => {
22+
assertInsideProxyZone();
23+
expect(arg1).toBe(1);
24+
expect(arg2).toBe(2);
25+
});
26+
test('test', () => { assertInsideProxyZone(); });
27+
test.each([[]])('test.each', () => { assertInsideProxyZone(); });
28+
});
29+
30+
it('it', () => { assertInsideProxyZone(); });
31+
it.each([[1, 2]])('it.each', (arg1, arg2) => {
32+
assertInsideProxyZone();
33+
expect(arg1).toBe(1);
34+
expect(arg2).toBe(2);
35+
});
36+
test('test', () => { assertInsideProxyZone(); });
37+
test.each([[]])('test.each', () => { assertInsideProxyZone(); });
38+
39+
test.todo('todo');

0 commit comments

Comments
 (0)