Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/browser-mode/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('browser mode - basic', () => {
expect(cli.stdout).toContain('dom.test.ts');
expect(cli.stdout).toContain('events.test.ts');
expect(cli.stdout).toContain('async.test.ts');
expect(cli.stdout).toContain('fakeTimers.test.ts');
expect(cli.stdout).not.toContain('/scheduler.html');
});

Expand Down
224 changes: 224 additions & 0 deletions e2e/browser-mode/fixtures/basic/tests/fakeTimers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { afterEach, describe, expect, it, rstest } from '@rstest/core';

const waitRealTimer = () =>
new Promise<void>((resolve) => {
const channel = new MessageChannel();
channel.port1.onmessage = () => {
channel.port1.close();
channel.port2.close();
resolve();
};
channel.port2.postMessage(undefined);
});

describe('Fake timers', () => {
afterEach(() => {
if (rstest.isFakeTimers()) {
rstest.useRealTimers();
}
});

it('should fake and restore browser timer globals', () => {
const RealDate = Date;
const realSetTimeout = globalThis.setTimeout;

expect(rstest.isFakeTimers()).toBe(false);

rstest.useFakeTimers({ now: 1000 });

expect(rstest.isFakeTimers()).toBe(true);
expect(Date).not.toBe(RealDate);
expect(globalThis.setTimeout).not.toBe(realSetTimeout);
expect(Date.now()).toBe(1000);
expect(new Date().getTime()).toBe(1000);
expect(performance.now()).toBe(0);

rstest.useRealTimers();

expect(rstest.isFakeTimers()).toBe(false);
expect(Date).toBe(RealDate);
expect(globalThis.setTimeout).toBe(realSetTimeout);
});

it('should run and clear timeout and interval timers', () => {
rstest.useFakeTimers({ now: 0 });

const timeout = rstest.fn();
const clearedTimeout = rstest.fn();
const interval = rstest.fn();

globalThis.setTimeout(timeout, 100);
const timeoutId = globalThis.setTimeout(clearedTimeout, 100);
globalThis.clearTimeout(timeoutId);
const intervalId = globalThis.setInterval(interval, 50);

expect(rstest.getTimerCount()).toBe(2);

rstest.advanceTimersByTime(100);

expect(timeout).toHaveBeenCalledTimes(1);
expect(clearedTimeout).toHaveBeenCalledTimes(0);
expect(interval).toHaveBeenCalledTimes(2);
expect(Date.now()).toBe(100);

globalThis.clearInterval(intervalId);
expect(rstest.getTimerCount()).toBe(0);
});

it('should run all timers and pending timers', () => {
rstest.useFakeTimers({ now: 0 });

const calls: string[] = [];
globalThis.setTimeout(() => calls.push('timeout-100'), 100);
globalThis.setTimeout(() => calls.push('timeout-200'), 200);

rstest.runOnlyPendingTimers();

expect(calls).toEqual(['timeout-100', 'timeout-200']);
expect(Date.now()).toBe(200);

globalThis.setTimeout(() => {
calls.push('outer');
globalThis.setTimeout(() => calls.push('inner'), 10);
}, 10);

rstest.runAllTimers();

expect(calls).toEqual(['timeout-100', 'timeout-200', 'outer', 'inner']);
expect(rstest.getTimerCount()).toBe(0);
});

it('should advance timers asynchronously', async () => {
rstest.useFakeTimers({ now: 0 });

const result = new Promise<number>((resolve) => {
globalThis.setTimeout(() => {
Promise.resolve().then(() => resolve(Date.now()));
}, 100);
});

await rstest.advanceTimersByTimeAsync(100);

await expect(result).resolves.toBe(100);
});

it('should advance to next timer', async () => {
rstest.useFakeTimers({ now: 0 });

const calls: string[] = [];
globalThis.setTimeout(() => calls.push('first'), 100);
globalThis.setTimeout(() => calls.push('second'), 200);

rstest.advanceTimersToNextTimer();

expect(calls).toEqual(['first']);
expect(Date.now()).toBe(100);

await rstest.advanceTimersToNextTimerAsync();

expect(calls).toEqual(['first', 'second']);
expect(Date.now()).toBe(200);
});

it('should advance animation frames', () => {
rstest.useFakeTimers({ now: 0 });

const cb = rstest.fn<(time: number) => void>();
globalThis.requestAnimationFrame(cb);

rstest.advanceTimersToNextFrame();

expect(cb).toHaveBeenCalledTimes(1);
expect(cb.mock.calls[0]?.[0]).toBe(16);
expect(Date.now()).toBe(16);
});

it('should flush faked microtasks', () => {
rstest.useFakeTimers({ now: 0, toFake: ['queueMicrotask'] });

let called = false;
globalThis.queueMicrotask(() => {
called = true;
});

expect(called).toBe(false);

rstest.runAllTicks();

expect(called).toBe(true);
});

it('should set, reset, and clear fake timers', () => {
rstest.useFakeTimers({ now: 1000 });

rstest.setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
expect(Date.now()).toBe(1704067200000);

rstest.setSystemTime({ epochMilliseconds: 1234 });
expect(Date.now()).toBe(1234);

const cb = rstest.fn();
globalThis.setTimeout(cb, 100);
expect(rstest.getTimerCount()).toBe(1);

rstest.clearAllTimers();
expect(rstest.getTimerCount()).toBe(0);

rstest.advanceTimersByTime(100);
expect(cb).toHaveBeenCalledTimes(0);
});

it('should advance timers with string durations', () => {
rstest.useFakeTimers({ now: 0 });

let called = false;
globalThis.setTimeout(() => {
called = true;
}, 1000);

rstest.advanceTimersByTime('00:01');

expect(called).toBe(true);
expect(Date.now()).toBe(1000);
});

it('should jump timers by time', () => {
rstest.useFakeTimers({ now: 0 });

const interval = rstest.fn();
globalThis.setInterval(interval, 1000);

rstest.jumpTimersByTime(5000);

expect(interval).toHaveBeenCalledTimes(1);
expect(Date.now()).toBe(5000);
});

it('should support nextAsync tick mode', async () => {
rstest.useFakeTimers({ now: 0 });

const timeout = new Promise<void>((resolve) => {
globalThis.setTimeout(resolve, 1000);
});

rstest.setTickMode({ mode: 'nextAsync' });
await timeout;

expect(Date.now()).toBe(1000);
});

it('should keep excluded timers real', async () => {
rstest.useFakeTimers({ now: 0, toNotFake: ['setTimeout'] });

let called = false;
globalThis.setTimeout(() => {
called = true;
}, 0);

rstest.advanceTimersByTime(1000);
expect(called).toBe(false);

await waitRealTimer();
Comment thread
9aoy marked this conversation as resolved.
Outdated
expect(called).toBe(true);
});
});
31 changes: 31 additions & 0 deletions e2e/fakeTimers/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ describe('fake timers', () => {
expect(cb1).toHaveBeenCalledTimes(1);
});

it('advanceTimersByTime with string duration', async () => {
const cb = rstest.fn();
setTimeout(cb, 1000);

rstest.advanceTimersByTime('00:01');

expect(cb).toHaveBeenCalledTimes(1);
});

it('advanceTimersToNextTimer', () => {
const cb = rstest.fn();
const cb1 = rstest.fn();
Expand All @@ -80,6 +89,28 @@ describe('fake timers', () => {
expect(rstest.getTimerCount()).toBe(0);
});

it('jumpTimersByTime', () => {
rstest.setSystemTime(0);
const cb = rstest.fn();
setInterval(cb, 100);

rstest.jumpTimersByTime(1000);

expect(cb).toHaveBeenCalledTimes(1);
expect(Date.now()).toBe(1000);
});

it('setTickMode', async () => {
rstest.setSystemTime(0);
const result = new Promise((resolve) => {
setTimeout(() => resolve(Date.now()), 100);
});

rstest.setTickMode({ mode: 'nextAsync' });

await expect(result).resolves.toBe(100);
});

it('should work with node:timers', async () => {
const { setTimeout } = require('node:timers');
const cb = rstest.fn();
Expand Down
2 changes: 0 additions & 2 deletions knip.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@
"entry": [
// Browser-side iframe entry (loaded at runtime by hostController)
"src/client/entry.ts",
// Aliased at build time via rspack resolve.alias in hostController.ts
"src/client/fakeTimersStub.ts",
"src/client/public.ts",
// Test files
"tests/**/*.ts",
Expand Down
1 change: 0 additions & 1 deletion packages/browser/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ Browser mode must stay provider-neutral at the framework boundary.
- `snapshot.ts` — Browser snapshot environment (proxies file ops to host)
- `sourceMapSupport.ts` — Source map handling for browser
- `public.ts` — Re-exports runtime API for browser
- `fakeTimersStub.ts` — Stub for @sinonjs/fake-timers in browser

## Commands

Expand Down
91 changes: 0 additions & 91 deletions packages/browser/src/client/fakeTimersStub.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/browser/src/hostController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,6 @@ const createBrowserRuntime = async ({
// Browser runtime APIs for entry.ts and public.ts
// Uses dist file with extractSourceMap to preserve sourcemap chain for inline snapshots
'@rstest/core/internal/browser-runtime': browserRuntimePath,
'@sinonjs/fake-timers': resolveBrowserFile('client/fakeTimersStub.ts'),
};

const rsbuildInstance = await createRsbuild({
Expand Down
Loading
Loading