Skip to content

Commit

Permalink
exposing runToFrame() from sinon/fake-timers
Browse files Browse the repository at this point in the history
  • Loading branch information
alexreardon committed Oct 3, 2023
1 parent 96f00c6 commit 1b7add7
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 0 deletions.
219 changes: 219 additions & 0 deletions packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ describe('FakeTimers', () => {
timers.useFakeTimers();
expect(global.clearImmediate).not.toBe(origClearImmediate);
});

it('mocks requestAnimationFrame if it exists on global', () => {
const global = {
Date,
clearTimeout,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;
const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();
expect(global.requestAnimationFrame).toBeDefined();
});

it('mocks cancelAnimationFrame if it exists on global', () => {
const global = {
Date,
cancelAnimationFrame: () => {},
clearTimeout,
setTimeout,
} as unknown as typeof globalThis;
const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();
expect(global.cancelAnimationFrame).toBeDefined();
});
});

describe('runAllTicks', () => {
Expand Down Expand Up @@ -570,6 +594,182 @@ describe('FakeTimers', () => {
});
});

describe('runToFrame', () => {
it('runs scheduled animation frames in order', () => {
const global = {
Date,
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];
const mock1 = jest.fn(() => runOrder.push('mock1'));
const mock2 = jest.fn(() => runOrder.push('mock2'));
const mock3 = jest.fn(() => runOrder.push('mock3'));

global.requestAnimationFrame(mock1);
global.requestAnimationFrame(mock2);
global.requestAnimationFrame(mock3);

timers.runToFrame();

expect(runOrder).toEqual(['mock1', 'mock2', 'mock3']);
});

it('should only run currently scheduled animation frames', () => {
const global = {
Date,
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];
function run() {
runOrder.push('first-frame');

// scheduling another animation frame in the first frame
global.requestAnimationFrame(() => runOrder.push('second-frame'));
}

global.requestAnimationFrame(run);

// only the first frame should be executed
timers.runToFrame();

expect(runOrder).toEqual(['first-frame']);

timers.runToFrame();

expect(runOrder).toEqual(['first-frame', 'second-frame']);
});

it('should allow cancelling of scheduled animation frames', () => {
const global = {
Date,
cancelAnimationFrame: () => {},
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];
const callback = () => runOrder.push('frame');

const timerId = global.requestAnimationFrame(callback);
global.cancelAnimationFrame(timerId);

// no frames should be executed
timers.runToFrame();

expect(runOrder).toEqual([]);
});

it('should only advance as much time is needed to get to the next frame', () => {
const global = {
Date,
cancelAnimationFrame: () => {},
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];
const start = global.Date.now();

const callback = () => runOrder.push('frame');
global.requestAnimationFrame(callback);

// Advancing timers less than a frame (which is 16ms)
timers.advanceTimersByTime(6);
expect(global.Date.now()).toEqual(start + 6);

// frame not yet executed
expect(runOrder).toEqual([]);

// move timers forward to execute frame
timers.runToFrame();

// frame has executed as time has moved forward 10ms to get to the 16ms frame time
expect(runOrder).toEqual(['frame']);
expect(global.Date.now()).toEqual(start + 16);
});

it('should execute any timers on the way to the animation frame', () => {
const global = {
Date,
cancelAnimationFrame: () => {},
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];

global.requestAnimationFrame(() => runOrder.push('frame'));

// scheduling a timeout that will be executed on the way to the frame
global.setTimeout(() => runOrder.push('timeout'), 10);

// move timers forward to execute frame
timers.runToFrame();

expect(runOrder).toEqual(['timeout', 'frame']);
});

it('should not execute any timers scheduled inside of a frame', () => {
const global = {
Date,
cancelAnimationFrame: () => {},
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;

const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const runOrder: Array<string> = [];

global.requestAnimationFrame(() => {
runOrder.push('frame');
// scheduling a timer inside of a frame
global.setTimeout(() => runOrder.push('timeout'), 1);
});

timers.runToFrame();

// timeout not yet executed
expect(runOrder).toEqual(['frame']);

// validating that the timer will still be executed
timers.advanceTimersByTime(1);
expect(runOrder).toEqual(['frame', 'timeout']);
});
});

describe('reset', () => {
it('resets all pending setTimeouts', () => {
const global = {
Expand Down Expand Up @@ -649,6 +849,25 @@ describe('FakeTimers', () => {
timers.advanceTimersByTime(50);
expect(mock1).toHaveBeenCalledTimes(0);
});

it('resets all scheduled animation frames', () => {
const global = {
Date,
clearTimeout,
process,
requestAnimationFrame: () => -1,
setTimeout,
} as unknown as typeof globalThis;
const timers = new FakeTimers({config: makeProjectConfig(), global});
timers.useFakeTimers();

const mock1 = jest.fn();
global.requestAnimationFrame(mock1);

timers.reset();
timers.runAllTimers();
expect(mock1).toHaveBeenCalledTimes(0);
});
});

describe('runOnlyPendingTimers', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/jest-fake-timers/src/modernFakeTimers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export default class FakeTimers {
}
}

runToFrame(): void {
if (this._checkFakeTimers()) {
this._clock.runToFrame();
}
}

useRealTimers(): void {
if (this._fakingTime) {
this._clock.uninstall();
Expand Down

0 comments on commit 1b7add7

Please sign in to comment.