feat(zone.js): add jest fakeTimers support (#39016)

Close #38851, support `jest` fakeTimers APIs' integration with `fakeAsync()`.
After enable this feature, calling `jest.useFakeTimers()` will make all test
run into `fakeAsync()` automatically.

```
beforeEach(() => {
    jest.useFakeTimers('modern');
  });
  afterEach(() => {
    jest.useRealTimers();
  });

  test('should run into fakeAsync() automatically', () => {
    const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
    expect(fakeAsyncZoneSpec).toBeTruthy();
  });
```

Also there are mappings between `jest` and `zone` APIs.

- `jest.runAllTicks()` will call `flushMicrotasks()`.
- `jest.runAllTimers()` will call `flush()`.
- `jest.advanceTimersByTime()` will call `tick()`
- `jest.runOnlyPendingTimers()` will call `flushOnlyPendingTimers()`
- `jest.advanceTimersToNextTimer()` will call `tickToNext()`
- `jest.clearAllTimers()` will call `removeAllTimers()`
- `jest.getTimerCount()` will call `getTimerCount()`

PR Close #39016
This commit is contained in:
JiaLiPassion
2020-09-22 22:52:20 +09:00
committed by Joey Perrott
parent a48c8edd83
commit 82d54fe8c3
13 changed files with 4700 additions and 14 deletions

View File

@ -92,6 +92,10 @@ class Scheduler {
this._currentRealTime = realTime;
}
getRealSystemTime() {
return OriginalDate.now();
}
scheduleFunction(cb: Function, delay: number, options?: {
args?: any[],
isPeriodic?: boolean,
@ -145,6 +149,27 @@ class Scheduler {
}
}
removeAll(): void {
this._schedulerQueue = [];
}
getTimerCount(): number {
return this._schedulerQueue.length;
}
tickToNext(step: number = 1, doTick?: (elapsed: number) => void, tickOptions?: {
processNewMacroTasksSynchronously: boolean
}) {
if (this._schedulerQueue.length < step) {
return;
}
// Find the last task currently queued in the scheduler queue and tick
// till that time.
const startTime = this._currentTime;
const targetTask = this._schedulerQueue[step - 1];
this.tick(targetTask.endTime - startTime, doTick, tickOptions);
}
tick(millis: number = 0, doTick?: (elapsed: number) => void, tickOptions?: {
processNewMacroTasksSynchronously: boolean
}): void {
@ -212,6 +237,18 @@ class Scheduler {
}
}
flushOnlyPendingTimers(doTick?: (elapsed: number) => void): number {
if (this._schedulerQueue.length === 0) {
return 0;
}
// Find the last task currently queued in the scheduler queue and tick
// till that time.
const startTime = this._currentTime;
const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
this.tick(lastTask.endTime - startTime, doTick, {processNewMacroTasksSynchronously: false});
return this._currentTime - startTime;
}
flush(limit = 20, flushPeriodic = false, doTick?: (elapsed: number) => void): number {
if (flushPeriodic) {
return this.flushPeriodic(doTick);
@ -401,6 +438,10 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
this._scheduler.setCurrentRealTime(realTime);
}
getRealSystemTime() {
return this._scheduler.getRealSystemTime();
}
static patchDate() {
if (!!global[Zone.__symbol__('disableDatePatching')]) {
// we don't want to patch global Date
@ -450,6 +491,20 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
FakeAsyncTestZoneSpec.resetDate();
}
tickToNext(steps: number = 1, doTick?: (elapsed: number) => void, tickOptions: {
processNewMacroTasksSynchronously: boolean
} = {processNewMacroTasksSynchronously: true}): void {
if (steps <= 0) {
return;
}
FakeAsyncTestZoneSpec.assertInZone();
this.flushMicrotasks();
this._scheduler.tickToNext(steps, doTick, tickOptions);
if (this._lastError !== null) {
this._resetLastErrorAndThrow();
}
}
tick(millis: number = 0, doTick?: (elapsed: number) => void, tickOptions: {
processNewMacroTasksSynchronously: boolean
} = {processNewMacroTasksSynchronously: true}): void {
@ -486,6 +541,27 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
return elapsed;
}
flushOnlyPendingTimers(doTick?: (elapsed: number) => void): number {
FakeAsyncTestZoneSpec.assertInZone();
this.flushMicrotasks();
const elapsed = this._scheduler.flushOnlyPendingTimers(doTick);
if (this._lastError !== null) {
this._resetLastErrorAndThrow();
}
return elapsed;
}
removeAllTimers() {
FakeAsyncTestZoneSpec.assertInZone();
this._scheduler.removeAll();
this.pendingPeriodicTimers = [];
this.pendingTimers = [];
}
getTimerCount() {
return this._scheduler.getTimerCount() + this._microtasks.length;
}
// ZoneSpec implementation below.
name: string;