Skip to content

Commit 1b7add7

Browse files
committed
exposing runToFrame() from sinon/fake-timers
1 parent 96f00c6 commit 1b7add7

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ describe('FakeTimers', () => {
103103
timers.useFakeTimers();
104104
expect(global.clearImmediate).not.toBe(origClearImmediate);
105105
});
106+
107+
it('mocks requestAnimationFrame if it exists on global', () => {
108+
const global = {
109+
Date,
110+
clearTimeout,
111+
requestAnimationFrame: () => -1,
112+
setTimeout,
113+
} as unknown as typeof globalThis;
114+
const timers = new FakeTimers({config: makeProjectConfig(), global});
115+
timers.useFakeTimers();
116+
expect(global.requestAnimationFrame).toBeDefined();
117+
});
118+
119+
it('mocks cancelAnimationFrame if it exists on global', () => {
120+
const global = {
121+
Date,
122+
cancelAnimationFrame: () => {},
123+
clearTimeout,
124+
setTimeout,
125+
} as unknown as typeof globalThis;
126+
const timers = new FakeTimers({config: makeProjectConfig(), global});
127+
timers.useFakeTimers();
128+
expect(global.cancelAnimationFrame).toBeDefined();
129+
});
106130
});
107131

108132
describe('runAllTicks', () => {
@@ -570,6 +594,182 @@ describe('FakeTimers', () => {
570594
});
571595
});
572596

597+
describe('runToFrame', () => {
598+
it('runs scheduled animation frames in order', () => {
599+
const global = {
600+
Date,
601+
clearTimeout,
602+
process,
603+
requestAnimationFrame: () => -1,
604+
setTimeout,
605+
} as unknown as typeof globalThis;
606+
607+
const timers = new FakeTimers({config: makeProjectConfig(), global});
608+
timers.useFakeTimers();
609+
610+
const runOrder: Array<string> = [];
611+
const mock1 = jest.fn(() => runOrder.push('mock1'));
612+
const mock2 = jest.fn(() => runOrder.push('mock2'));
613+
const mock3 = jest.fn(() => runOrder.push('mock3'));
614+
615+
global.requestAnimationFrame(mock1);
616+
global.requestAnimationFrame(mock2);
617+
global.requestAnimationFrame(mock3);
618+
619+
timers.runToFrame();
620+
621+
expect(runOrder).toEqual(['mock1', 'mock2', 'mock3']);
622+
});
623+
624+
it('should only run currently scheduled animation frames', () => {
625+
const global = {
626+
Date,
627+
clearTimeout,
628+
process,
629+
requestAnimationFrame: () => -1,
630+
setTimeout,
631+
} as unknown as typeof globalThis;
632+
633+
const timers = new FakeTimers({config: makeProjectConfig(), global});
634+
timers.useFakeTimers();
635+
636+
const runOrder: Array<string> = [];
637+
function run() {
638+
runOrder.push('first-frame');
639+
640+
// scheduling another animation frame in the first frame
641+
global.requestAnimationFrame(() => runOrder.push('second-frame'));
642+
}
643+
644+
global.requestAnimationFrame(run);
645+
646+
// only the first frame should be executed
647+
timers.runToFrame();
648+
649+
expect(runOrder).toEqual(['first-frame']);
650+
651+
timers.runToFrame();
652+
653+
expect(runOrder).toEqual(['first-frame', 'second-frame']);
654+
});
655+
656+
it('should allow cancelling of scheduled animation frames', () => {
657+
const global = {
658+
Date,
659+
cancelAnimationFrame: () => {},
660+
clearTimeout,
661+
process,
662+
requestAnimationFrame: () => -1,
663+
setTimeout,
664+
} as unknown as typeof globalThis;
665+
666+
const timers = new FakeTimers({config: makeProjectConfig(), global});
667+
timers.useFakeTimers();
668+
669+
const runOrder: Array<string> = [];
670+
const callback = () => runOrder.push('frame');
671+
672+
const timerId = global.requestAnimationFrame(callback);
673+
global.cancelAnimationFrame(timerId);
674+
675+
// no frames should be executed
676+
timers.runToFrame();
677+
678+
expect(runOrder).toEqual([]);
679+
});
680+
681+
it('should only advance as much time is needed to get to the next frame', () => {
682+
const global = {
683+
Date,
684+
cancelAnimationFrame: () => {},
685+
clearTimeout,
686+
process,
687+
requestAnimationFrame: () => -1,
688+
setTimeout,
689+
} as unknown as typeof globalThis;
690+
691+
const timers = new FakeTimers({config: makeProjectConfig(), global});
692+
timers.useFakeTimers();
693+
694+
const runOrder: Array<string> = [];
695+
const start = global.Date.now();
696+
697+
const callback = () => runOrder.push('frame');
698+
global.requestAnimationFrame(callback);
699+
700+
// Advancing timers less than a frame (which is 16ms)
701+
timers.advanceTimersByTime(6);
702+
expect(global.Date.now()).toEqual(start + 6);
703+
704+
// frame not yet executed
705+
expect(runOrder).toEqual([]);
706+
707+
// move timers forward to execute frame
708+
timers.runToFrame();
709+
710+
// frame has executed as time has moved forward 10ms to get to the 16ms frame time
711+
expect(runOrder).toEqual(['frame']);
712+
expect(global.Date.now()).toEqual(start + 16);
713+
});
714+
715+
it('should execute any timers on the way to the animation frame', () => {
716+
const global = {
717+
Date,
718+
cancelAnimationFrame: () => {},
719+
clearTimeout,
720+
process,
721+
requestAnimationFrame: () => -1,
722+
setTimeout,
723+
} as unknown as typeof globalThis;
724+
725+
const timers = new FakeTimers({config: makeProjectConfig(), global});
726+
timers.useFakeTimers();
727+
728+
const runOrder: Array<string> = [];
729+
730+
global.requestAnimationFrame(() => runOrder.push('frame'));
731+
732+
// scheduling a timeout that will be executed on the way to the frame
733+
global.setTimeout(() => runOrder.push('timeout'), 10);
734+
735+
// move timers forward to execute frame
736+
timers.runToFrame();
737+
738+
expect(runOrder).toEqual(['timeout', 'frame']);
739+
});
740+
741+
it('should not execute any timers scheduled inside of a frame', () => {
742+
const global = {
743+
Date,
744+
cancelAnimationFrame: () => {},
745+
clearTimeout,
746+
process,
747+
requestAnimationFrame: () => -1,
748+
setTimeout,
749+
} as unknown as typeof globalThis;
750+
751+
const timers = new FakeTimers({config: makeProjectConfig(), global});
752+
timers.useFakeTimers();
753+
754+
const runOrder: Array<string> = [];
755+
756+
global.requestAnimationFrame(() => {
757+
runOrder.push('frame');
758+
// scheduling a timer inside of a frame
759+
global.setTimeout(() => runOrder.push('timeout'), 1);
760+
});
761+
762+
timers.runToFrame();
763+
764+
// timeout not yet executed
765+
expect(runOrder).toEqual(['frame']);
766+
767+
// validating that the timer will still be executed
768+
timers.advanceTimersByTime(1);
769+
expect(runOrder).toEqual(['frame', 'timeout']);
770+
});
771+
});
772+
573773
describe('reset', () => {
574774
it('resets all pending setTimeouts', () => {
575775
const global = {
@@ -649,6 +849,25 @@ describe('FakeTimers', () => {
649849
timers.advanceTimersByTime(50);
650850
expect(mock1).toHaveBeenCalledTimes(0);
651851
});
852+
853+
it('resets all scheduled animation frames', () => {
854+
const global = {
855+
Date,
856+
clearTimeout,
857+
process,
858+
requestAnimationFrame: () => -1,
859+
setTimeout,
860+
} as unknown as typeof globalThis;
861+
const timers = new FakeTimers({config: makeProjectConfig(), global});
862+
timers.useFakeTimers();
863+
864+
const mock1 = jest.fn();
865+
global.requestAnimationFrame(mock1);
866+
867+
timers.reset();
868+
timers.runAllTimers();
869+
expect(mock1).toHaveBeenCalledTimes(0);
870+
});
652871
});
653872

654873
describe('runOnlyPendingTimers', () => {

packages/jest-fake-timers/src/modernFakeTimers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ export default class FakeTimers {
117117
}
118118
}
119119

120+
runToFrame(): void {
121+
if (this._checkFakeTimers()) {
122+
this._clock.runToFrame();
123+
}
124+
}
125+
120126
useRealTimers(): void {
121127
if (this._fakingTime) {
122128
this._clock.uninstall();

0 commit comments

Comments
 (0)