diff --git a/src/testing/module-mock-lifecycle-tracking.test.ts b/src/testing/module-mock-lifecycle-tracking.test.ts index b7a48c619..024e0610c 100644 --- a/src/testing/module-mock-lifecycle-tracking.test.ts +++ b/src/testing/module-mock-lifecycle-tracking.test.ts @@ -113,6 +113,49 @@ describe("installModuleMockLifecycle active-test tracking", () => { ]) }) + test("treats module-evaluation mocks from another file as persistent while a different test is active", () => { + // given + const events: string[] = [] + let callerStack = "Error\n at file:///repo/tests/first.test.ts:5:1\n at test (native:1:11)" + let callerUrl = "file:///repo/tests/first.test.ts" + const mockApi = { + module: (specifier: string, factory: () => Record) => { + events.push(`module:${specifier}:${String(factory().named)}`) + }, + restore: mock(() => { + events.push("delegate:restore") + }), + } + + const { beginTestMockTracking, endTestMockTracking } = installModuleMockLifecycle(mockApi, { + getCallerStack: () => callerStack, + getCallerUrl: () => callerUrl, + trackOnlyDuringActiveTest: true, + isPersistentModuleMockOwner: () => true, + resolveSpecifier: (specifier, ownerUrl) => `resolved:${ownerUrl}:${specifier}`, + loadOriginalModule: () => ({ ok: true, value: { named: "original" } }), + }) + + beginTestMockTracking() + callerStack = "Error\n at file:///repo/tests/second.test.ts:5:1\n at moduleEvaluation (native:1:11)" + callerUrl = "file:///repo/tests/second.test.ts" + mockApi.module("./dependency", () => ({ named: "top-level second" })) + + // when + callerStack = "Error\n at file:///repo/tests/first.test.ts:10:1\n at cleanup (native:1:11)" + callerUrl = "file:///repo/tests/first.test.ts" + mockApi.restore() + endTestMockTracking() + + // then + expect(events).toEqual([ + "module:./dependency:top-level second", + "delegate:restore", + "module:./dependency:top-level second", + "module:resolved:file:///repo/tests/second.test.ts:./dependency:top-level second", + ]) + }) + test("clears persistent module-evaluation snapshots when restore runs while inactive", () => { // given const events: string[] = [] diff --git a/src/testing/module-mock-lifecycle.ts b/src/testing/module-mock-lifecycle.ts index 992888136..c2e214cca 100644 --- a/src/testing/module-mock-lifecycle.ts +++ b/src/testing/module-mock-lifecycle.ts @@ -1,6 +1,6 @@ import { createRequire } from "node:module" import { fileURLToPath } from "node:url" -import { defaultGetCallerStack, resolveCallerUrlFromStack } from "./module-mock-stack" +import { defaultGetCallerStack, isModuleEvaluationStack, resolveCallerUrlFromStack } from "./module-mock-stack" import { createRestoreExports } from "./module-mock-restore-exports" type MockModuleFactory = () => Record @@ -98,6 +98,7 @@ export function installModuleMockLifecycle( let lastRestoredSnapshots: ModuleSnapshot[] = [] let isActiveTest = !options.trackOnlyDuringActiveTest let hasStartedTest = false + let activeTestOwnerUrl: string | null = null const delegateModule = mockApi.module.bind(mockApi) const delegateRestore = mockApi.restore.bind(mockApi) const getCallerStack = options.getCallerStack ?? defaultGetCallerStack @@ -200,18 +201,27 @@ export function installModuleMockLifecycle( function beginTestMockTracking(): void { hasStartedTest = true isActiveTest = true + const callerStack = getCallerStack() + activeTestOwnerUrl = getCallerUrl(callerStack) } function endTestMockTracking(): void { isActiveTest = !options.trackOnlyDuringActiveTest + activeTestOwnerUrl = null } mockApi.module = (specifier: string, factory: MockModuleFactory): unknown => { lastRestoredSnapshots = [] const callerStack = getCallerStack() const callerUrl = getCallerUrl(callerStack) + const isParallelFileEvaluationMock = + isActiveTest && + options.trackOnlyDuringActiveTest === true && + activeTestOwnerUrl !== null && + callerUrl !== activeTestOwnerUrl && + isModuleEvaluationStack(callerStack) - if (!isActiveTest && isPersistentModuleMockOwner(callerUrl)) { + if ((!isActiveTest || isParallelFileEvaluationMock) && isPersistentModuleMockOwner(callerUrl)) { const resolvedSpecifier = resolveSpecifier(specifier, callerUrl) const snapshotsByOwner = persistentSnapshots.get(resolvedSpecifier) ?? new Map() const existingSnapshot = snapshotsByOwner.get(callerUrl)