mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Compare commits
13 Commits
jarred/aut
...
pfg/fake-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1309c3aba | ||
|
|
534aa441ba | ||
|
|
7e1927d980 | ||
|
|
4dbb62edf1 | ||
|
|
0126c67af1 | ||
|
|
0380651913 | ||
|
|
e0a2a9ed84 | ||
|
|
148bd5e9f6 | ||
|
|
91716fa72e | ||
|
|
785a3ca13f | ||
|
|
75e982b618 | ||
|
|
68cc7538cd | ||
|
|
a83d57b1de |
4
TODO.md
Normal file
4
TODO.md
Normal file
@@ -0,0 +1,4 @@
|
||||
need to support bun.timespec.now() for time mocking
|
||||
|
||||
- support performance.now
|
||||
- consider supporting edge-case where `timeout0(A, timeout0(B)), timeout0(C)` prints `A=0, C=0, B=1` (date.now())
|
||||
32
packages/bun-types/test.d.ts
vendored
32
packages/bun-types/test.d.ts
vendored
@@ -97,6 +97,12 @@ declare module "bun:test" {
|
||||
function setTimeout(milliseconds: number): void;
|
||||
function useFakeTimers(): void;
|
||||
function useRealTimers(): void;
|
||||
function setSystemTime(now: string | number | Date): void;
|
||||
function getMockedSystemTime(): Date | null;
|
||||
function getRealSystemTime(): number;
|
||||
function isFakeTimers(): boolean;
|
||||
function runAllTimers(): void;
|
||||
function runAllTicks(): void;
|
||||
function spyOn<T extends object, K extends keyof T>(
|
||||
obj: T,
|
||||
methodOrPropertyValue: K,
|
||||
@@ -184,6 +190,32 @@ declare module "bun:test" {
|
||||
resetAllMocks: typeof jest.resetAllMocks;
|
||||
useFakeTimers: typeof jest.useFakeTimers;
|
||||
useRealTimers: typeof jest.useRealTimers;
|
||||
runAllTicks: typeof jest.runAllTicks;
|
||||
/**
|
||||
* Advances all timers to the next timer (earliest scheduled timer).
|
||||
* Useful for stepping through timers one at a time.
|
||||
*
|
||||
* @returns this (the vi object) for chaining
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { vi, test, expect } from "bun:test";
|
||||
*
|
||||
* test("vi.advanceTimersToNextTimer", () => {
|
||||
* vi.useFakeTimers();
|
||||
*
|
||||
* let i = 0;
|
||||
* setInterval(() => console.log(++i), 50);
|
||||
*
|
||||
* vi.advanceTimersToNextTimer() // log: 1
|
||||
* .advanceTimersToNextTimer() // log: 2
|
||||
* .advanceTimersToNextTimer(); // log: 3
|
||||
*
|
||||
* vi.useRealTimers();
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
advanceTimersToNextTimer(): typeof vi;
|
||||
};
|
||||
|
||||
interface FunctionLike {
|
||||
|
||||
@@ -120,6 +120,10 @@ argv: []const []const u8 = &[_][]const u8{},
|
||||
|
||||
origin_timer: std.time.Timer = undefined,
|
||||
origin_timestamp: u64 = 0,
|
||||
/// Frozen value of performance.now() when fake timers are enabled
|
||||
/// When < 0, fake timers are disabled for performance.now() and real time is used
|
||||
/// When >= 0, this is the frozen value that performance.now() returns (advances with fake time)
|
||||
overriden_performance_now_ms: f64 = -1,
|
||||
macro_event_loop: EventLoop = EventLoop{},
|
||||
regular_event_loop: EventLoop = EventLoop{},
|
||||
event_loop: *EventLoop = undefined,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const Timer = @This();
|
||||
|
||||
extern fn Bun__Timer__isFakeTimersEnabled(*jsc.JSGlobalObject) bool;
|
||||
extern fn Bun__Timer__advanceDateNowMs(*jsc.JSGlobalObject, f64) void;
|
||||
|
||||
/// TimeoutMap is map of i32 to nullable Timeout structs
|
||||
/// i32 is exposed to JavaScript and can be used with clearTimeout, clearInterval, etc.
|
||||
/// When Timeout is null, it means the tasks have been scheduled but not yet executed.
|
||||
@@ -19,6 +22,7 @@ pub const All = struct {
|
||||
lock: bun.Mutex = .{},
|
||||
thread_id: std.Thread.Id,
|
||||
timers: TimerHeap = .{ .context = {} },
|
||||
vi_timers: TimerHeap = .{ .context = {} },
|
||||
active_timer_count: i32 = 0,
|
||||
uv_timer: if (Environment.isWindows) uv.Timer else void = if (Environment.isWindows) std.mem.zeroes(uv.Timer),
|
||||
/// Whether we have emitted a warning for passing a negative timeout duration
|
||||
@@ -30,6 +34,8 @@ pub const All = struct {
|
||||
epoch: u25 = 0,
|
||||
immediate_ref_count: i32 = 0,
|
||||
uv_idle: if (Environment.isWindows) uv.uv_idle_t else void = if (Environment.isWindows) std.mem.zeroes(uv.uv_idle_t),
|
||||
/// Virtual time for fake timers - when set (not null), new timers will be scheduled relative to this time
|
||||
vi_current_time: ?timespec = null,
|
||||
|
||||
// Event loop delay monitoring (not exposed to JS)
|
||||
event_loop_delay: EventLoopDelayMonitor = .{},
|
||||
@@ -61,18 +67,32 @@ pub const All = struct {
|
||||
pub fn insert(this: *All, timer: *EventLoopTimer) void {
|
||||
this.lock.lock();
|
||||
defer this.lock.unlock();
|
||||
this.timers.insert(timer);
|
||||
|
||||
const vm: *VirtualMachine = @alignCast(@fieldParentPtr("timer", this));
|
||||
// Check if fake timers are enabled and timer is not a WTFTimer
|
||||
if (timer.tag.allowFakeTimers() and Bun__Timer__isFakeTimersEnabled(vm.global)) {
|
||||
timer.is_vi_timer = true;
|
||||
this.vi_timers.insert(timer);
|
||||
} else {
|
||||
timer.is_vi_timer = false;
|
||||
this.timers.insert(timer);
|
||||
}
|
||||
timer.state = .ACTIVE;
|
||||
|
||||
if (Environment.isWindows) {
|
||||
this.ensureUVTimer(@alignCast(@fieldParentPtr("timer", this)));
|
||||
this.ensureUVTimer(vm);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(this: *All, timer: *EventLoopTimer) void {
|
||||
this.lock.lock();
|
||||
defer this.lock.unlock();
|
||||
this.timers.remove(timer);
|
||||
|
||||
if (timer.is_vi_timer) {
|
||||
this.vi_timers.remove(timer);
|
||||
} else {
|
||||
this.timers.remove(timer);
|
||||
}
|
||||
|
||||
timer.state = .CANCELLED;
|
||||
}
|
||||
@@ -82,7 +102,12 @@ pub const All = struct {
|
||||
this.lock.lock();
|
||||
defer this.lock.unlock();
|
||||
if (timer.state == .ACTIVE) {
|
||||
this.timers.remove(timer);
|
||||
// Remove from the correct heap
|
||||
if (timer.is_vi_timer) {
|
||||
this.vi_timers.remove(timer);
|
||||
} else {
|
||||
this.timers.remove(timer);
|
||||
}
|
||||
}
|
||||
|
||||
timer.state = .ACTIVE;
|
||||
@@ -98,9 +123,17 @@ pub const All = struct {
|
||||
flags.epoch = this.epoch;
|
||||
}
|
||||
|
||||
this.timers.insert(timer);
|
||||
const vm: *VirtualMachine = @alignCast(@fieldParentPtr("timer", this));
|
||||
// Check if fake timers are enabled and timer is not a WTFTimer
|
||||
if (timer.tag != .WTFTimer and Bun__Timer__isFakeTimersEnabled(vm.global)) {
|
||||
timer.is_vi_timer = true;
|
||||
this.vi_timers.insert(timer);
|
||||
} else {
|
||||
timer.is_vi_timer = false;
|
||||
this.timers.insert(timer);
|
||||
}
|
||||
if (Environment.isWindows) {
|
||||
this.ensureUVTimer(@alignCast(@fieldParentPtr("timer", this)));
|
||||
this.ensureUVTimer(vm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +579,150 @@ pub const All = struct {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
/// Internal function that advances to the next timer in the vi_timers heap.
|
||||
/// Returns true if a timer was fired, false if no timers remain.
|
||||
fn advanceToNextViTimer(timer: *All, vm: *VirtualMachine) bool {
|
||||
// Get the next timer from the heap
|
||||
timer.lock.lock();
|
||||
const event_timer = timer.vi_timers.deleteMin();
|
||||
timer.lock.unlock();
|
||||
|
||||
if (event_timer) |et| {
|
||||
// Calculate the time delta between current virtual time and the timer's scheduled time
|
||||
const old_vi_time = timer.vi_current_time orelse timespec{ .sec = 0, .nsec = 0 };
|
||||
const delta_ms: f64 = @floatFromInt(et.next.ms() - old_vi_time.ms());
|
||||
|
||||
// Set the virtual time to this timer's scheduled time
|
||||
// This ensures nested timers scheduled during callbacks get the correct base time
|
||||
timer.vi_current_time = et.next;
|
||||
|
||||
// Also advance Date.now() by the same delta
|
||||
// This keeps Date.now() in sync with the virtual time
|
||||
Bun__Timer__advanceDateNowMs(vm.global, delta_ms);
|
||||
|
||||
// Fire the timer at its scheduled time
|
||||
const arm_result = et.fire(&et.next, vm);
|
||||
|
||||
// If the timer wants to rearm (like setInterval), reinsert it
|
||||
if (arm_result == .rearm) {
|
||||
timer.lock.lock();
|
||||
timer.vi_timers.insert(et);
|
||||
timer.lock.unlock();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn runAllTimers(
|
||||
globalThis: *JSGlobalObject,
|
||||
_: *jsc.CallFrame,
|
||||
) JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
const vm = globalThis.bunVM();
|
||||
const timer = &vm.timer;
|
||||
|
||||
// Keep firing timers until there are no more
|
||||
// We need to keep checking the heap because firing timers can schedule new timers
|
||||
while (advanceToNextViTimer(timer, vm)) {}
|
||||
|
||||
// Clear the virtual time when we're done
|
||||
timer.vi_current_time = null;
|
||||
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
pub fn runOnlyPendingTimers(
|
||||
globalThis: *JSGlobalObject,
|
||||
_: *jsc.CallFrame,
|
||||
) JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
const vm = globalThis.bunVM();
|
||||
const timer = &vm.timer;
|
||||
|
||||
// Collect all currently pending timers into a list by removing them from the heap
|
||||
var pending_timers = std.ArrayList(*EventLoopTimer).init(bun.default_allocator);
|
||||
defer pending_timers.deinit();
|
||||
|
||||
// Remove all timers from the heap and collect them
|
||||
timer.lock.lock();
|
||||
while (timer.vi_timers.deleteMin()) |event_timer| {
|
||||
pending_timers.append(event_timer) catch |err| bun.handleOom(err);
|
||||
}
|
||||
timer.lock.unlock();
|
||||
|
||||
// Sort timers by their scheduled time to fire them in the correct order
|
||||
std.mem.sort(*EventLoopTimer, pending_timers.items, {}, struct {
|
||||
fn lessThan(_: void, a: *EventLoopTimer, b: *EventLoopTimer) bool {
|
||||
return a.next.order(&b.next) == .lt;
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
// Now fire exactly those timers (not any that get scheduled during execution)
|
||||
for (pending_timers.items) |event_timer| {
|
||||
// Calculate the time delta between current virtual time and the timer's scheduled time
|
||||
const old_vi_time = timer.vi_current_time orelse timespec{ .sec = 0, .nsec = 0 };
|
||||
const delta_ms: f64 = @floatFromInt(event_timer.next.ms() - old_vi_time.ms());
|
||||
|
||||
// Set the virtual time to this timer's scheduled time
|
||||
timer.vi_current_time = event_timer.next;
|
||||
|
||||
// Also advance Date.now() by the same delta
|
||||
Bun__Timer__advanceDateNowMs(vm.global, delta_ms);
|
||||
|
||||
// Fire the timer at its scheduled time
|
||||
const arm_result = event_timer.fire(&event_timer.next, vm);
|
||||
|
||||
// If the timer wants to rearm (like setInterval), reinsert it
|
||||
// but it won't be fired in this call to runOnlyPendingTimers
|
||||
if (arm_result == .rearm) {
|
||||
timer.lock.lock();
|
||||
timer.vi_timers.insert(event_timer);
|
||||
timer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the virtual time when we're done
|
||||
timer.vi_current_time = null;
|
||||
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
pub fn advanceTimersToNextTimer(
|
||||
globalThis: *JSGlobalObject,
|
||||
callframe: *jsc.CallFrame,
|
||||
) JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
const vm = globalThis.bunVM();
|
||||
const timer = &vm.timer;
|
||||
|
||||
// Advance to the next timer (if any)
|
||||
_ = advanceToNextViTimer(timer, vm);
|
||||
|
||||
// Keep the virtual time set for subsequent calls (don't clear it)
|
||||
// Return the 'this' value (the vi object) for chaining
|
||||
return callframe.this();
|
||||
}
|
||||
|
||||
export fn Bun__Timer__initViTime(vm: *VirtualMachine) callconv(.C) void {
|
||||
const timer = &vm.timer;
|
||||
// Initialize virtual time to zero so timers are scheduled relative to time 0
|
||||
timer.vi_current_time = timespec{ .sec = 0, .nsec = 0 };
|
||||
}
|
||||
|
||||
export fn Bun__Timer__clearViTimers(vm: *VirtualMachine) callconv(.C) void {
|
||||
const timer = &vm.timer;
|
||||
timer.lock.lock();
|
||||
defer timer.lock.unlock();
|
||||
|
||||
// Remove all timers from vi_timers heap and cancel them
|
||||
while (timer.vi_timers.deleteMin()) |event_timer| {
|
||||
event_timer.state = .CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
comptime {
|
||||
@export(&jsc.host_fn.wrap3(setImmediate), .{ .name = "Bun__Timer__setImmediate" });
|
||||
@export(&jsc.host_fn.wrap3(sleep), .{ .name = "Bun__Timer__sleep" });
|
||||
@@ -554,7 +731,12 @@ pub const All = struct {
|
||||
@export(&jsc.host_fn.wrap2(clearImmediate), .{ .name = "Bun__Timer__clearImmediate" });
|
||||
@export(&jsc.host_fn.wrap2(clearTimeout), .{ .name = "Bun__Timer__clearTimeout" });
|
||||
@export(&jsc.host_fn.wrap2(clearInterval), .{ .name = "Bun__Timer__clearInterval" });
|
||||
@export(&jsc.host_fn.wrap2(runAllTimers), .{ .name = "Bun__Timer__runAllTimers" });
|
||||
@export(&jsc.host_fn.wrap2(runOnlyPendingTimers), .{ .name = "Bun__Timer__runOnlyPendingTimers" });
|
||||
@export(&jsc.host_fn.wrap2(advanceTimersToNextTimer), .{ .name = "Bun__Timer__advanceTimersToNextTimer" });
|
||||
@export(&getNextID, .{ .name = "Bun__Timer__getNextID" });
|
||||
_ = &Bun__Timer__initViTime;
|
||||
_ = &Bun__Timer__clearViTimers;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ const Self = @This();
|
||||
next: timespec,
|
||||
state: State = .PENDING,
|
||||
tag: Tag,
|
||||
/// Whether this timer is in the vi_timers heap (for fake timers)
|
||||
is_vi_timer: bool = false,
|
||||
/// Internal heap fields.
|
||||
heap: bun.io.heap.IntrusiveField(Self) = .{},
|
||||
|
||||
@@ -96,6 +98,17 @@ pub const Tag = enum {
|
||||
.EventLoopDelayMonitor => jsc.API.Timer.EventLoopDelayMonitor,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn allowFakeTimers(self: Tag) bool {
|
||||
return switch (self) {
|
||||
.WTFTimer, // internal
|
||||
.BunTest, // for test timeouts
|
||||
.EventLoopDelayMonitor, // probably important
|
||||
.StatWatcherScheduler,
|
||||
=> false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const UnreachableTimer = struct {
|
||||
@@ -104,6 +117,17 @@ const UnreachableTimer = struct {
|
||||
if (Environment.ci_assert) bun.assert(false);
|
||||
return .disarm;
|
||||
}
|
||||
|
||||
pub fn allowFakeTimers(self: Tag) bool {
|
||||
return switch (self) {
|
||||
.WTFTimer, // internal
|
||||
.BunTest, // for test timeouts
|
||||
.EventLoopDelayMonitor, // probably important
|
||||
.StatWatcherScheduler,
|
||||
=> false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const TimerCallback = struct {
|
||||
|
||||
@@ -180,6 +180,10 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
|
||||
if (!this.shouldRescheduleTimer(repeat, idle_timeout)) {
|
||||
break :is_timer_done true;
|
||||
}
|
||||
// Recompute time_before_call using virtual time if in fake timers mode
|
||||
const base_time = vm.timer.vi_current_time orelse timespec.now();
|
||||
time_before_call = base_time.addMs(this.interval);
|
||||
|
||||
switch (this.eventLoopTimer().state) {
|
||||
.FIRED => {
|
||||
// If we didn't clear the setInterval, reschedule it starting from
|
||||
@@ -382,7 +386,9 @@ pub fn reschedule(this: *TimerObjectInternals, timer: JSValue, vm: *VirtualMachi
|
||||
// https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L612
|
||||
if (!this.shouldRescheduleTimer(repeat, idle_timeout)) return;
|
||||
|
||||
const now = timespec.msFromNow(this.interval);
|
||||
// Use virtual time if we're in fake timers mode
|
||||
const base_time = vm.timer.vi_current_time orelse timespec.now();
|
||||
const now = base_time.addMs(this.interval);
|
||||
const was_active = this.eventLoopTimer().state == .ACTIVE;
|
||||
if (was_active) {
|
||||
vm.timer.remove(this.eventLoopTimer());
|
||||
|
||||
@@ -27,11 +27,16 @@
|
||||
#include "BunPlugin.h"
|
||||
#include "AsyncContextFrame.h"
|
||||
#include "ErrorCode.h"
|
||||
#include "JSNextTickQueue.h"
|
||||
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsUseFakeTimers);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsUseRealTimers);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsNow);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsSetSystemTime);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsGetMockedSystemTime);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsGetRealSystemTime);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsIsFakeTimers);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsRunAllTicks);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsRestoreAllMocks);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsClearAllMocks);
|
||||
BUN_DECLARE_HOST_FUNCTION(JSMock__jsSpyOn);
|
||||
@@ -1418,15 +1423,53 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionWithImplementation, (JSC::JSGlobalObject
|
||||
using namespace Bun;
|
||||
using namespace JSC;
|
||||
|
||||
// This is a stub. Exists so that the same code can be run in Jest
|
||||
extern "C" void Bun__Timer__initViTime(void*);
|
||||
extern "C" void Bun__Timer__setPerformanceNow(void*, double);
|
||||
extern "C" double Bun__Timer__getPerformanceNow(void*);
|
||||
extern "C" uint64_t Bun__readOriginTimer(void*);
|
||||
|
||||
// Enables fake timers and sets up Date mocking
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsUseFakeTimers, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
// Enable fake timers flag - this will make Date use the overridden time
|
||||
// When overridenDateNow is >= 0, Date will use that value instead of current time
|
||||
// If overridenDateNow is not set (< 0), we'll set it to current time to freeze it
|
||||
if (globalObject->overridenDateNow < 0) {
|
||||
// Initialize with current time if not already set
|
||||
globalObject->overridenDateNow = globalObject->jsDateNow();
|
||||
}
|
||||
|
||||
// Freeze performance.now() at current value when fake timers are enabled
|
||||
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
auto* vm = bunGlobal->bunVM();
|
||||
if (vm) {
|
||||
double currentFrozenValue = Bun__Timer__getPerformanceNow(vm);
|
||||
if (currentFrozenValue < 0) {
|
||||
// Get current performance.now() value and freeze it, rounded up
|
||||
auto nowNano = Bun__readOriginTimer(vm);
|
||||
double currentPerfNow = std::ceil(static_cast<double>(nowNano) / 1000000.0);
|
||||
Bun__Timer__setPerformanceNow(vm, currentPerfNow);
|
||||
}
|
||||
Bun__Timer__initViTime(vm);
|
||||
}
|
||||
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
extern "C" void Bun__Timer__clearViTimers(void*);
|
||||
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsUseRealTimers, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
globalObject->overridenDateNow = -1;
|
||||
|
||||
// Clear all pending fake timers
|
||||
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
auto* vm = bunGlobal->bunVM();
|
||||
if (vm) {
|
||||
Bun__Timer__setPerformanceNow(vm, -1);
|
||||
Bun__Timer__clearViTimers(vm);
|
||||
}
|
||||
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
@@ -1438,14 +1481,142 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSetSystemTime, (JSC::JSGlobalObject * globalO
|
||||
{
|
||||
JSValue argument0 = callframe->argument(0);
|
||||
|
||||
// Handle Date object
|
||||
if (auto* dateInstance = jsDynamicCast<DateInstance*>(argument0)) {
|
||||
if (std::isnormal(dateInstance->internalNumber())) {
|
||||
globalObject->overridenDateNow = dateInstance->internalNumber();
|
||||
}
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
// number > 0 is a valid date otherwise it's invalid and we should reset the time (set to -1)
|
||||
globalObject->overridenDateNow = (argument0.isNumber() && argument0.asNumber() >= 0) ? argument0.asNumber() : -1;
|
||||
|
||||
// Handle string (parse it as a date)
|
||||
if (argument0.isString()) {
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Create a new Date object from the string
|
||||
JSValue dateConstructor = globalObject->dateConstructor();
|
||||
MarkedArgumentBuffer args;
|
||||
args.append(argument0);
|
||||
JSValue dateValue = construct(globalObject, dateConstructor, dateConstructor, args, "Failed to parse date string"_s);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (auto* dateInstance = jsDynamicCast<DateInstance*>(dateValue)) {
|
||||
double timeValue = dateInstance->internalNumber();
|
||||
// Only set if it's a valid date
|
||||
if (std::isnormal(timeValue) || timeValue == 0) {
|
||||
globalObject->overridenDateNow = timeValue;
|
||||
}
|
||||
}
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
// Handle number (timestamp in milliseconds)
|
||||
if (argument0.isNumber()) {
|
||||
double timeValue = argument0.asNumber();
|
||||
// Accept 0 or any positive number as valid timestamps
|
||||
if (timeValue >= 0) {
|
||||
globalObject->overridenDateNow = timeValue;
|
||||
}
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
// If no argument or invalid argument, reset to current time (for compatibility)
|
||||
if (argument0.isUndefinedOrNull()) {
|
||||
globalObject->overridenDateNow = -1;
|
||||
}
|
||||
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
// Returns the currently mocked system time as a Date object, or null if not mocked
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsGetMockedSystemTime, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
// Check if fake timers are active (overridenDateNow >= 0)
|
||||
if (globalObject->overridenDateNow >= 0) {
|
||||
// Return a new Date object with the mocked time
|
||||
auto& vm = globalObject->vm();
|
||||
DateInstance* dateInstance = DateInstance::create(vm, globalObject->dateStructure(), globalObject->overridenDateNow);
|
||||
return JSValue::encode(dateInstance);
|
||||
}
|
||||
|
||||
// Return null if fake timers are not active
|
||||
return JSValue::encode(jsNull());
|
||||
}
|
||||
|
||||
// Always returns the real system time, regardless of mocked time
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsGetRealSystemTime, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
// Get the real system time by temporarily disabling the override
|
||||
double originalOverride = globalObject->overridenDateNow;
|
||||
globalObject->overridenDateNow = -1;
|
||||
double realTime = globalObject->jsDateNow();
|
||||
globalObject->overridenDateNow = originalOverride;
|
||||
|
||||
return JSValue::encode(jsNumber(realTime));
|
||||
}
|
||||
|
||||
// Returns true if fake timers are currently enabled
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsIsFakeTimers, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
// Fake timers are enabled when overridenDateNow is >= 0
|
||||
bool isFakeTimersEnabled = globalObject->overridenDateNow >= 0;
|
||||
return JSValue::encode(jsBoolean(isFakeTimersEnabled));
|
||||
}
|
||||
|
||||
// Export C function for Zig to check if fake timers are enabled
|
||||
extern "C" bool Bun__Timer__isFakeTimersEnabled(JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
return globalObject->overridenDateNow >= 0;
|
||||
}
|
||||
|
||||
extern "C" void Bun__Timer__advancePerformanceNow(void*, double);
|
||||
|
||||
// Export C function for Zig to advance the Date.now() mock time by a delta
|
||||
extern "C" void Bun__Timer__advanceDateNowMs(JSC::JSGlobalObject* globalObject, double deltaMilliseconds)
|
||||
{
|
||||
if (globalObject->overridenDateNow >= 0) {
|
||||
globalObject->overridenDateNow += deltaMilliseconds;
|
||||
}
|
||||
// Also advance performance.now() if fake timers are enabled
|
||||
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
auto* vm = bunGlobal->bunVM();
|
||||
if (vm) {
|
||||
Bun__Timer__advancePerformanceNow(vm, deltaMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
// Drains all pending process.nextTick callbacks
|
||||
// This will also run all microtasks scheduled by themselves
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsRunAllTicks, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
auto& vm = bunGlobal->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Repeatedly drain the nextTick queue and microtasks until both are empty
|
||||
// This matches Vitest's behavior where runAllTicks runs all microtasks scheduled by themselves
|
||||
const int maxIterations = 10000; // Safety limit to prevent infinite loops
|
||||
int iterations = 0;
|
||||
|
||||
while (iterations++ < maxIterations) {
|
||||
bool hadWork = false;
|
||||
|
||||
// Drain nextTick queue if it has callbacks
|
||||
if (auto nextTickQueue = bunGlobal->m_nextTickQueue.get()) {
|
||||
Bun::JSNextTickQueue* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueue);
|
||||
if (!queue->isEmpty()) {
|
||||
queue->drain(vm, bunGlobal);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
hadWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no work was done, we're done
|
||||
if (!hadWork) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return JSValue::encode(callframe->thisValue());
|
||||
}
|
||||
|
||||
1
src/bun.js/bindings/headers.h
generated
1
src/bun.js/bindings/headers.h
generated
@@ -663,6 +663,7 @@ ZIG_DECL JSC::EncodedJSValue Bun__Timer__setInterval(JSC::JSGlobalObject* global
|
||||
ZIG_DECL JSC::EncodedJSValue Bun__Timer__setTimeout(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue arguments, JSC::EncodedJSValue countdown);
|
||||
ZIG_DECL JSC::EncodedJSValue Bun__Timer__sleep(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue promise, JSC::EncodedJSValue countdown);
|
||||
ZIG_DECL JSC::EncodedJSValue Bun__Timer__setImmediate(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue arguments);
|
||||
ZIG_DECL bool Bun__Timer__isFakeTimersEnabled(JSC::JSGlobalObject* globalThis);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ pub const Jest = struct {
|
||||
Expect.js.getConstructor(globalObject),
|
||||
);
|
||||
|
||||
const vi = JSValue.createEmptyObject(globalObject, 8);
|
||||
const vi = JSValue.createEmptyObject(globalObject, 13);
|
||||
vi.put(globalObject, ZigString.static("fn"), mockFn);
|
||||
vi.put(globalObject, ZigString.static("mock"), mockModuleFn);
|
||||
vi.put(globalObject, ZigString.static("spyOn"), spyOn);
|
||||
@@ -254,6 +254,14 @@ pub const Jest = struct {
|
||||
vi.put(globalObject, ZigString.static("clearAllMocks"), clearAllMocks);
|
||||
vi.put(globalObject, ZigString.static("useFakeTimers"), useFakeTimers);
|
||||
vi.put(globalObject, ZigString.static("useRealTimers"), useRealTimers);
|
||||
vi.put(globalObject, ZigString.static("setSystemTime"), setSystemTime);
|
||||
vi.put(globalObject, ZigString.static("getMockedSystemTime"), jsc.host_fn.NewFunction(globalObject, ZigString.static("getMockedSystemTime"), 0, JSMock__jsGetMockedSystemTime, false));
|
||||
vi.put(globalObject, ZigString.static("getRealSystemTime"), jsc.host_fn.NewFunction(globalObject, ZigString.static("getRealSystemTime"), 0, JSMock__jsGetRealSystemTime, false));
|
||||
vi.put(globalObject, ZigString.static("isFakeTimers"), jsc.host_fn.NewFunction(globalObject, ZigString.static("isFakeTimers"), 0, JSMock__jsIsFakeTimers, false));
|
||||
vi.put(globalObject, ZigString.static("runAllTimers"), jsc.host_fn.NewFunction(globalObject, ZigString.static("runAllTimers"), 0, Bun__Timer__runAllTimers, false));
|
||||
vi.put(globalObject, ZigString.static("runOnlyPendingTimers"), jsc.host_fn.NewFunction(globalObject, ZigString.static("runOnlyPendingTimers"), 0, Bun__Timer__runOnlyPendingTimers, false));
|
||||
vi.put(globalObject, ZigString.static("runAllTicks"), jsc.host_fn.NewFunction(globalObject, ZigString.static("runAllTicks"), 0, JSMock__jsRunAllTicks, false));
|
||||
vi.put(globalObject, ZigString.static("advanceTimersToNextTimer"), jsc.host_fn.NewFunction(globalObject, ZigString.static("advanceTimersToNextTimer"), 0, Bun__Timer__advanceTimersToNextTimer, false));
|
||||
module.put(globalObject, ZigString.static("vi"), vi);
|
||||
}
|
||||
|
||||
@@ -262,11 +270,18 @@ pub const Jest = struct {
|
||||
extern fn JSMock__jsModuleMock(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsNow(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsSetSystemTime(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsGetMockedSystemTime(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsGetRealSystemTime(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsIsFakeTimers(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsRunAllTicks(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsRestoreAllMocks(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsClearAllMocks(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsSpyOn(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsUseFakeTimers(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn JSMock__jsUseRealTimers(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn Bun__Timer__runAllTimers(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn Bun__Timer__runOnlyPendingTimers(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
extern fn Bun__Timer__advanceTimersToNextTimer(*JSGlobalObject, *CallFrame) callconv(jsc.conv) JSValue;
|
||||
|
||||
pub fn call(
|
||||
globalObject: *JSGlobalObject,
|
||||
|
||||
@@ -18,6 +18,10 @@ pub export fn Bun__drainMicrotasks() void {
|
||||
}
|
||||
|
||||
export fn Bun__readOriginTimer(vm: *jsc.VirtualMachine) u64 {
|
||||
// If fake timers are enabled, return the frozen value converted to nanoseconds
|
||||
if (vm.overriden_performance_now_ms >= 0) {
|
||||
return @intFromFloat(vm.overriden_performance_now_ms * 1_000_000.0);
|
||||
}
|
||||
return vm.origin_timer.read();
|
||||
}
|
||||
|
||||
@@ -26,6 +30,20 @@ export fn Bun__readOriginTimerStart(vm: *jsc.VirtualMachine) f64 {
|
||||
return @as(f64, @floatCast((@as(f64, @floatFromInt(vm.origin_timestamp)) + jsc.VirtualMachine.origin_relative_epoch) / 1_000_000.0));
|
||||
}
|
||||
|
||||
export fn Bun__Timer__getPerformanceNow(vm: *jsc.VirtualMachine) f64 {
|
||||
return vm.overriden_performance_now_ms;
|
||||
}
|
||||
|
||||
export fn Bun__Timer__setPerformanceNow(vm: *jsc.VirtualMachine, value: f64) void {
|
||||
vm.overriden_performance_now_ms = value;
|
||||
}
|
||||
|
||||
export fn Bun__Timer__advancePerformanceNow(vm: *jsc.VirtualMachine, delta: f64) void {
|
||||
if (vm.overriden_performance_now_ms >= 0) {
|
||||
vm.overriden_performance_now_ms += delta;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn Bun__GlobalObject__connectedIPC(global: *JSGlobalObject) bool {
|
||||
if (global.bunVM().ipc) |ipc| {
|
||||
if (ipc == .initialized) {
|
||||
|
||||
152
test/js/bun/test/fake-timers/date-now-advances.test.ts
Normal file
152
test/js/bun/test/fake-timers/date-now-advances.test.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { describe, expect, test, vi } from "bun:test";
|
||||
|
||||
describe("fake timers advance Date.now()", () => {
|
||||
test("vi.runAllTimers advances Date.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
const times: number[] = [];
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 3000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// Each timer should see Date.now() advanced to its scheduled time
|
||||
expect(times[0]).toBe(initialTime + 1000);
|
||||
expect(times[1]).toBe(initialTime + 2000);
|
||||
expect(times[2]).toBe(initialTime + 3000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer advances Date.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
const times: number[] = [];
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 5000);
|
||||
|
||||
// Advance to first timer
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(times[0]).toBe(initialTime + 1000);
|
||||
|
||||
// Advance to second timer
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(times[1]).toBe(initialTime + 5000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.runOnlyPendingTimers advances Date.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
const times: number[] = [];
|
||||
let nestedCalled = false;
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
// Schedule a nested timer - should not run in runOnlyPendingTimers
|
||||
setTimeout(() => {
|
||||
nestedCalled = true;
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(Date.now());
|
||||
}, 2000);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
// Both pending timers should have run
|
||||
expect(times[0]).toBe(initialTime + 1000);
|
||||
expect(times[1]).toBe(initialTime + 2000);
|
||||
|
||||
// Nested timer should not have run yet
|
||||
expect(nestedCalled).toBe(false);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("Date.now() stays consistent within same timer", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
let time1: number;
|
||||
let time2: number;
|
||||
|
||||
setTimeout(() => {
|
||||
time1 = Date.now();
|
||||
// Multiple calls within same callback should return same time
|
||||
time2 = Date.now();
|
||||
}, 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(time1).toBe(initialTime + 1000);
|
||||
expect(time2).toBe(initialTime + 1000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("setInterval advances Date.now() each iteration", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
const times: number[] = [];
|
||||
let count = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
times.push(Date.now());
|
||||
count++;
|
||||
if (count >= 3) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(times[0]).toBe(initialTime + 1000);
|
||||
expect(times[1]).toBe(initialTime + 2000);
|
||||
expect(times[2]).toBe(initialTime + 3000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("Date.now() reflects current virtual time", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = Date.now();
|
||||
|
||||
// First check that Date.now() doesn't advance without running timers
|
||||
setTimeout(() => {}, 1000);
|
||||
expect(Date.now()).toBe(initialTime);
|
||||
|
||||
// Now advance timers
|
||||
vi.advanceTimersToNextTimer();
|
||||
|
||||
// Date.now() should now reflect the advanced time
|
||||
expect(Date.now()).toBe(initialTime + 1000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
132
test/js/bun/test/fake-timers/order.test.ts
Normal file
132
test/js/bun/test/fake-timers/order.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { afterAll, beforeAll, expect, test, vi } from "vitest";
|
||||
|
||||
beforeAll(() => vi.useFakeTimers());
|
||||
afterAll(() => vi.useRealTimers());
|
||||
|
||||
class TimeHelper {
|
||||
base: number = 0;
|
||||
order: string[] = [];
|
||||
orderNum: number[] = [];
|
||||
time(d: number, l: string, cb: () => void) {
|
||||
const start = this.base;
|
||||
setTimeout(() => {
|
||||
this.addOrder(start + d, l);
|
||||
if (this.base != 0) throw new Error("base is not 0");
|
||||
this.base = start + d;
|
||||
cb();
|
||||
this.base = 0;
|
||||
}, d);
|
||||
}
|
||||
addOrder(d: number, l: string) {
|
||||
this.orderNum.push(d);
|
||||
this.order.push(`${d}${l ? ` (${l})` : ""}`);
|
||||
}
|
||||
expectOrder() {
|
||||
expect(this.orderNum).toEqual(this.orderNum.toSorted((a, b) => a - b));
|
||||
const order = this.order;
|
||||
this.order = [];
|
||||
return expect(order);
|
||||
}
|
||||
}
|
||||
|
||||
test.each(["runAllTimers", "advanceTimersToNextTimer"])("%s runs in order of time", mode => {
|
||||
const tester = new TimeHelper();
|
||||
const time = tester.time.bind(tester);
|
||||
|
||||
time(1000, "", () => {
|
||||
time(500, "", () => {});
|
||||
time(1500, "", () => {});
|
||||
});
|
||||
time(500, "", () => {
|
||||
time(600, "", () => {});
|
||||
time(0, "500 + 0", () => {});
|
||||
});
|
||||
time(2000, "", () => {});
|
||||
time(0, "zero 1", () => {
|
||||
time(0, "zero 1.1", () => {});
|
||||
time(0, "zero 1.2", () => {});
|
||||
});
|
||||
time(0, "zero 2", () => {
|
||||
time(0, "zero 2.1", () => {});
|
||||
time(0, "zero 2.2", () => {});
|
||||
});
|
||||
let intervalCount = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (intervalCount > 3) clearInterval(interval);
|
||||
intervalCount += 1;
|
||||
tester.addOrder(intervalCount * 499, "interval");
|
||||
setTimeout(() => {
|
||||
tester.addOrder(intervalCount * 499 + 25, "interval + 25");
|
||||
}, 25);
|
||||
}, 499);
|
||||
|
||||
if (mode === "runAllTimers") {
|
||||
vi.runAllTimers();
|
||||
} else if (mode === "advanceTimersToNextTimer") {
|
||||
let orderLen = 0;
|
||||
while (true) {
|
||||
vi.advanceTimersToNextTimer();
|
||||
if (tester.order.length > orderLen) {
|
||||
expect(tester.order.length).toBe(orderLen + 1);
|
||||
orderLen = tester.order.length;
|
||||
} else if (tester.order.length === orderLen) {
|
||||
break;
|
||||
} else {
|
||||
expect.fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tester.expectOrder().toMatchInlineSnapshot(`
|
||||
[
|
||||
"0 (zero 1)",
|
||||
"0 (zero 2)",
|
||||
"0 (zero 1.1)",
|
||||
"0 (zero 1.2)",
|
||||
"0 (zero 2.1)",
|
||||
"0 (zero 2.2)",
|
||||
"499 (interval)",
|
||||
"500",
|
||||
"500 (500 + 0)",
|
||||
"524 (interval + 25)",
|
||||
"998 (interval)",
|
||||
"1000",
|
||||
"1023 (interval + 25)",
|
||||
"1100",
|
||||
"1497 (interval)",
|
||||
"1500",
|
||||
"1522 (interval + 25)",
|
||||
"1996 (interval)",
|
||||
"2000",
|
||||
"2021 (interval + 25)",
|
||||
"2495 (interval)",
|
||||
"2500",
|
||||
"2520 (interval + 25)",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("runAllTimers supports interval", () => {
|
||||
let ticks = 0;
|
||||
const interval = setInterval(() => {
|
||||
ticks += 1;
|
||||
if (ticks >= 10) clearInterval(interval);
|
||||
}, 25);
|
||||
|
||||
vi.runAllTimers();
|
||||
expect(ticks).toBe(10);
|
||||
});
|
||||
|
||||
test("fake timers clear after useRealTimers", () => {
|
||||
let ticks = 0;
|
||||
setTimeout(() => {
|
||||
ticks += 1;
|
||||
}, 10);
|
||||
expect(ticks).toBe(0);
|
||||
vi.useRealTimers();
|
||||
expect(ticks).toBe(0);
|
||||
vi.useFakeTimers();
|
||||
vi.runAllTimers();
|
||||
expect(ticks).toBe(0);
|
||||
// TODO: check for memory leak of the callbacks
|
||||
});
|
||||
204
test/js/bun/test/fake-timers/performance-now-advances.test.ts
Normal file
204
test/js/bun/test/fake-timers/performance-now-advances.test.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { describe, expect, test, vi } from "bun:test";
|
||||
|
||||
describe("fake timers advance performance.now()", () => {
|
||||
test("vi.runAllTimers advances performance.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
const times: number[] = [];
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 3000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// Each timer should see performance.now() advanced to its scheduled time
|
||||
|
||||
expect(times.map(t => t - initialTime)).toEqual([1000, 2000, 3000, 4000]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer advances performance.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
const times: number[] = [];
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 5000);
|
||||
|
||||
// Advance to first timer
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(times[0]).toBeCloseTo(initialTime + 1000, 5);
|
||||
|
||||
// Advance to second timer
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(times[1]).toBeCloseTo(initialTime + 5000, 5);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.runOnlyPendingTimers advances performance.now()", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
const times: number[] = [];
|
||||
let nestedCalled = false;
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
// Schedule a nested timer - should not run in runOnlyPendingTimers
|
||||
setTimeout(() => {
|
||||
nestedCalled = true;
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
times.push(performance.now());
|
||||
}, 2000);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
// Both pending timers should have run
|
||||
expect(times[0]).toBeCloseTo(initialTime + 1000, 5);
|
||||
expect(times[1]).toBeCloseTo(initialTime + 2000, 5);
|
||||
|
||||
// Nested timer should not have run yet
|
||||
expect(nestedCalled).toBe(false);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("performance.now() stays consistent within same timer", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
let time1: number;
|
||||
let time2: number;
|
||||
|
||||
setTimeout(() => {
|
||||
time1 = performance.now();
|
||||
// Multiple calls within same callback should return same time
|
||||
time2 = performance.now();
|
||||
}, 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(time1).toBeCloseTo(initialTime + 1000, 5);
|
||||
expect(time2).toBeCloseTo(initialTime + 1000, 5);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("setInterval advances performance.now() each iteration", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
const times: number[] = [];
|
||||
let count = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
times.push(performance.now());
|
||||
count++;
|
||||
if (count >= 3) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// Use toBeCloseTo for floating point comparisons to avoid precision issues
|
||||
expect(times[0]).toBeCloseTo(initialTime + 1000, 5);
|
||||
expect(times[1]).toBeCloseTo(initialTime + 2000, 5);
|
||||
expect(times[2]).toBeCloseTo(initialTime + 3000, 5);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("performance.now() reflects current virtual time", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialTime = performance.now();
|
||||
|
||||
// First check that performance.now() doesn't advance without running timers
|
||||
setTimeout(() => {}, 1000);
|
||||
expect(performance.now()).toBeCloseTo(initialTime, 5);
|
||||
|
||||
// Now advance timers
|
||||
vi.advanceTimersToNextTimer();
|
||||
|
||||
// performance.now() should now reflect the advanced time
|
||||
expect(performance.now()).toBeCloseTo(initialTime + 1000, 5);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("both Date.now() and performance.now() advance together", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const initialDate = Date.now();
|
||||
const initialPerf = performance.now();
|
||||
|
||||
const dateTime: number[] = [];
|
||||
const perfTime: number[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
dateTime.push(Date.now());
|
||||
perfTime.push(performance.now());
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
dateTime.push(Date.now());
|
||||
perfTime.push(performance.now());
|
||||
}, 2500);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// Both should advance by the same amount
|
||||
expect(dateTime[0]).toBe(initialDate + 1000);
|
||||
expect(perfTime[0]).toBeCloseTo(initialPerf + 1000, 5);
|
||||
expect(dateTime[1]).toBe(initialDate + 2500);
|
||||
expect(perfTime[1]).toBeCloseTo(initialPerf + 2500, 5);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("performance.now() returns to real time after useRealTimers", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Advance time with fake timers
|
||||
setTimeout(() => {}, 5000);
|
||||
vi.runAllTimers();
|
||||
|
||||
const fakeTime = performance.now();
|
||||
|
||||
vi.useRealTimers();
|
||||
|
||||
// After switching back to real timers, performance.now() should return to real time
|
||||
const realTime = performance.now();
|
||||
|
||||
// Real time should be much less than fake time (we advanced 5 seconds)
|
||||
// This checks that we're not still using the fake offset
|
||||
expect(realTime).toBeLessThan(fakeTime);
|
||||
});
|
||||
});
|
||||
240
test/js/bun/test/fake-timers/runOnlyPendingTimers.test.ts
Normal file
240
test/js/bun/test/fake-timers/runOnlyPendingTimers.test.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
beforeAll(() => vi.useFakeTimers());
|
||||
afterAll(() => vi.useRealTimers());
|
||||
|
||||
describe("vi.runOnlyPendingTimers", () => {
|
||||
test("runs only timers that were pending when called", () => {
|
||||
let i = 0;
|
||||
setInterval(() => {
|
||||
i++;
|
||||
}, 50);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
// Should have run exactly once, not the timer that was scheduled during execution
|
||||
expect(i).toBe(1);
|
||||
});
|
||||
|
||||
test("does not run timers created during execution", () => {
|
||||
const order: number[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push(1);
|
||||
// This timer should NOT run in the same runOnlyPendingTimers call
|
||||
setTimeout(() => {
|
||||
order.push(2);
|
||||
}, 100);
|
||||
}, 100);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(order).toEqual([1]);
|
||||
|
||||
// Now run again to execute the timer that was scheduled during the first execution
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
test("runs multiple pending timers", () => {
|
||||
const order: number[] = [];
|
||||
|
||||
setTimeout(() => order.push(1), 100);
|
||||
setTimeout(() => order.push(2), 200);
|
||||
setTimeout(() => order.push(3), 300);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("handles nested setTimeout calls correctly", () => {
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push("outer1");
|
||||
setTimeout(() => {
|
||||
order.push("inner1");
|
||||
}, 50);
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
order.push("outer2");
|
||||
setTimeout(() => {
|
||||
order.push("inner2");
|
||||
}, 50);
|
||||
}, 200);
|
||||
|
||||
// First call: runs outer1 and outer2, but not inner1 and inner2
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["outer1", "outer2"]);
|
||||
|
||||
// Second call: runs inner1 and inner2
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["outer1", "outer2", "inner1", "inner2"]);
|
||||
|
||||
// Third call: no more timers
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["outer1", "outer2", "inner1", "inner2"]);
|
||||
});
|
||||
|
||||
test("handles setInterval correctly", () => {
|
||||
let count = 0;
|
||||
const interval = setInterval(() => {
|
||||
count++;
|
||||
if (count >= 3) clearInterval(interval);
|
||||
}, 50);
|
||||
|
||||
// First call: runs once
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(count).toBe(1);
|
||||
|
||||
// Second call: runs the rescheduled interval
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(count).toBe(2);
|
||||
|
||||
// Third call: runs again and clears
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(count).toBe(3);
|
||||
|
||||
// Fourth call: no more timers
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
|
||||
test("example from vitest documentation", () => {
|
||||
let i = 0;
|
||||
setInterval(() => {
|
||||
console.log(++i);
|
||||
}, 50);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
// log: 1
|
||||
expect(i).toBe(1);
|
||||
});
|
||||
|
||||
test("handles multiple intervals", () => {
|
||||
const order: string[] = [];
|
||||
|
||||
const interval1 = setInterval(() => {
|
||||
order.push("interval1");
|
||||
}, 50);
|
||||
|
||||
const interval2 = setInterval(() => {
|
||||
order.push("interval2");
|
||||
}, 100);
|
||||
|
||||
// First call: both intervals fire once
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["interval1", "interval2"]);
|
||||
|
||||
// Second call: both intervals fire again (they were rescheduled)
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["interval1", "interval2", "interval1", "interval2"]);
|
||||
|
||||
clearInterval(interval1);
|
||||
clearInterval(interval2);
|
||||
});
|
||||
|
||||
test("handles mixed setTimeout and setInterval", () => {
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => order.push("timeout"), 100);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
order.push("interval");
|
||||
}, 50);
|
||||
|
||||
// First call: both fire
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["interval", "timeout"]);
|
||||
|
||||
// Second call: only interval fires (timeout doesn't repeat)
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["interval", "timeout", "interval"]);
|
||||
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
test("handles timers with same delay", () => {
|
||||
const order: number[] = [];
|
||||
|
||||
setTimeout(() => order.push(1), 100);
|
||||
setTimeout(() => order.push(2), 100);
|
||||
setTimeout(() => order.push(3), 100);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("does not run timers with zero delay scheduled during execution", () => {
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push("outer");
|
||||
setTimeout(() => {
|
||||
order.push("inner");
|
||||
}, 0);
|
||||
}, 0);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["outer"]);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(order).toEqual(["outer", "inner"]);
|
||||
});
|
||||
|
||||
test("works with order.test.ts TimeHelper pattern", () => {
|
||||
class TimeHelper {
|
||||
base: number = 0;
|
||||
order: string[] = [];
|
||||
orderNum: number[] = [];
|
||||
time(d: number, l: string, cb: () => void) {
|
||||
const start = this.base;
|
||||
setTimeout(() => {
|
||||
this.addOrder(start + d, l);
|
||||
if (this.base != 0) throw new Error("base is not 0");
|
||||
this.base = start + d;
|
||||
cb();
|
||||
this.base = 0;
|
||||
}, d);
|
||||
}
|
||||
addOrder(d: number, l: string) {
|
||||
this.orderNum.push(d);
|
||||
this.order.push(`${d}${l ? ` (${l})` : ""}`);
|
||||
}
|
||||
expectOrder() {
|
||||
expect(this.orderNum).toEqual(this.orderNum.toSorted((a, b) => a - b));
|
||||
const order = this.order;
|
||||
this.order = [];
|
||||
return expect(order);
|
||||
}
|
||||
}
|
||||
|
||||
const tester = new TimeHelper();
|
||||
const time = tester.time.bind(tester);
|
||||
|
||||
time(100, "first", () => {
|
||||
time(50, "nested1", () => {});
|
||||
});
|
||||
time(200, "second", () => {
|
||||
time(100, "nested2", () => {});
|
||||
});
|
||||
|
||||
// First call: runs "first" and "second"
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(tester.order).toEqual(["100 (first)", "200 (second)"]);
|
||||
tester.order = []; // Clear for next assertion
|
||||
|
||||
// Second call: runs "nested1" and "nested2"
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(tester.order).toEqual(["150 (nested1)", "300 (nested2)"]);
|
||||
tester.order = []; // Clear for next assertion
|
||||
|
||||
// Third call: no more timers
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(tester.order).toEqual([]);
|
||||
});
|
||||
});
|
||||
205
test/js/bun/test/vi-advance-timers-to-next-timer.test.ts
Normal file
205
test/js/bun/test/vi-advance-timers-to-next-timer.test.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { expect, test, vi } from "bun:test";
|
||||
|
||||
test("vi.advanceTimersToNextTimer() advances to next timer", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let i = 0;
|
||||
setInterval(() => i++, 50);
|
||||
|
||||
expect(i).toBe(0);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(i).toBe(1);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(i).toBe(2);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(i).toBe(3);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() is chainable", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let i = 0;
|
||||
setInterval(() => i++, 50);
|
||||
|
||||
expect(i).toBe(0);
|
||||
|
||||
vi.advanceTimersToNextTimer().advanceTimersToNextTimer().advanceTimersToNextTimer();
|
||||
|
||||
expect(i).toBe(3);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() handles multiple timers with different delays", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: number[] = [];
|
||||
|
||||
setTimeout(() => order.push(1), 100);
|
||||
setTimeout(() => order.push(2), 50);
|
||||
setTimeout(() => order.push(3), 150);
|
||||
|
||||
expect(order).toEqual([]);
|
||||
|
||||
// Should fire in order of scheduled time, not order they were created
|
||||
vi.advanceTimersToNextTimer(); // fires timer at 50ms
|
||||
expect(order).toEqual([2]);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // fires timer at 100ms
|
||||
expect(order).toEqual([2, 1]);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // fires timer at 150ms
|
||||
expect(order).toEqual([2, 1, 3]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() handles nested timers", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: number[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push(1);
|
||||
setTimeout(() => order.push(3), 50);
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => order.push(2), 150);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // fires timer at 100ms, schedules new timer at current_time + 50ms
|
||||
expect(order).toEqual([1]);
|
||||
|
||||
// When two timers are at the same time (150ms), they fire in the order they were scheduled (FIFO)
|
||||
// Timer 2 was scheduled first (at time 0), so it fires before timer 3 (scheduled during callback at time 100)
|
||||
vi.advanceTimersToNextTimer(); // fires timer at 150ms (timer 2, scheduled first)
|
||||
expect(order).toEqual([1, 2]);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // fires nested timer at 150ms (timer 3, scheduled during callback)
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() does nothing when no timers are pending", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let called = false;
|
||||
setTimeout(() => {
|
||||
called = true;
|
||||
}, 100);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(called).toBe(true);
|
||||
|
||||
// No more timers, this should be safe to call
|
||||
vi.advanceTimersToNextTimer();
|
||||
vi.advanceTimersToNextTimer();
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() works with setInterval", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let count = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
count++;
|
||||
if (count >= 3) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
expect(count).toBe(0);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(count).toBe(1);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(count).toBe(2);
|
||||
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(count).toBe(3);
|
||||
|
||||
// Interval was cleared, no more timers
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(count).toBe(3);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() handles mix of setTimeout and setInterval", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const events: string[] = [];
|
||||
|
||||
setTimeout(() => events.push("timeout-1"), 50);
|
||||
const intervalId = setInterval(() => {
|
||||
events.push("interval");
|
||||
if (events.filter(e => e === "interval").length >= 2) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 100);
|
||||
setTimeout(() => events.push("timeout-2"), 200);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // 50ms: timeout-1
|
||||
expect(events).toEqual(["timeout-1"]);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // 100ms: interval (1st)
|
||||
expect(events).toEqual(["timeout-1", "interval"]);
|
||||
|
||||
vi.advanceTimersToNextTimer(); // 200ms: interval (2nd) and timeout-2 compete
|
||||
// The interval at 200ms should fire first (it was scheduled first), then gets cleared
|
||||
expect(events.length).toBe(3);
|
||||
expect(events).toContain("interval");
|
||||
|
||||
vi.advanceTimersToNextTimer(); // 200ms: timeout-2
|
||||
expect(events).toContain("timeout-2");
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() executes timer callbacks with automatic microtask processing", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push("timer");
|
||||
process.nextTick(() => order.push("tick"));
|
||||
}, 100);
|
||||
|
||||
// When a timer callback runs, microtasks (including nextTick) are processed automatically
|
||||
// This is standard JavaScript execution behavior
|
||||
vi.advanceTimersToNextTimer();
|
||||
expect(order).toEqual(["timer", "tick"]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.advanceTimersToNextTimer() returns vi object for chaining with other vi methods", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let timerCalled = false;
|
||||
let tickCalled = false;
|
||||
|
||||
setTimeout(() => {
|
||||
timerCalled = true;
|
||||
process.nextTick(() => {
|
||||
tickCalled = true;
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// Microtasks run automatically after timer callback, so runAllTicks() is not needed here
|
||||
// but chaining still works
|
||||
vi.advanceTimersToNextTimer();
|
||||
|
||||
expect(timerCalled).toBe(true);
|
||||
expect(tickCalled).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
145
test/js/bun/test/vi-is-fake-timers.test.ts
Normal file
145
test/js/bun/test/vi-is-fake-timers.test.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { test, expect, vi } from "bun:test";
|
||||
|
||||
test("vi.isFakeTimers() returns false by default", () => {
|
||||
// Ensure we start with real timers
|
||||
vi.useRealTimers();
|
||||
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() returns true when fake timers are enabled", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() returns false after useRealTimers", () => {
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() state persists across multiple calls", () => {
|
||||
// Start with real timers
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
expect(vi.isFakeTimers()).toBe(false); // Should be consistent
|
||||
|
||||
// Enable fake timers
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
expect(vi.isFakeTimers()).toBe(true); // Should be consistent
|
||||
|
||||
// Set a system time (should still be fake)
|
||||
vi.setSystemTime(1234567890000);
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
// Back to real timers
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
expect(vi.isFakeTimers()).toBe(false); // Should be consistent
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() works with getMockedSystemTime", () => {
|
||||
// When fake timers are off, both should indicate no mocking
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
expect(vi.getMockedSystemTime()).toBeNull();
|
||||
|
||||
// When fake timers are on, both should indicate mocking
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
expect(vi.getMockedSystemTime()).not.toBeNull();
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() reflects state after setSystemTime", () => {
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
|
||||
// setSystemTime should implicitly enable fake timers
|
||||
vi.setSystemTime(0);
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("multiple transitions between real and fake timers", () => {
|
||||
const states = [];
|
||||
|
||||
// Initial state
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
// Enable fake timers
|
||||
vi.useFakeTimers();
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
// Back to real
|
||||
vi.useRealTimers();
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
// Fake again
|
||||
vi.useFakeTimers();
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
// Set system time (should keep fake)
|
||||
vi.setSystemTime(1000000);
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
// Real again
|
||||
vi.useRealTimers();
|
||||
states.push(vi.isFakeTimers());
|
||||
|
||||
expect(states).toEqual([false, true, false, true, true, false]);
|
||||
});
|
||||
|
||||
test("vi.isFakeTimers() is type-safe", () => {
|
||||
const result = vi.isFakeTimers();
|
||||
|
||||
// TypeScript would catch this, but we can verify at runtime too
|
||||
expect(typeof result).toBe("boolean");
|
||||
expect(result === true || result === false).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("calling useFakeTimers multiple times keeps fake timers enabled", () => {
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useFakeTimers();
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
});
|
||||
|
||||
test("isFakeTimers works correctly with Date behavior", () => {
|
||||
vi.useRealTimers();
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
|
||||
// Real timers - dates should differ
|
||||
const date1 = new Date();
|
||||
const date2 = new Date();
|
||||
// They might be equal due to timing, but isFakeTimers should be false
|
||||
expect(vi.isFakeTimers()).toBe(false);
|
||||
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1234567890000);
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
// Fake timers - dates should be equal
|
||||
const fakeDate1 = new Date();
|
||||
const fakeDate2 = new Date();
|
||||
expect(fakeDate1.getTime()).toBe(fakeDate2.getTime());
|
||||
expect(vi.isFakeTimers()).toBe(true);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
127
test/js/bun/test/vi-run-all-ticks.test.ts
Normal file
127
test/js/bun/test/vi-run-all-ticks.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { describe, expect, test, vi } from "bun:test";
|
||||
|
||||
describe("vi.runAllTicks", () => {
|
||||
test("runs all pending nextTick callbacks", () => {
|
||||
const callback1 = vi.fn();
|
||||
const callback2 = vi.fn();
|
||||
const callback3 = vi.fn();
|
||||
|
||||
process.nextTick(callback1);
|
||||
process.nextTick(callback2);
|
||||
process.nextTick(callback3);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
expect(callback3).not.toHaveBeenCalled();
|
||||
|
||||
vi.runAllTicks();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("runs nested nextTick callbacks", () => {
|
||||
const order: number[] = [];
|
||||
const callback1 = vi.fn(() => {
|
||||
order.push(1);
|
||||
process.nextTick(callback3);
|
||||
});
|
||||
const callback2 = vi.fn(() => {
|
||||
order.push(2);
|
||||
});
|
||||
const callback3 = vi.fn(() => {
|
||||
order.push(3);
|
||||
});
|
||||
|
||||
process.nextTick(callback1);
|
||||
process.nextTick(callback2);
|
||||
|
||||
expect(order).toEqual([]);
|
||||
|
||||
vi.runAllTicks();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("runs recursively scheduled nextTick callbacks", () => {
|
||||
let count = 0;
|
||||
const maxCount = 5;
|
||||
|
||||
const scheduleNext = vi.fn(() => {
|
||||
count++;
|
||||
if (count < maxCount) {
|
||||
process.nextTick(scheduleNext);
|
||||
}
|
||||
});
|
||||
|
||||
process.nextTick(scheduleNext);
|
||||
|
||||
vi.runAllTicks();
|
||||
|
||||
expect(count).toBe(maxCount);
|
||||
expect(scheduleNext).toHaveBeenCalledTimes(maxCount);
|
||||
});
|
||||
|
||||
test("does nothing when there are no pending nextTick callbacks", () => {
|
||||
// Should not throw or hang
|
||||
vi.runAllTicks();
|
||||
vi.runAllTicks();
|
||||
});
|
||||
|
||||
test("handles errors in nextTick callbacks", () => {
|
||||
const callback1 = vi.fn(() => {
|
||||
throw new Error("nextTick error");
|
||||
});
|
||||
const callback2 = vi.fn();
|
||||
|
||||
process.nextTick(callback1);
|
||||
process.nextTick(callback2);
|
||||
|
||||
// runAllTicks should throw when a callback throws
|
||||
expect(() => vi.runAllTicks()).toThrow("nextTick error");
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("works with async callbacks", async () => {
|
||||
let resolved = false;
|
||||
const promise = new Promise<void>(resolve => {
|
||||
process.nextTick(() => {
|
||||
resolved = true;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
vi.runAllTicks();
|
||||
|
||||
expect(resolved).toBe(true);
|
||||
await promise; // Should resolve immediately since nextTick already fired
|
||||
});
|
||||
|
||||
test("runs nextTick callbacks scheduled during microtasks", () => {
|
||||
const order: string[] = [];
|
||||
|
||||
process.nextTick(() => order.push("nextTick1"));
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
order.push("microtask1");
|
||||
process.nextTick(() => order.push("nextTick2"));
|
||||
});
|
||||
|
||||
vi.runAllTicks();
|
||||
|
||||
// nextTick1 runs first, then microtask1, then nextTick2
|
||||
expect(order).toEqual(["nextTick1", "microtask1", "nextTick2"]);
|
||||
});
|
||||
|
||||
test("returns the vi object for chaining", () => {
|
||||
const result = vi.runAllTicks();
|
||||
expect(result).toBe(vi);
|
||||
});
|
||||
});
|
||||
271
test/js/bun/test/vi-run-all-timers.test.ts
Normal file
271
test/js/bun/test/vi-run-all-timers.test.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import { test, expect, vi, describe } from "bun:test";
|
||||
|
||||
describe("vi.runAllTimers", () => {
|
||||
test("runs all pending timers immediately", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const callback1 = vi.fn();
|
||||
const callback2 = vi.fn();
|
||||
const callback3 = vi.fn();
|
||||
|
||||
setTimeout(callback1, 1000);
|
||||
setTimeout(callback2, 5000);
|
||||
setTimeout(callback3, 10000);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
expect(callback3).not.toHaveBeenCalled();
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("runs nested timers", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: number[] = [];
|
||||
const callback1 = vi.fn(() => {
|
||||
order.push(1);
|
||||
setTimeout(callback3, 1000);
|
||||
});
|
||||
const callback2 = vi.fn(() => {
|
||||
order.push(2);
|
||||
});
|
||||
const callback3 = vi.fn(() => {
|
||||
order.push(3);
|
||||
});
|
||||
|
||||
setTimeout(callback1, 1000);
|
||||
setTimeout(callback2, 2000);
|
||||
|
||||
expect(order).toEqual([]);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
expect(callback3).toHaveBeenCalledTimes(1);
|
||||
expect(order).toEqual([1, 2, 3]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles setInterval correctly", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let count = 0;
|
||||
const callback = vi.fn(() => {
|
||||
count++;
|
||||
if (count >= 3) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
});
|
||||
|
||||
const intervalId = setInterval(callback, 1000);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
// runAllTimers should run intervals multiple times until cleared
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(3);
|
||||
expect(count).toBe(3);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles mix of setTimeout and setInterval", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const timeoutCallback = vi.fn();
|
||||
const intervalCallback = vi.fn();
|
||||
let intervalCount = 0;
|
||||
|
||||
setTimeout(timeoutCallback, 5000);
|
||||
const intervalId = setInterval(() => {
|
||||
intervalCallback();
|
||||
intervalCount++;
|
||||
if (intervalCount >= 2) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(timeoutCallback).toHaveBeenCalledTimes(1);
|
||||
expect(intervalCallback).toHaveBeenCalledTimes(2);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("does nothing when fake timers are disabled", async () => {
|
||||
// Don't use fake timers
|
||||
const callback = vi.fn();
|
||||
|
||||
const timeoutId = setTimeout(callback, 10);
|
||||
|
||||
// This should do nothing since fake timers are not enabled
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
// Wait for the real timer to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
test("runs timers in order they were scheduled", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => order.push("first"), 100);
|
||||
setTimeout(() => order.push("second"), 200);
|
||||
setTimeout(() => order.push("third"), 300);
|
||||
setTimeout(() => order.push("also-100ms"), 100);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// Timers with same delay should fire in order they were scheduled
|
||||
expect(order).toEqual(["first", "also-100ms", "second", "third"]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles timer that schedules another timer with same delay", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const order: string[] = [];
|
||||
|
||||
setTimeout(() => {
|
||||
order.push("first");
|
||||
setTimeout(() => order.push("nested"), 1000);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => order.push("second"), 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
// The nested timer should fire after the second timer
|
||||
expect(order).toEqual(["first", "second", "nested"]);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles zero-delay timers", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const callback1 = vi.fn();
|
||||
const callback2 = vi.fn();
|
||||
|
||||
setTimeout(callback1, 0);
|
||||
setTimeout(callback2, 0);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles timers that throw errors", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const callback1 = vi.fn(() => {
|
||||
throw new Error("Timer error");
|
||||
});
|
||||
const callback2 = vi.fn();
|
||||
|
||||
setTimeout(callback1, 1000);
|
||||
setTimeout(callback2, 2000);
|
||||
|
||||
// Even if one timer throws, others should still run
|
||||
expect(() => vi.runAllTimers()).toThrow("Timer error");
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
// The second callback may or may not run depending on error handling
|
||||
// This behavior might need to be verified
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("works with promises and async callbacks", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let resolved = false;
|
||||
const promise = new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolved = true;
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(resolved).toBe(true);
|
||||
await promise; // Should resolve immediately since timer already fired
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("handles recursive timer scheduling", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let count = 0;
|
||||
const maxCount = 5;
|
||||
|
||||
const scheduleNext = () => {
|
||||
count++;
|
||||
if (count < maxCount) {
|
||||
setTimeout(scheduleNext, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(scheduleNext, 1000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(count).toBe(maxCount);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("clears timers correctly with clearTimeout during execution", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const callback1 = vi.fn();
|
||||
const callback2 = vi.fn();
|
||||
let timerId2: ReturnType<typeof setTimeout>;
|
||||
|
||||
setTimeout(() => {
|
||||
callback1();
|
||||
clearTimeout(timerId2);
|
||||
}, 1000);
|
||||
|
||||
timerId2 = setTimeout(callback2, 2000);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(callback1).toHaveBeenCalledTimes(1);
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
// Note: setImmediate uses a different queue system (not the timer heap)
|
||||
// and would require additional implementation in runAllTimers to support
|
||||
});
|
||||
166
test/js/bun/test/vi-timer-get.test.ts
Normal file
166
test/js/bun/test/vi-timer-get.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { test, expect, vi } from "bun:test";
|
||||
|
||||
test("vi.getMockedSystemTime() returns null when timers are not mocked", () => {
|
||||
// Ensure we start with real timers
|
||||
vi.useRealTimers();
|
||||
|
||||
const mockedTime = vi.getMockedSystemTime();
|
||||
expect(mockedTime).toBeNull();
|
||||
});
|
||||
|
||||
test("vi.getMockedSystemTime() returns Date when fake timers are active", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const mockedTime = vi.getMockedSystemTime();
|
||||
expect(mockedTime).toBeInstanceOf(Date);
|
||||
expect(mockedTime).not.toBeNull();
|
||||
|
||||
// The mocked time should be frozen
|
||||
const time1 = vi.getMockedSystemTime();
|
||||
const time2 = vi.getMockedSystemTime();
|
||||
expect(time1.getTime()).toBe(time2.getTime());
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.getMockedSystemTime() reflects setSystemTime changes", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Set a specific time
|
||||
const targetTime = new Date(2024, 5, 15, 12, 0, 0);
|
||||
vi.setSystemTime(targetTime);
|
||||
|
||||
const mockedTime = vi.getMockedSystemTime();
|
||||
expect(mockedTime).toBeInstanceOf(Date);
|
||||
expect(mockedTime.getTime()).toBe(targetTime.getTime());
|
||||
expect(mockedTime.getFullYear()).toBe(2024);
|
||||
expect(mockedTime.getMonth()).toBe(5);
|
||||
expect(mockedTime.getDate()).toBe(15);
|
||||
|
||||
// Change the time again
|
||||
vi.setSystemTime(0);
|
||||
const epochTime = vi.getMockedSystemTime();
|
||||
expect(epochTime.getTime()).toBe(0);
|
||||
expect(epochTime.toISOString()).toBe("1970-01-01T00:00:00.000Z");
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.getRealSystemTime() returns real time regardless of mocking", () => {
|
||||
// Test without fake timers
|
||||
const realTime1 = vi.getRealSystemTime();
|
||||
expect(typeof realTime1).toBe("number");
|
||||
expect(realTime1).toBeGreaterThan(0);
|
||||
|
||||
// Should be close to Date.now()
|
||||
const now1 = Date.now();
|
||||
expect(Math.abs(realTime1 - now1)).toBeLessThan(100);
|
||||
|
||||
// Enable fake timers and set to past
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000); // September 9, 2001
|
||||
|
||||
// getRealSystemTime should still return current time, not mocked time
|
||||
const realTime2 = vi.getRealSystemTime();
|
||||
const now2 = Date.now(); // This will return mocked time (1000000000000)
|
||||
|
||||
expect(now2).toBe(1000000000000); // Date.now() is mocked
|
||||
expect(realTime2).toBeGreaterThan(1700000000000); // Real time should be recent (after 2023)
|
||||
expect(realTime2).not.toBe(1000000000000); // Should NOT be the mocked time
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.getRealSystemTime() always returns increasing values", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0); // Set to epoch
|
||||
|
||||
const times = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
times.push(vi.getRealSystemTime());
|
||||
}
|
||||
|
||||
// Real times should be monotonically increasing (or equal)
|
||||
for (let i = 1; i < times.length; i++) {
|
||||
expect(times[i]).toBeGreaterThanOrEqual(times[i - 1]);
|
||||
}
|
||||
|
||||
// All real times should be recent (not epoch)
|
||||
for (const time of times) {
|
||||
expect(time).toBeGreaterThan(1700000000000); // After 2023
|
||||
}
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.getMockedSystemTime() and new Date() return same time when mocked", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1234567890000);
|
||||
|
||||
const mockedTime = vi.getMockedSystemTime();
|
||||
const newDate = new Date();
|
||||
|
||||
expect(mockedTime.getTime()).toBe(newDate.getTime());
|
||||
expect(mockedTime.getTime()).toBe(1234567890000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.getRealSystemTime() differs from Date.now() when timers are mocked", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
|
||||
const realTime = vi.getRealSystemTime();
|
||||
const mockedNow = Date.now();
|
||||
|
||||
// They should be very different
|
||||
expect(Math.abs(realTime - mockedNow)).toBeGreaterThan(1000000000); // More than ~31 years difference
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("after useRealTimers, getMockedSystemTime returns null", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1234567890000);
|
||||
|
||||
// Should have a mocked time
|
||||
expect(vi.getMockedSystemTime()).not.toBeNull();
|
||||
expect(vi.getMockedSystemTime().getTime()).toBe(1234567890000);
|
||||
|
||||
// After restoring real timers
|
||||
vi.useRealTimers();
|
||||
|
||||
// Should return null
|
||||
expect(vi.getMockedSystemTime()).toBeNull();
|
||||
});
|
||||
|
||||
test("getRealSystemTime works correctly across multiple timer state changes", () => {
|
||||
const realTimes = [];
|
||||
|
||||
// Real timer mode
|
||||
vi.useRealTimers();
|
||||
realTimes.push(vi.getRealSystemTime());
|
||||
|
||||
// Fake timer mode
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
realTimes.push(vi.getRealSystemTime());
|
||||
|
||||
// Change mocked time
|
||||
vi.setSystemTime(9999999999999);
|
||||
realTimes.push(vi.getRealSystemTime());
|
||||
|
||||
// Back to real timer mode
|
||||
vi.useRealTimers();
|
||||
realTimes.push(vi.getRealSystemTime());
|
||||
|
||||
// All real times should be close to each other (within a second)
|
||||
const minTime = Math.min(...realTimes);
|
||||
const maxTime = Math.max(...realTimes);
|
||||
expect(maxTime - minTime).toBeLessThan(1000);
|
||||
|
||||
// All should be recent
|
||||
for (const time of realTimes) {
|
||||
expect(time).toBeGreaterThan(1700000000000);
|
||||
}
|
||||
});
|
||||
151
test/js/bun/test/vi-timer.test.ts
Normal file
151
test/js/bun/test/vi-timer.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { test, expect, vi } from "bun:test";
|
||||
|
||||
test("vi.useFakeTimers() freezes Date", () => {
|
||||
const beforeFakeTimers = new Date();
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Date should be frozen at the time useFakeTimers was called
|
||||
const duringFakeTimers1 = new Date();
|
||||
const duringFakeTimers2 = new Date();
|
||||
|
||||
// Both dates should be equal since time is frozen
|
||||
expect(duringFakeTimers1.getTime()).toBe(duringFakeTimers2.getTime());
|
||||
|
||||
// Should be close to when we enabled fake timers (within a few ms)
|
||||
expect(Math.abs(duringFakeTimers1.getTime() - beforeFakeTimers.getTime())).toBeLessThan(100);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime() sets a specific date", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Test with number (timestamp)
|
||||
const timestamp = 1000000000000; // September 9, 2001
|
||||
vi.setSystemTime(timestamp);
|
||||
|
||||
const date1 = new Date();
|
||||
expect(date1.getTime()).toBe(timestamp);
|
||||
|
||||
// Date.now() should also return the mocked time
|
||||
expect(Date.now()).toBe(timestamp);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime() accepts Date object", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const targetDate = new Date(2023, 0, 1, 12, 0, 0); // January 1, 2023, 12:00:00
|
||||
vi.setSystemTime(targetDate);
|
||||
|
||||
const date = new Date();
|
||||
expect(date.getTime()).toBe(targetDate.getTime());
|
||||
expect(date.getFullYear()).toBe(2023);
|
||||
expect(date.getMonth()).toBe(0);
|
||||
expect(date.getDate()).toBe(1);
|
||||
expect(date.getHours()).toBe(12);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime() accepts date string", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const dateString = "2024-06-15T10:30:00.000Z";
|
||||
vi.setSystemTime(dateString);
|
||||
|
||||
const date = new Date();
|
||||
expect(date.toISOString()).toBe(dateString);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.useRealTimers() restores real Date behavior", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
|
||||
const fakeDate = new Date();
|
||||
expect(fakeDate.getTime()).toBe(1000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
|
||||
// After restoring, dates should be current
|
||||
const realDate1 = new Date();
|
||||
const realDate2 = new Date();
|
||||
|
||||
// Real dates should be recent (within the last minute)
|
||||
const now = Date.now();
|
||||
expect(Math.abs(realDate1.getTime() - now)).toBeLessThan(1000);
|
||||
|
||||
// Two consecutive dates might have slightly different times
|
||||
expect(realDate2.getTime()).toBeGreaterThanOrEqual(realDate1.getTime());
|
||||
});
|
||||
|
||||
test("vi.setSystemTime() works without calling useFakeTimers first", () => {
|
||||
// This should implicitly enable fake timers
|
||||
const timestamp = 1500000000000;
|
||||
vi.setSystemTime(timestamp);
|
||||
|
||||
const date = new Date();
|
||||
expect(date.getTime()).toBe(timestamp);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("Date constructor with arguments still works with fake timers", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
|
||||
// Creating a date with specific arguments should still work
|
||||
const specificDate = new Date(2025, 5, 15, 14, 30, 0);
|
||||
expect(specificDate.getFullYear()).toBe(2025);
|
||||
expect(specificDate.getMonth()).toBe(5);
|
||||
expect(specificDate.getDate()).toBe(15);
|
||||
|
||||
// But Date.now() and new Date() should use the mocked time
|
||||
expect(Date.now()).toBe(1000000000000);
|
||||
expect(new Date().getTime()).toBe(1000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime(0) sets epoch time", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
|
||||
const date = new Date();
|
||||
expect(date.getTime()).toBe(0);
|
||||
expect(date.toISOString()).toBe("1970-01-01T00:00:00.000Z");
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("multiple calls to vi.setSystemTime() update the mocked time", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
vi.setSystemTime(1000000000000);
|
||||
expect(new Date().getTime()).toBe(1000000000000);
|
||||
|
||||
vi.setSystemTime(2000000000000);
|
||||
expect(new Date().getTime()).toBe(2000000000000);
|
||||
|
||||
vi.setSystemTime(3000000000000);
|
||||
expect(new Date().getTime()).toBe(3000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("calling vi.useFakeTimers() multiple times preserves the set time", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1234567890000);
|
||||
|
||||
// Call useFakeTimers again
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Time should still be the previously set time
|
||||
expect(new Date().getTime()).toBe(1234567890000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
68
test/regression/issue/vi-timer-edge-cases.test.ts
Normal file
68
test/regression/issue/vi-timer-edge-cases.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { test, expect, vi } from "bun:test";
|
||||
|
||||
test("vi.setSystemTime properly handles invalid date strings", () => {
|
||||
vi.useFakeTimers();
|
||||
const beforeInvalid = new Date().getTime();
|
||||
|
||||
// Invalid date string should not change the time
|
||||
vi.setSystemTime("invalid-date");
|
||||
const afterInvalid = new Date().getTime();
|
||||
|
||||
// Time should remain unchanged
|
||||
expect(afterInvalid).toBe(beforeInvalid);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime with negative numbers doesn't set time", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
|
||||
// Negative numbers should be ignored
|
||||
vi.setSystemTime(-1000);
|
||||
expect(new Date().getTime()).toBe(1000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi timer methods can be chained", () => {
|
||||
// Should not throw
|
||||
const result = vi
|
||||
.useFakeTimers()
|
||||
.setSystemTime(1234567890000)
|
||||
.useRealTimers();
|
||||
|
||||
// Result should be the vi object for chaining
|
||||
expect(result).toBe(vi);
|
||||
});
|
||||
|
||||
test("Date constructor respects fake timer when called without arguments", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
|
||||
// These should all give the same mocked time
|
||||
const date1 = new Date();
|
||||
const date2 = new Date(Date.now());
|
||||
const timestamp = Date.now();
|
||||
|
||||
expect(date1.getTime()).toBe(1000000000000);
|
||||
expect(date2.getTime()).toBe(1000000000000);
|
||||
expect(timestamp).toBe(1000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test("vi.setSystemTime accepts undefined/null to reset", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1000000000000);
|
||||
expect(new Date().getTime()).toBe(1000000000000);
|
||||
|
||||
// Reset with undefined
|
||||
vi.setSystemTime(undefined);
|
||||
const afterReset = new Date().getTime();
|
||||
|
||||
// Should no longer be the old mocked time
|
||||
expect(afterReset).not.toBe(1000000000000);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
Reference in New Issue
Block a user