Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions packages/vitest/src/runtime/external-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export class ExternalModulesExecutor {
this.resolvers = [this.vite.resolve]
}

public syncBuiltinESMExports(): void {
this.cjs.syncBuiltinESMExports()
}

async import(identifier: string): Promise<object> {
const module = await this.createModule(identifier)
await this.esm.evaluateModule(module)
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/runtime/moduleRunner/moduleRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export class VitestModuleRunner
return super.import(resolved ? resolved.url : rawId)
}

public syncBuiltinESMExports(): void {
this.vitestOptions.vm?.externalModulesExecutor.syncBuiltinESMExports()
}

public async fetchModule(url: string, importer?: string): Promise<EvaluatedModuleNode> {
const module = await (this as any).cachedModule(url, importer)
return module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface TestModuleRunner {
moduleExecutionInfo?: ModuleExecutionInfo
mocker?: TestModuleMocker
import: <T = any>(moduleId: string) => Promise<T>
syncBuiltinESMExports?: () => void
}
1 change: 1 addition & 0 deletions packages/vitest/src/runtime/runVmTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export async function run(
util,
timers,
timersPromises,
syncBuiltinESMExports: () => moduleRunner.syncBuiltinESMExports?.(),
}

await traces.$('vitest.runtime.coverage.start', () => startCoverageInsideWorker(config.coverage, moduleRunner, { isolate: false }))
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/runtime/setup-node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Environment } from '../types/environment'
import type { SerializedConfig } from './config'
import { createRequire } from 'node:module'
import { createRequire, syncBuiltinESMExports } from 'node:module'
import timers from 'node:timers'
import timersPromises from 'node:timers/promises'
import util from 'node:util'
Expand Down Expand Up @@ -54,6 +54,7 @@ export async function setupGlobalEnv(
util,
timers,
timersPromises,
syncBuiltinESMExports,
}

if (!config.disableConsoleIntercept) {
Expand Down
16 changes: 16 additions & 0 deletions packages/vitest/src/runtime/vm/commonjs-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,22 @@ export class CommonjsExecutor {
return module
}

public syncBuiltinESMExports(): void {
_Module.syncBuiltinESMExports()

for (const [identifier, module] of this.moduleCache) {
if (!isBuiltin(identifier) || module.status !== 'evaluated') {
continue
}
const exports = this.require(identifier)
for (const key of Object.keys(module.namespace)) {
if (key !== 'default') {
module.setExport(key, exports[key])
}
}
}
}

public getCjsSyntheticModule(path: string, identifier: string): VMSyntheticModule {
if (this.moduleCache.has(identifier)) {
return this.moduleCache.get(identifier)!
Expand Down
158 changes: 143 additions & 15 deletions patches/@sinonjs__fake-timers@15.4.0.patch
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
diff --git a/src/fake-timers-src.js b/src/fake-timers-src.js
index 241a7ad3eeb8b5eb691439fab31a87ef13c76345..2df1b2086670bfa06cdb148a9dbe61cd41680f2e 100644
index 241a7ad3eeb8b5eb691439fab31a87ef13c76345..ce5b46a643ab4d4b820dda2c84b0fba2a0d2f5a4 100644
--- a/src/fake-timers-src.js
+++ b/src/fake-timers-src.js
@@ -2,14 +2,14 @@
@@ -1,19 +1,6 @@
"use strict";

const globalObject = require("@sinonjs/commons").global;
let timersModule, timersPromisesModule;
-let timersModule, timersPromisesModule;
-if (typeof require === "function" && typeof module === "object") {
+if (typeof __vitest_required__ !== 'undefined') {
try {
- try {
- timersModule = require("timers");
+ timersModule = __vitest_required__.timers;
} catch {
// ignored
}
try {
- } catch {
- // ignored
- }
- try {
- timersPromisesModule = require("timers/promises");
+ timersPromisesModule = __vitest_required__.timersPromises;
} catch {
// ignored
}
@@ -537,7 +537,7 @@ function withGlobal(_global) {
- } catch {
- // ignored
- }
-}

/**
* @typedef {"nextAsync" | "manual" | "interval"} TickMode
@@ -440,8 +427,9 @@ if (typeof require === "function" && typeof module === "object") {
* @property {Uninstall} uninstall - restores native timers
* @property {string[]} methods - names of faked methods
* @property {boolean} [shouldClearNativeTimers] - inherited from config
- * @property {{methodName:string, original:unknown}[] | undefined} timersModuleMethods - saved Node timers module methods
- * @property {{methodName:string, original:unknown}[] | undefined} timersPromisesModuleMethods - saved Node timers/promises methods
+ * @property {{methodName:string, original:unknown, target:object}[] | undefined} timersModuleMethods - saved Node timers module methods
+ * @property {{methodName:string, original:unknown, target:object}[] | undefined} timersPromisesModuleMethods - saved Node timers/promises methods
+ * @property {(() => void) | undefined} syncBuiltinESMExports - synchronizes Node builtin ESM exports
* @property {Map<VoidVarArgsFunc, AbortSignal>} abortListenerMap - active abort listeners
* @property {SetTickMode} setTickMode - switches the auto-tick mode
* @property {Map<number, Timer>} [timers] - internal timer storage
@@ -537,7 +525,7 @@ function withGlobal(_global) {
isPresent.hrtime && typeof _global.process.hrtime.bigint === "function";
isPresent.nextTick =
_global.process && typeof _global.process.nextTick === "function";
Expand All @@ -29,3 +43,117 @@ index 241a7ad3eeb8b5eb691439fab31a87ef13c76345..2df1b2086670bfa06cdb148a9dbe61cd
isPresent.performance =
_global.performance && typeof _global.performance.now === "function";
const hasPerformancePrototype =
@@ -2618,28 +2606,25 @@ function withGlobal(_global) {
}
}
}
- if (clock.timersModuleMethods !== undefined) {
- for (
- let j = 0;
- j < clock.timersModuleMethods.length;
- j++
- ) {
- const entry = clock.timersModuleMethods[j];
- timersModule[entry.methodName] = entry.original;
- }
+ }
+
+ if (clock.timersModuleMethods !== undefined) {
+ for (const entry of clock.timersModuleMethods) {
+ entry.target[entry.methodName] = entry.original;
}
- if (clock.timersPromisesModuleMethods !== undefined) {
- for (
- let j = 0;
- j < clock.timersPromisesModuleMethods.length;
- j++
- ) {
- const entry = clock.timersPromisesModuleMethods[j];
- timersPromisesModule[entry.methodName] =
- entry.original;
- }
+ }
+ if (clock.timersPromisesModuleMethods !== undefined) {
+ for (const entry of clock.timersPromisesModuleMethods) {
+ entry.target[entry.methodName] = entry.original;
}
}
+ if (
+ clock.syncBuiltinESMExports &&
+ (clock.timersModuleMethods?.length ||
+ clock.timersPromisesModuleMethods?.length)
+ ) {
+ clock.syncBuiltinESMExports();
+ }

// Prevent multiple executions which will completely remove these props
clock.methods = [];
@@ -2726,6 +2711,13 @@ function withGlobal(_global) {
);
}

+ const vitestRequired = _global.__vitest_required__;
+ const timersModule = vitestRequired && vitestRequired.timers;
+ const timersPromisesModule =
+ vitestRequired && vitestRequired.timersPromises;
+ const syncBuiltinESMExports =
+ vitestRequired && vitestRequired.syncBuiltinESMExports;
+
if (config.target) {
throw new TypeError(
"config.target is no longer supported. Use `withGlobal(target)` instead.",
@@ -2815,6 +2807,7 @@ function withGlobal(_global) {
if (_global === globalObject && timersPromisesModule) {
clock.timersPromisesModuleMethods = [];
}
+ clock.syncBuiltinESMExports = syncBuiltinESMExports;
for (i = 0, l = clock.methods.length; i < l; i++) {
const nameOfMethodToReplace = clock.methods[i];

@@ -2849,6 +2842,7 @@ function withGlobal(_global) {
clock.timersModuleMethods.push({
methodName: nameOfMethodToReplace,
original: original,
+ target: timersModule,
});
timersModule[nameOfMethodToReplace] =
_global[nameOfMethodToReplace];
@@ -2858,6 +2852,7 @@ function withGlobal(_global) {
clock.timersPromisesModuleMethods.push({
methodName: "setTimeout",
original: timersPromisesModule.setTimeout,
+ target: timersPromisesModule,
});

timersPromisesModule.setTimeout = (
@@ -2911,6 +2906,7 @@ function withGlobal(_global) {
clock.timersPromisesModuleMethods.push({
methodName: "setImmediate",
original: timersPromisesModule.setImmediate,
+ target: timersPromisesModule,
});

timersPromisesModule.setImmediate = (value, options = {}) =>
@@ -2960,6 +2956,7 @@ function withGlobal(_global) {
clock.timersPromisesModuleMethods.push({
methodName: "setInterval",
original: timersPromisesModule.setInterval,
+ target: timersPromisesModule,
});

timersPromisesModule.setInterval = (
@@ -3091,6 +3088,14 @@ function withGlobal(_global) {
}
}

+ if (
+ clock.syncBuiltinESMExports &&
+ (clock.timersModuleMethods?.length ||
+ clock.timersPromisesModuleMethods?.length)
+ ) {
+ clock.syncBuiltinESMExports();
+ }
+
return clock;
}

8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions test/unit/test/timers-node.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,87 @@
// @vitest-environment node

import { createRequire } from 'node:module'
import timers, { setTimeout as namedSetTimeout } from 'node:timers'
import * as timersNamespace from 'node:timers'
import timersPromises, { setTimeout as namedSetTimeoutPromise } from 'node:timers/promises'
import * as timersPromisesNamespace from 'node:timers/promises'
import { afterEach, expect, test, vi } from 'vitest'
import './fixtures/timers.suite'

const require = createRequire(import.meta.url)

afterEach(() => {
vi.useRealTimers()
})

test('mocks node:timers default and require imports', () => {
vi.useFakeTimers()
const called: string[] = []

timers.setTimeout(() => called.push('default'), 100)
require('node:timers').setTimeout(() => called.push('require'), 100)

vi.advanceTimersByTime(100)
expect(called).toEqual(['default', 'require'])
})

test('mocks node:timers/promises default and require imports', async () => {
vi.useFakeTimers()
const resolved: string[] = []

const promises = [
timersPromises.setTimeout(100).then(() => resolved.push('default')),
require('node:timers/promises').setTimeout(100).then(() => resolved.push('require')),
]

await vi.advanceTimersByTimeAsync(100)
await Promise.all(promises)
expect(resolved).toEqual(['default', 'require'])
})

test('restores node timer imports', () => {
const originalSetTimeout = timers.setTimeout
const originalSetTimeoutPromise = timersPromises.setTimeout

vi.useFakeTimers()
expect(timers.setTimeout).not.toBe(originalSetTimeout)
expect(timersPromises.setTimeout).not.toBe(originalSetTimeoutPromise)

vi.useRealTimers()
expect(timers.setTimeout).toBe(originalSetTimeout)
expect(timersPromises.setTimeout).toBe(originalSetTimeoutPromise)
})

test('mocks named Node timer imports', async () => {
vi.useFakeTimers()
const called: string[] = []

namedSetTimeout(() => called.push('named'), 100)
timersNamespace.setTimeout(() => called.push('namespace'), 100)
const promises = [
namedSetTimeoutPromise(100).then(() => called.push('promises named')),
timersPromisesNamespace.setTimeout(100).then(() => called.push('promises namespace')),
]

await vi.advanceTimersByTimeAsync(100)
await Promise.all(promises)
expect(called).toEqual([
'named',
'namespace',
'promises named',
'promises namespace',
])
})

test('restores named Node timer imports', () => {
const originalNamedSetTimeout = namedSetTimeout
const originalNamedSetTimeoutPromise = namedSetTimeoutPromise

vi.useFakeTimers()
expect(namedSetTimeout).not.toBe(originalNamedSetTimeout)
expect(namedSetTimeoutPromise).not.toBe(originalNamedSetTimeoutPromise)

vi.useRealTimers()
expect(namedSetTimeout).toBe(originalNamedSetTimeout)
expect(namedSetTimeoutPromise).toBe(originalNamedSetTimeoutPromise)
})
Loading