Skip to content

Commit 17b862c

Browse files
JiaLiPassionmhevery
authored andcommitted
feat: add an tickOptions parameter with property processNewMacroTasksSynchronously. (#33838)
This option will control whether to invoke the new macro tasks when ticking. Close #33799 PR Close #33838
1 parent 2562a3b commit 17b862c

9 files changed

Lines changed: 258 additions & 70 deletions

File tree

aio/content/examples/testing/src/app/demo/async-helper.spec.ts

Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ describe('Angular async helper', () => {
2525
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
2626

2727
it('should run async test with task', async(() => {
28-
const id = setInterval(() => {
29-
actuallyDone = true;
30-
clearInterval(id);
31-
}, 100);
32-
}));
28+
const id = setInterval(() => {
29+
actuallyDone = true;
30+
clearInterval(id);
31+
}, 100);
32+
}));
3333

3434
it('should run async test with successful promise', async(() => {
35-
const p = new Promise(resolve => { setTimeout(resolve, 10); });
36-
p.then(() => { actuallyDone = true; });
37-
}));
35+
const p = new Promise(resolve => { setTimeout(resolve, 10); });
36+
p.then(() => { actuallyDone = true; });
37+
}));
3838

3939
it('should run async test with failed promise', async(() => {
40-
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
41-
p.catch(() => { actuallyDone = true; });
42-
}));
40+
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
41+
p.catch(() => { actuallyDone = true; });
42+
}));
4343

4444
// Use done. Can also use async or fakeAsync.
4545
it('should run async test with successful delayed Observable', (done: DoneFn) => {
@@ -48,56 +48,84 @@ describe('Angular async helper', () => {
4848
});
4949

5050
it('should run async test with successful delayed Observable', async(() => {
51-
const source = of (true).pipe(delay(10));
52-
source.subscribe(val => actuallyDone = true, err => fail(err));
53-
}));
51+
const source = of (true).pipe(delay(10));
52+
source.subscribe(val => actuallyDone = true, err => fail(err));
53+
}));
5454

5555
it('should run async test with successful delayed Observable', fakeAsync(() => {
56-
const source = of (true).pipe(delay(10));
57-
source.subscribe(val => actuallyDone = true, err => fail(err));
56+
const source = of (true).pipe(delay(10));
57+
source.subscribe(val => actuallyDone = true, err => fail(err));
5858

59-
tick(10);
60-
}));
59+
tick(10);
60+
}));
6161
});
6262

6363
describe('fakeAsync', () => {
6464
// #docregion fake-async-test-tick
6565
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
66-
let called = false;
67-
setTimeout(() => { called = true; }, 100);
68-
tick(100);
69-
expect(called).toBe(true);
70-
}));
66+
let called = false;
67+
setTimeout(() => { called = true; }, 100);
68+
tick(100);
69+
expect(called).toBe(true);
70+
}));
7171
// #enddocregion fake-async-test-tick
7272

73+
// #docregion fake-async-test-tick-new-macro-task-sync
74+
it('should run new macro task callback with delay after call tick with millis',
75+
fakeAsync(() => {
76+
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
77+
const callback = jasmine.createSpy('callback');
78+
nestedTimer(callback);
79+
expect(callback).not.toHaveBeenCalled();
80+
tick(0);
81+
// the nested timeout will also be triggered
82+
expect(callback).toHaveBeenCalled();
83+
}));
84+
// #enddocregion fake-async-test-tick-new-macro-task-sync
85+
86+
// #docregion fake-async-test-tick-new-macro-task-async
87+
it('should not run new macro task callback with delay after call tick with millis',
88+
fakeAsync(() => {
89+
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
90+
const callback = jasmine.createSpy('callback');
91+
nestedTimer(callback);
92+
expect(callback).not.toHaveBeenCalled();
93+
tick(0, {processNewMacroTasksSynchronously: false});
94+
// the nested timeout will not be triggered
95+
expect(callback).not.toHaveBeenCalled();
96+
tick(0);
97+
expect(callback).toHaveBeenCalled();
98+
}));
99+
// #enddocregion fake-async-test-tick-new-macro-task-async
100+
73101
// #docregion fake-async-test-date
74102
it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
75-
const start = Date.now();
76-
tick(100);
77-
const end = Date.now();
78-
expect(end - start).toBe(100);
79-
}));
103+
const start = Date.now();
104+
tick(100);
105+
const end = Date.now();
106+
expect(end - start).toBe(100);
107+
}));
80108
// #enddocregion fake-async-test-date
81109

82110
// #docregion fake-async-test-rxjs
83111
it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => {
84-
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
85-
// to patch rxjs scheduler
86-
let result = null;
87-
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
88-
expect(result).toBeNull();
89-
tick(1000);
90-
expect(result).toBe('hello');
91-
92-
const start = new Date().getTime();
93-
let dateDiff = 0;
94-
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
95-
96-
tick(1000);
97-
expect(dateDiff).toBe(1000);
98-
tick(1000);
99-
expect(dateDiff).toBe(2000);
100-
}));
112+
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
113+
// to patch rxjs scheduler
114+
let result = null;
115+
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
116+
expect(result).toBeNull();
117+
tick(1000);
118+
expect(result).toBe('hello');
119+
120+
const start = new Date().getTime();
121+
let dateDiff = 0;
122+
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
123+
124+
tick(1000);
125+
expect(dateDiff).toBe(1000);
126+
tick(1000);
127+
expect(dateDiff).toBe(2000);
128+
}));
101129
// #enddocregion fake-async-test-rxjs
102130
});
103131

aio/content/guide/testing.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,8 @@ You do have to call [tick()](api/core/testing/tick) to advance the (virtual) clo
12661266
Calling [tick()](api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish.
12671267
In this case, it waits for the error handler's `setTimeout()`.
12681268

1269-
The [tick()](api/core/testing/tick) function accepts milliseconds as a parameter (defaults to 0 if not provided). The parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback.
1269+
The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called processNewMacroTasksSynchronously (defaults is true) represents whether to invoke
1270+
new generated macro tasks when ticking.
12701271

12711272
<code-example
12721273
path="testing/src/app/demo/async-helper.spec.ts"
@@ -1276,6 +1277,22 @@ The [tick()](api/core/testing/tick) function accepts milliseconds as a parameter
12761277
The [tick()](api/core/testing/tick) function is one of the Angular testing utilities that you import with `TestBed`.
12771278
It's a companion to `fakeAsync()` and you can only call it within a `fakeAsync()` body.
12781279

1280+
#### tickOptions
1281+
1282+
<code-example
1283+
path="testing/src/app/demo/async-helper.spec.ts"
1284+
region="fake-async-test-tick-new-macro-task-sync">
1285+
</code-example>
1286+
1287+
In this example, we have a new macro task (nested setTimeout), by default, when we `tick`, the setTimeout `outside` and `nested` will both be triggered.
1288+
1289+
<code-example
1290+
path="testing/src/app/demo/async-helper.spec.ts"
1291+
region="fake-async-test-tick-new-macro-task-async">
1292+
</code-example>
1293+
1294+
And in some case, we don't want to trigger the new maco task when ticking, we can use `tick(milliseconds, {processNewMacroTasksSynchronously: false})` to not invoke new maco task.
1295+
12791296
#### Comparing dates inside fakeAsync()
12801297
12811298
`fakeAsync()` simulates passage of time, which allows you to calculate the difference between dates inside `fakeAsync()`.

packages/core/test/fake_async_spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,31 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
127127
expect(ran).toEqual(true);
128128
}));
129129

130+
it('should run new macro tasks created by timer callback', fakeAsync(() => {
131+
function nestedTimer(callback: () => any): void {
132+
setTimeout(() => setTimeout(() => callback()));
133+
}
134+
const callback = jasmine.createSpy('callback');
135+
nestedTimer(callback);
136+
expect(callback).not.toHaveBeenCalled();
137+
tick(0);
138+
expect(callback).toHaveBeenCalled();
139+
}));
140+
141+
it('should not queue nested timer on tick with processNewMacroTasksSynchronously=false',
142+
fakeAsync(() => {
143+
function nestedTimer(callback: () => any): void {
144+
setTimeout(() => setTimeout(() => callback()));
145+
}
146+
const callback = jasmine.createSpy('callback');
147+
nestedTimer(callback);
148+
expect(callback).not.toHaveBeenCalled();
149+
tick(0, {processNewMacroTasksSynchronously: false});
150+
expect(callback).not.toHaveBeenCalled();
151+
flush();
152+
expect(callback).toHaveBeenCalled();
153+
}));
154+
130155
it('should run queued timer only once', fakeAsync(() => {
131156
let cycles = 0;
132157
setTimeout(() => { cycles++; }, 10);

packages/core/testing/src/fake_async.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,54 @@ export function fakeAsync(fn: Function): (...args: any[]) => any {
6262
*
6363
* {@example core/testing/ts/fake_async.ts region='basic'}
6464
*
65+
* @param millis, the number of millisecond to advance the virtual timer
66+
* @param tickOptions, the options of tick with a flag called
67+
* processNewMacroTasksSynchronously, whether to invoke the new macroTasks, by default is
68+
* false, means the new macroTasks will be invoked
69+
*
70+
* For example,
71+
*
72+
* it ('test with nested setTimeout', fakeAsync(() => {
73+
* let nestedTimeoutInvoked = false;
74+
* function funcWithNestedTimeout() {
75+
* setTimeout(() => {
76+
* nestedTimeoutInvoked = true;
77+
* });
78+
* };
79+
* setTimeout(funcWithNestedTimeout);
80+
* tick();
81+
* expect(nestedTimeoutInvoked).toBe(true);
82+
* }));
83+
*
84+
* in this case, we have a nested timeout (new macroTask), when we tick, both the
85+
* funcWithNestedTimeout and the nested timeout both will be invoked.
86+
*
87+
* it ('test with nested setTimeout', fakeAsync(() => {
88+
* let nestedTimeoutInvoked = false;
89+
* function funcWithNestedTimeout() {
90+
* setTimeout(() => {
91+
* nestedTimeoutInvoked = true;
92+
* });
93+
* };
94+
* setTimeout(funcWithNestedTimeout);
95+
* tick(0, {processNewMacroTasksSynchronously: false});
96+
* expect(nestedTimeoutInvoked).toBe(false);
97+
* }));
98+
*
99+
* if we pass the tickOptions with processNewMacroTasksSynchronously to be false, the nested timeout
100+
* will not be invoked.
101+
*
102+
*
65103
* @publicApi
66104
*/
67-
export function tick(millis: number = 0): void {
105+
export function tick(
106+
millis: number = 0, tickOptions: {processNewMacroTasksSynchronously: boolean} = {
107+
processNewMacroTasksSynchronously: true
108+
}): void {
68109
if (fakeAsyncTestModule) {
69-
return fakeAsyncTestModule.tick(millis);
110+
return fakeAsyncTestModule.tick(millis, tickOptions);
70111
} else {
71-
return tickFallback(millis);
112+
return tickFallback(millis, tickOptions);
72113
}
73114
}
74115

packages/core/testing/src/fake_async_fallback.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,11 @@ function _getFakeAsyncZoneSpec(): any {
118118
*
119119
* @publicApi
120120
*/
121-
export function tickFallback(millis: number = 0): void {
122-
_getFakeAsyncZoneSpec().tick(millis);
121+
export function tickFallback(
122+
millis: number = 0, tickOptions: {processNewMacroTasksSynchronously: boolean} = {
123+
processNewMacroTasksSynchronously: true
124+
}): void {
125+
_getFakeAsyncZoneSpec().tick(millis, null, tickOptions);
123126
}
124127

125128
/**

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate)
117117
*
118118
* @experimental
119119
*/
120-
function tick(millis: number = 0): void { _getFakeAsyncZoneSpec().tick(millis); }
120+
function tick(millis: number = 0, ignoreNestedTimeout = false): void {
121+
_getFakeAsyncZoneSpec().tick(millis, null, ignoreNestedTimeout);
122+
}
121123

122124
/**
123125
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone by

0 commit comments

Comments
 (0)