Skip to content

Commit 432a273

Browse files
committed
feat(zone.js): support vitest patching in zone.js/testing
To support `fakeAsync` usage while using `vitest` as a test runner, Zone.js now provides patching when using the `zone.js/testing` package import. This patching is similar to that of the existing jasmine, mocha, and jest functionality.
1 parent 68d774f commit 432a273

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {patchJasmine} from '../jasmine/jasmine';
1010
import {patchJest} from '../jest/jest';
1111
import {patchMocha} from '../mocha/mocha';
12+
import {patchVitest} from '../vitest/vitest';
1213
import {ZoneType} from '../zone-impl';
1314
import {patchAsyncTest} from '../zone-spec/async-test';
1415
import {patchFakeAsyncTest} from '../zone-spec/fake-async-test';
@@ -25,6 +26,7 @@ export function rollupTesting(Zone: ZoneType): void {
2526
patchJasmine(Zone);
2627
patchJest(Zone);
2728
patchMocha(Zone);
29+
patchVitest(Zone);
2830
patchAsyncTest(Zone);
2931
patchFakeAsyncTest(Zone);
3032
patchPromiseTesting(Zone);
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC 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.dev/license
7+
*/
8+
9+
import {ZoneType} from '../zone-impl';
10+
11+
declare let vitest: any;
12+
13+
export function patchVitest(Zone: ZoneType): void {
14+
Zone.__load_patch('vitest', (context: any, Zone: ZoneType, api: _ZonePrivate) => {
15+
if (typeof vitest === 'undefined' || vitest['__zone_patch__']) {
16+
return;
17+
}
18+
19+
vitest['__zone_patch__'] = true;
20+
21+
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'];
22+
const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec'];
23+
24+
if (!ProxyZoneSpec) {
25+
throw new Error('Missing ProxyZoneSpec');
26+
}
27+
28+
const rootZone = Zone.current;
29+
const syncZone = rootZone.fork(new SyncTestZoneSpec('vitest.describe'));
30+
const proxyZoneSpec = new ProxyZoneSpec();
31+
const proxyZone = rootZone.fork(proxyZoneSpec);
32+
33+
function wrapDescribeFactoryInZone(originalVitestFn: Function) {
34+
return function (this: unknown, ...describeArgs: any[]) {
35+
const originalDescribeFn = originalVitestFn.apply(this, describeArgs);
36+
return function (this: unknown, ...args: any[]) {
37+
args[1] = wrapDescribeInZone(args[1]);
38+
return originalDescribeFn.apply(this, args);
39+
};
40+
};
41+
}
42+
43+
function wrapTestFactoryInZone(originalVitestFn: Function) {
44+
return function (this: unknown, ...testArgs: any[]) {
45+
return function (this: unknown, ...args: any[]) {
46+
args[1] = wrapTestInZone(args[1]);
47+
return originalVitestFn.apply(this, testArgs).apply(this, args);
48+
};
49+
};
50+
}
51+
52+
/**
53+
* Gets a function wrapping the body of a vitest `describe` block to execute in a
54+
* synchronous-only zone.
55+
*/
56+
function wrapDescribeInZone(describeBody: Function): Function {
57+
return function (this: unknown, ...args: any[]) {
58+
return syncZone.run(describeBody, this, args);
59+
};
60+
}
61+
62+
/**
63+
* Gets a function wrapping the body of a vitest `it/beforeEach/afterEach` block to
64+
* execute in a ProxyZone zone.
65+
* This will run in the `proxyZone`.
66+
*/
67+
function wrapTestInZone(testBody: Function, isTestFunc = false): Function {
68+
if (typeof testBody !== 'function') {
69+
return testBody;
70+
}
71+
const wrappedFunc = function () {
72+
proxyZoneSpec.isTestFunc = isTestFunc;
73+
return proxyZone.run(testBody, null, arguments as any);
74+
};
75+
// Update the length of wrappedFunc to be the same as the length of the testBody
76+
// So vitest core can handle whether the test function has `done()` or not correctly
77+
Object.defineProperty(wrappedFunc, 'length', {
78+
configurable: true,
79+
writable: true,
80+
enumerable: false,
81+
});
82+
wrappedFunc.length = testBody.length;
83+
return wrappedFunc;
84+
}
85+
86+
['suite', 'describe'].forEach((methodName) => {
87+
let originalVitestFn: Function = context[methodName];
88+
if (context[Zone.__symbol__(methodName)]) {
89+
return;
90+
}
91+
92+
context[Zone.__symbol__(methodName)] = originalVitestFn;
93+
context[methodName] = function (this: unknown, ...args: any[]) {
94+
args[1] = wrapDescribeInZone(args[1]);
95+
return originalVitestFn.apply(this, args);
96+
};
97+
98+
[
99+
'skip',
100+
'skipIf',
101+
'runIf',
102+
'only',
103+
'concurrent',
104+
'sequential',
105+
'shuffle',
106+
'todo',
107+
'each',
108+
'for',
109+
].forEach((subMethodName) => {
110+
context[methodName][subMethodName] = wrapDescribeFactoryInZone(
111+
(originalVitestFn as any)[subMethodName],
112+
);
113+
});
114+
});
115+
116+
['it', 'test'].forEach((methodName) => {
117+
let originalVitestFn: Function = context[methodName];
118+
if (context[Zone.__symbol__(methodName)]) {
119+
return;
120+
}
121+
122+
context[Zone.__symbol__(methodName)] = originalVitestFn;
123+
context[methodName] = function (this: unknown, ...args: any[]) {
124+
args[1] = wrapTestInZone(args[1], true);
125+
return originalVitestFn.apply(this, args);
126+
};
127+
128+
[
129+
'skip',
130+
'skipIf',
131+
'runIf',
132+
'only',
133+
'concurrent',
134+
'sequential',
135+
'todo',
136+
'fails',
137+
'each',
138+
'for',
139+
].forEach((subMethodName) => {
140+
context[methodName][subMethodName] = wrapTestFactoryInZone(
141+
(originalVitestFn as any)[subMethodName],
142+
);
143+
});
144+
});
145+
146+
['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach((methodName) => {
147+
const originalVitestFn: Function = context[methodName];
148+
if (context[Zone.__symbol__(methodName)]) {
149+
return;
150+
}
151+
152+
context[Zone.__symbol__(methodName)] = originalVitestFn;
153+
context[methodName] = function (this: unknown, ...args: any[]) {
154+
args[0] = wrapTestInZone(args[0]);
155+
return originalVitestFn.apply(this, args);
156+
};
157+
});
158+
});
159+
}

0 commit comments

Comments
 (0)