From 11822f7b3fb4b90b6a95ea5f4152eaeb3d50ab1b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 19:06:29 +0100 Subject: [PATCH] refactor(tests): centralize vitest mock typing --- .../server.control-server.test-harness.ts | 19 ++--- src/commands/doctor.e2e-harness.ts | 71 +++++++++---------- .../bot.create-telegram-bot.test-harness.ts | 22 +++--- src/test-utils/vitest-mock-fn.ts | 6 ++ 4 files changed, 63 insertions(+), 55 deletions(-) create mode 100644 src/test-utils/vitest-mock-fn.ts diff --git a/src/browser/server.control-server.test-harness.ts b/src/browser/server.control-server.test-harness.ts index b8c4dcda2b1..630440806cc 100644 --- a/src/browser/server.control-server.test-harness.ts +++ b/src/browser/server.control-server.test-harness.ts @@ -3,6 +3,7 @@ import { type AddressInfo, createServer } from "node:net"; import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest"; +import type { MockFn } from "../test-utils/vitest-mock-fn.js"; type HarnessState = { testPort: number; @@ -22,23 +23,23 @@ const state: HarnessState = { prevGatewayPort: undefined, }; -export function getBrowserControlServerTestState() { +export function getBrowserControlServerTestState(): HarnessState { return state; } -export function getBrowserControlServerBaseUrl() { +export function getBrowserControlServerBaseUrl(): string { return `http://127.0.0.1:${state.testPort}`; } -export function setBrowserControlServerCreateTargetId(targetId: string | null) { +export function setBrowserControlServerCreateTargetId(targetId: string | null): void { state.createTargetId = targetId; } -export function setBrowserControlServerAttachOnly(attachOnly: boolean) { +export function setBrowserControlServerAttachOnly(attachOnly: boolean): void { state.cfgAttachOnly = attachOnly; } -export function setBrowserControlServerReachable(reachable: boolean) { +export function setBrowserControlServerReachable(reachable: boolean): void { state.reachable = reachable; } @@ -51,8 +52,8 @@ const cdpMocks = vi.hoisted(() => ({ })), })); -export function getCdpMocks() { - return cdpMocks; +export function getCdpMocks(): { createTargetViaCdp: MockFn; snapshotAria: MockFn } { + return cdpMocks as unknown as { createTargetViaCdp: MockFn; snapshotAria: MockFn }; } const pwMocks = vi.hoisted(() => ({ @@ -97,8 +98,8 @@ const pwMocks = vi.hoisted(() => ({ waitForViaPlaywright: vi.fn(async () => {}), })); -export function getPwMocks() { - return pwMocks; +export function getPwMocks(): Record { + return pwMocks as unknown as Record; } const chromeUserDataDir = vi.hoisted(() => ({ dir: "/tmp/openclaw" })); diff --git a/src/commands/doctor.e2e-harness.ts b/src/commands/doctor.e2e-harness.ts index 62de3c7ca37..6aef868d555 100644 --- a/src/commands/doctor.e2e-harness.ts +++ b/src/commands/doctor.e2e-harness.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, vi } from "vitest"; +import type { MockFn } from "../test-utils/vitest-mock-fn.js"; let originalIsTTY: boolean | undefined; let originalStateDir: string | undefined; @@ -19,40 +20,40 @@ function setStdinTty(value: boolean | undefined) { } } -export const readConfigFileSnapshot: ReturnType = vi.fn(); -export const confirm: ReturnType = vi.fn().mockResolvedValue(true); -export const select: ReturnType = vi.fn().mockResolvedValue("node"); -export const note: ReturnType = vi.fn(); -export const writeConfigFile: ReturnType = vi.fn().mockResolvedValue(undefined); -export const resolveOpenClawPackageRoot: ReturnType = vi.fn().mockResolvedValue(null); -export const runGatewayUpdate: ReturnType = vi.fn().mockResolvedValue({ +export const readConfigFileSnapshot = vi.fn() as unknown as MockFn; +export const confirm = vi.fn().mockResolvedValue(true) as unknown as MockFn; +export const select = vi.fn().mockResolvedValue("node") as unknown as MockFn; +export const note = vi.fn() as unknown as MockFn; +export const writeConfigFile = vi.fn().mockResolvedValue(undefined) as unknown as MockFn; +export const resolveOpenClawPackageRoot = vi.fn().mockResolvedValue(null) as unknown as MockFn; +export const runGatewayUpdate = vi.fn().mockResolvedValue({ status: "skipped", mode: "unknown", steps: [], durationMs: 0, -}); -export const migrateLegacyConfig: ReturnType = vi.fn((raw: unknown) => ({ +}) as unknown as MockFn; +export const migrateLegacyConfig = vi.fn((raw: unknown) => ({ config: raw as Record, changes: ["Moved routing.allowFrom → channels.whatsapp.allowFrom."], -})); +})) as unknown as MockFn; -export const runExec: ReturnType = vi.fn().mockResolvedValue({ +export const runExec = vi.fn().mockResolvedValue({ stdout: "", stderr: "", -}); -export const runCommandWithTimeout: ReturnType = vi.fn().mockResolvedValue({ +}) as unknown as MockFn; +export const runCommandWithTimeout = vi.fn().mockResolvedValue({ stdout: "", stderr: "", code: 0, signal: null, killed: false, -}); +}) as unknown as MockFn; -export const ensureAuthProfileStore: ReturnType = vi +export const ensureAuthProfileStore = vi .fn() - .mockReturnValue({ version: 1, profiles: {} }); + .mockReturnValue({ version: 1, profiles: {} }) as unknown as MockFn; -export const legacyReadConfigFileSnapshot: ReturnType = vi.fn().mockResolvedValue({ +export const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ path: "/tmp/openclaw.json", exists: false, raw: null, @@ -61,30 +62,28 @@ export const legacyReadConfigFileSnapshot: ReturnType = vi.fn().mo config: {}, issues: [], legacyIssues: [], -}); -export const createConfigIO: ReturnType = vi.fn(() => ({ +}) as unknown as MockFn; +export const createConfigIO = vi.fn(() => ({ readConfigFileSnapshot: legacyReadConfigFileSnapshot, -})); +})) as unknown as MockFn; -export const findLegacyGatewayServices: ReturnType = vi.fn().mockResolvedValue([]); -export const uninstallLegacyGatewayServices: ReturnType = vi +export const findLegacyGatewayServices = vi.fn().mockResolvedValue([]) as unknown as MockFn; +export const uninstallLegacyGatewayServices = vi.fn().mockResolvedValue([]) as unknown as MockFn; +export const findExtraGatewayServices = vi.fn().mockResolvedValue([]) as unknown as MockFn; +export const renderGatewayServiceCleanupHints = vi .fn() - .mockResolvedValue([]); -export const findExtraGatewayServices: ReturnType = vi.fn().mockResolvedValue([]); -export const renderGatewayServiceCleanupHints: ReturnType = vi - .fn() - .mockReturnValue(["cleanup"]); -export const resolveGatewayProgramArguments: ReturnType = vi.fn().mockResolvedValue({ + .mockReturnValue(["cleanup"]) as unknown as MockFn; +export const resolveGatewayProgramArguments = vi.fn().mockResolvedValue({ programArguments: ["node", "cli", "gateway", "--port", "18789"], -}); -export const serviceInstall: ReturnType = vi.fn().mockResolvedValue(undefined); -export const serviceIsLoaded: ReturnType = vi.fn().mockResolvedValue(false); -export const serviceStop: ReturnType = vi.fn().mockResolvedValue(undefined); -export const serviceRestart: ReturnType = vi.fn().mockResolvedValue(undefined); -export const serviceUninstall: ReturnType = vi.fn().mockResolvedValue(undefined); -export const callGateway: ReturnType = vi +}) as unknown as MockFn; +export const serviceInstall = vi.fn().mockResolvedValue(undefined) as unknown as MockFn; +export const serviceIsLoaded = vi.fn().mockResolvedValue(false) as unknown as MockFn; +export const serviceStop = vi.fn().mockResolvedValue(undefined) as unknown as MockFn; +export const serviceRestart = vi.fn().mockResolvedValue(undefined) as unknown as MockFn; +export const serviceUninstall = vi.fn().mockResolvedValue(undefined) as unknown as MockFn; +export const callGateway = vi .fn() - .mockRejectedValue(new Error("gateway closed")); + .mockRejectedValue(new Error("gateway closed")) as unknown as MockFn; vi.mock("@clack/prompts", () => ({ confirm, diff --git a/src/telegram/bot.create-telegram-bot.test-harness.ts b/src/telegram/bot.create-telegram-bot.test-harness.ts index 6201d4cb909..60824b8a2e5 100644 --- a/src/telegram/bot.create-telegram-bot.test-harness.ts +++ b/src/telegram/bot.create-telegram-bot.test-harness.ts @@ -1,8 +1,10 @@ -import { beforeEach, vi, type Mock } from "vitest"; +import { beforeEach, vi } from "vitest"; +import type { MockFn } from "../test-utils/vitest-mock-fn.js"; import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; -type AnyMock = Mock<(...args: unknown[]) => unknown>; -type AnyAsyncMock = Mock<(...args: unknown[]) => Promise>; +type AnyMock = MockFn<(...args: unknown[]) => unknown>; +type AnyAsyncMock = MockFn<(...args: unknown[]) => Promise>; + type ReplyOpts = | { onReplyStart?: () => void | Promise; @@ -74,12 +76,12 @@ vi.mock("../pairing/pairing-store.js", () => ({ upsertChannelPairingRequest, })); -export const useSpy: Mock<(arg: unknown) => void> = vi.fn(); -export const middlewareUseSpy: Mock<(...args: unknown[]) => unknown> = vi.fn(); -export const onSpy: Mock<(...args: unknown[]) => unknown> = vi.fn(); -export const stopSpy: Mock<(...args: unknown[]) => unknown> = vi.fn(); -export const commandSpy: Mock<(...args: unknown[]) => unknown> = vi.fn(); -export const botCtorSpy: Mock<(...args: unknown[]) => unknown> = vi.fn(); +export const useSpy: MockFn<(arg: unknown) => void> = vi.fn(); +export const middlewareUseSpy: AnyMock = vi.fn(); +export const onSpy: AnyMock = vi.fn(); +export const stopSpy: AnyMock = vi.fn(); +export const commandSpy: AnyMock = vi.fn(); +export const botCtorSpy: AnyMock = vi.fn(); export const answerCallbackQuerySpy: AnyAsyncMock = vi.fn(async () => undefined); export const sendChatActionSpy: AnyMock = vi.fn(); export const setMessageReactionSpy: AnyAsyncMock = vi.fn(async () => undefined); @@ -154,7 +156,7 @@ vi.mock("@grammyjs/transformer-throttler", () => ({ apiThrottler: () => throttlerSpy(), })); -export const replySpy: Mock<(ctx: unknown, opts?: ReplyOpts) => Promise> = vi.fn( +export const replySpy: MockFn<(ctx: unknown, opts?: ReplyOpts) => Promise> = vi.fn( async (_ctx, opts) => { await opts?.onReplyStart?.(); return undefined; diff --git a/src/test-utils/vitest-mock-fn.ts b/src/test-utils/vitest-mock-fn.ts new file mode 100644 index 00000000000..9e9526d7bda --- /dev/null +++ b/src/test-utils/vitest-mock-fn.ts @@ -0,0 +1,6 @@ +// Centralized Vitest mock type for harness modules under `src/`. +// Using an explicit named type avoids exporting inferred `vi.fn()` types that can trip TS2742. +// +// oxlint-disable-next-line typescript/no-explicit-any +export type MockFn any = (...args: any[]) => any> = + import("vitest").Mock;