From 003d6c45d6b4718a9f8500f25a9ef694eb42581b Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 17 Feb 2026 10:52:25 +0900 Subject: [PATCH] chore: Fix types in tests 6/N. --- src/commands/gateway-status.e2e.test.ts | 42 +++++++++----- src/commands/status.e2e.test.ts | 65 +++++++++++----------- src/memory/embeddings.test.ts | 74 ++++++++++++++++--------- src/memory/qmd-manager.test.ts | 62 ++++++++++++--------- src/tui/tui-event-handlers.test.ts | 40 +++++++------ src/tui/tui-event-handlers.ts | 13 +++-- 6 files changed, 180 insertions(+), 116 deletions(-) diff --git a/src/commands/gateway-status.e2e.test.ts b/src/commands/gateway-status.e2e.test.ts index 900bc679e48..1e532a752c6 100644 --- a/src/commands/gateway-status.e2e.test.ts +++ b/src/commands/gateway-status.e2e.test.ts @@ -7,12 +7,23 @@ const loadConfig = vi.fn(() => ({ auth: { token: "ltok" }, }, })); -const resolveGatewayPort = vi.fn(() => 18789); -const discoverGatewayBeacons = vi.fn(async () => []); +const resolveGatewayPort = vi.fn((_cfg?: unknown) => 18789); +const discoverGatewayBeacons = vi.fn( + async (_opts?: unknown): Promise> => [], +); const pickPrimaryTailnetIPv4 = vi.fn(() => "100.64.0.10"); const sshStop = vi.fn(async () => {}); -const resolveSshConfig = vi.fn(async () => null); -const startSshPortForward = vi.fn(async () => ({ +const resolveSshConfig = vi.fn( + async ( + _opts?: unknown, + ): Promise<{ + user: string; + host: string; + port: number; + identityFiles: string[]; + } | null> => null, +); +const startSshPortForward = vi.fn(async (_opts?: unknown) => ({ parsedTarget: { user: "me", host: "studio", port: 22 }, localPort: 18789, remotePort: 18789, @@ -20,7 +31,8 @@ const startSshPortForward = vi.fn(async () => ({ stderr: [], stop: sshStop, })); -const probeGateway = vi.fn(async ({ url }: { url: string }) => { +const probeGateway = vi.fn(async (opts: { url: string }) => { + const { url } = opts; if (url.includes("127.0.0.1")) { return { ok: true, @@ -80,32 +92,32 @@ const probeGateway = vi.fn(async ({ url }: { url: string }) => { }); vi.mock("../config/config.js", () => ({ - loadConfig: () => loadConfig(), - resolveGatewayPort: (cfg: unknown) => resolveGatewayPort(cfg), + loadConfig, + resolveGatewayPort, })); vi.mock("../infra/bonjour-discovery.js", () => ({ - discoverGatewayBeacons: (opts: unknown) => discoverGatewayBeacons(opts), + discoverGatewayBeacons, })); vi.mock("../infra/tailnet.js", () => ({ - pickPrimaryTailnetIPv4: () => pickPrimaryTailnetIPv4(), + pickPrimaryTailnetIPv4, })); vi.mock("../infra/ssh-tunnel.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - startSshPortForward: (opts: unknown) => startSshPortForward(opts), + startSshPortForward, }; }); vi.mock("../infra/ssh-config.js", () => ({ - resolveSshConfig: (opts: unknown) => resolveSshConfig(opts), + resolveSshConfig, })); vi.mock("../gateway/probe.js", () => ({ - probeGateway: (opts: unknown) => probeGateway(opts), + probeGateway, })); function createRuntimeCapture() { @@ -198,7 +210,8 @@ describe("gateway-status command", () => { loadConfig.mockReturnValueOnce({ gateway: { mode: "remote", - remote: {}, + remote: { url: "", token: "" }, + auth: { token: "ltok" }, }, }); discoverGatewayBeacons.mockResolvedValueOnce([ @@ -226,6 +239,7 @@ describe("gateway-status command", () => { gateway: { mode: "remote", remote: { url: "ws://peters-mac-studio-1.sheep-coho.ts.net:18789", token: "rtok" }, + auth: { token: "ltok" }, }, }); resolveSshConfig.mockResolvedValueOnce({ @@ -259,6 +273,7 @@ describe("gateway-status command", () => { gateway: { mode: "remote", remote: { url: "ws://studio.example:18789", token: "rtok" }, + auth: { token: "ltok" }, }, }); resolveSshConfig.mockResolvedValueOnce(null); @@ -284,6 +299,7 @@ describe("gateway-status command", () => { gateway: { mode: "remote", remote: { url: "ws://studio.example:18789", token: "rtok" }, + auth: { token: "ltok" }, }, }); resolveSshConfig.mockResolvedValueOnce({ diff --git a/src/commands/status.e2e.test.ts b/src/commands/status.e2e.test.ts index 55596f881ca..ffa438b1cf0 100644 --- a/src/commands/status.e2e.test.ts +++ b/src/commands/status.e2e.test.ts @@ -1,4 +1,5 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import type { Mock } from "vitest"; import { captureEnv } from "../test-utils/env.js"; let envSnapshot: ReturnType; @@ -323,10 +324,12 @@ const runtime = { exit: vi.fn(), }; +const runtimeLogMock = runtime.log as Mock<(...args: unknown[]) => void>; + describe("statusCommand", () => { it("prints JSON when requested", async () => { await statusCommand({ json: true }, runtime as never); - const payload = JSON.parse((runtime.log as vi.Mock).mock.calls[0][0]); + const payload = JSON.parse(String(runtimeLogMock.mock.calls[0]?.[0])); expect(payload.linkChannel.linked).toBe(true); expect(payload.memory.agentId).toBe("main"); expect(payload.memoryPlugin.enabled).toBe(true); @@ -348,9 +351,9 @@ describe("statusCommand", () => { it("surfaces unknown usage when totalTokens is missing", async () => { await withUnknownUsageStore(async () => { - (runtime.log as vi.Mock).mockClear(); + runtimeLogMock.mockClear(); await statusCommand({ json: true }, runtime as never); - const payload = JSON.parse((runtime.log as vi.Mock).mock.calls.at(-1)?.[0]); + const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0])); expect(payload.sessions.recent[0].totalTokens).toBeNull(); expect(payload.sessions.recent[0].totalTokensFresh).toBe(false); expect(payload.sessions.recent[0].percentUsed).toBeNull(); @@ -360,37 +363,37 @@ describe("statusCommand", () => { it("prints unknown usage in formatted output when totalTokens is missing", async () => { await withUnknownUsageStore(async () => { - (runtime.log as vi.Mock).mockClear(); + runtimeLogMock.mockClear(); await statusCommand({}, runtime as never); - const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0])); + const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])); expect(logs.some((line) => line.includes("unknown/") && line.includes("(?%)"))).toBe(true); }); }); it("prints formatted lines otherwise", async () => { - (runtime.log as vi.Mock).mockClear(); + runtimeLogMock.mockClear(); await statusCommand({}, runtime as never); - const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0])); - expect(logs.some((l) => l.includes("OpenClaw status"))).toBe(true); - expect(logs.some((l) => l.includes("Overview"))).toBe(true); - expect(logs.some((l) => l.includes("Security audit"))).toBe(true); - expect(logs.some((l) => l.includes("Summary:"))).toBe(true); - expect(logs.some((l) => l.includes("CRITICAL"))).toBe(true); - expect(logs.some((l) => l.includes("Dashboard"))).toBe(true); - expect(logs.some((l) => l.includes("macos 14.0 (arm64)"))).toBe(true); - expect(logs.some((l) => l.includes("Memory"))).toBe(true); - expect(logs.some((l) => l.includes("Channels"))).toBe(true); - expect(logs.some((l) => l.includes("WhatsApp"))).toBe(true); - expect(logs.some((l) => l.includes("Sessions"))).toBe(true); - expect(logs.some((l) => l.includes("+1000"))).toBe(true); - expect(logs.some((l) => l.includes("50%"))).toBe(true); - expect(logs.some((l) => l.includes("LaunchAgent"))).toBe(true); - expect(logs.some((l) => l.includes("FAQ:"))).toBe(true); - expect(logs.some((l) => l.includes("Troubleshooting:"))).toBe(true); - expect(logs.some((l) => l.includes("Next steps:"))).toBe(true); + const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])); + expect(logs.some((l: string) => l.includes("OpenClaw status"))).toBe(true); + expect(logs.some((l: string) => l.includes("Overview"))).toBe(true); + expect(logs.some((l: string) => l.includes("Security audit"))).toBe(true); + expect(logs.some((l: string) => l.includes("Summary:"))).toBe(true); + expect(logs.some((l: string) => l.includes("CRITICAL"))).toBe(true); + expect(logs.some((l: string) => l.includes("Dashboard"))).toBe(true); + expect(logs.some((l: string) => l.includes("macos 14.0 (arm64)"))).toBe(true); + expect(logs.some((l: string) => l.includes("Memory"))).toBe(true); + expect(logs.some((l: string) => l.includes("Channels"))).toBe(true); + expect(logs.some((l: string) => l.includes("WhatsApp"))).toBe(true); + expect(logs.some((l: string) => l.includes("Sessions"))).toBe(true); + expect(logs.some((l: string) => l.includes("+1000"))).toBe(true); + expect(logs.some((l: string) => l.includes("50%"))).toBe(true); + expect(logs.some((l: string) => l.includes("LaunchAgent"))).toBe(true); + expect(logs.some((l: string) => l.includes("FAQ:"))).toBe(true); + expect(logs.some((l: string) => l.includes("Troubleshooting:"))).toBe(true); + expect(logs.some((l: string) => l.includes("Next steps:"))).toBe(true); expect( logs.some( - (l) => + (l: string) => l.includes("openclaw status --all") || l.includes("openclaw --profile isolated status --all") || l.includes("openclaw status --all") || @@ -414,10 +417,10 @@ describe("statusCommand", () => { presence: [], configSnapshot: null, }); - (runtime.log as vi.Mock).mockClear(); + runtimeLogMock.mockClear(); await statusCommand({}, runtime as never); - const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0])); - expect(logs.some((l) => l.includes("auth token"))).toBe(true); + const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])); + expect(logs.some((l: string) => l.includes("auth token"))).toBe(true); } finally { if (prevToken === undefined) { delete process.env.OPENCLAW_GATEWAY_TOKEN; @@ -462,9 +465,9 @@ describe("statusCommand", () => { }, }); - (runtime.log as vi.Mock).mockClear(); + runtimeLogMock.mockClear(); await statusCommand({}, runtime as never); - const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0])); + const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])); expect(logs.join("\n")).toMatch(/Signal/i); expect(logs.join("\n")).toMatch(/iMessage/i); expect(logs.join("\n")).toMatch(/gateway:/i); @@ -507,7 +510,7 @@ describe("statusCommand", () => { }); await statusCommand({ json: true }, runtime as never); - const payload = JSON.parse((runtime.log as vi.Mock).mock.calls.at(-1)?.[0]); + const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0])); expect(payload.sessions.count).toBe(2); expect(payload.sessions.paths.length).toBe(2); expect( diff --git a/src/memory/embeddings.test.ts b/src/memory/embeddings.test.ts index 98971c389da..699d4e76d81 100644 --- a/src/memory/embeddings.test.ts +++ b/src/memory/embeddings.test.ts @@ -19,11 +19,18 @@ vi.mock("./node-llama.js", () => ({ })); const createFetchMock = () => - vi.fn(async () => ({ + vi.fn(async (_input?: unknown, _init?: unknown) => ({ ok: true, status: 200, json: async () => ({ data: [{ embedding: [1, 2, 3] }] }), - })) as unknown as typeof fetch; + })); + +function requireProvider(result: Awaited>) { + if (!result.provider) { + throw new Error("Expected embedding provider"); + } + return result.provider; +} describe("embedding provider remote overrides", () => { afterEach(() => { @@ -69,10 +76,12 @@ describe("embedding provider remote overrides", () => { fallback: "openai", }); - await result.provider.embedQuery("hello"); + const provider = requireProvider(result); + await provider.embedQuery("hello"); expect(authModule.resolveApiKeyForProvider).not.toHaveBeenCalled(); - const [url, init] = fetchMock.mock.calls[0] ?? []; + const url = fetchMock.mock.calls[0]?.[0]; + const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined; expect(url).toBe("https://remote.example/v1/embeddings"); const headers = (init?.headers ?? {}) as Record; expect(headers.Authorization).toBe("Bearer remote-key"); @@ -112,19 +121,21 @@ describe("embedding provider remote overrides", () => { fallback: "openai", }); - await result.provider.embedQuery("hello"); + const provider = requireProvider(result); + await provider.embedQuery("hello"); expect(authModule.resolveApiKeyForProvider).toHaveBeenCalledTimes(1); - const headers = (fetchMock.mock.calls[0]?.[1]?.headers as Record) ?? {}; + const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined; + const headers = (init?.headers as Record) ?? {}; expect(headers.Authorization).toBe("Bearer provider-key"); }); it("builds Gemini embeddings requests with api key header", async () => { - const fetchMock = vi.fn(async () => ({ + const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({ ok: true, status: 200, json: async () => ({ embedding: { values: [1, 2, 3] } }), - })) as unknown as typeof fetch; + })); vi.stubGlobal("fetch", fetchMock); vi.mocked(authModule.resolveApiKeyForProvider).mockResolvedValue({ apiKey: "provider-key", @@ -152,9 +163,11 @@ describe("embedding provider remote overrides", () => { fallback: "openai", }); - await result.provider.embedQuery("hello"); + const provider = requireProvider(result); + await provider.embedQuery("hello"); - const [url, init] = fetchMock.mock.calls[0] ?? []; + const url = fetchMock.mock.calls[0]?.[0]; + const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined; expect(url).toBe( "https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent", ); @@ -186,15 +199,16 @@ describe("embedding provider auto selection", () => { }); expect(result.requestedProvider).toBe("auto"); - expect(result.provider.id).toBe("openai"); + const provider = requireProvider(result); + expect(provider.id).toBe("openai"); }); it("uses gemini when openai is missing", async () => { - const fetchMock = vi.fn(async () => ({ + const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({ ok: true, status: 200, json: async () => ({ embedding: { values: [1, 2, 3] } }), - })) as unknown as typeof fetch; + })); vi.stubGlobal("fetch", fetchMock); vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => { if (provider === "openai") { @@ -214,8 +228,9 @@ describe("embedding provider auto selection", () => { }); expect(result.requestedProvider).toBe("auto"); - expect(result.provider.id).toBe("gemini"); - await result.provider.embedQuery("hello"); + const provider = requireProvider(result); + expect(provider.id).toBe("gemini"); + await provider.embedQuery("hello"); const [url] = fetchMock.mock.calls[0] ?? []; expect(url).toBe( `https://generativelanguage.googleapis.com/v1beta/models/${DEFAULT_GEMINI_EMBEDDING_MODEL}:embedContent`, @@ -223,11 +238,11 @@ describe("embedding provider auto selection", () => { }); it("keeps explicit model when openai is selected", async () => { - const fetchMock = vi.fn(async () => ({ + const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({ ok: true, status: 200, json: async () => ({ data: [{ embedding: [1, 2, 3] }] }), - })) as unknown as typeof fetch; + })); vi.stubGlobal("fetch", fetchMock); vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => { if (provider === "openai") { @@ -244,11 +259,13 @@ describe("embedding provider auto selection", () => { }); expect(result.requestedProvider).toBe("auto"); - expect(result.provider.id).toBe("openai"); - await result.provider.embedQuery("hello"); - const [url, init] = fetchMock.mock.calls[0] ?? []; + const provider = requireProvider(result); + expect(provider.id).toBe("openai"); + await provider.embedQuery("hello"); + const url = fetchMock.mock.calls[0]?.[0]; + const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined; expect(url).toBe("https://api.openai.com/v1/embeddings"); - const payload = JSON.parse(String(init?.body ?? "{}")) as { model?: string }; + const payload = JSON.parse(init?.body as string) as { model?: string }; expect(payload.model).toBe("text-embedding-3-small"); }); }); @@ -282,7 +299,8 @@ describe("embedding provider local fallback", () => { fallback: "openai", }); - expect(result.provider.id).toBe("openai"); + const provider = requireProvider(result); + expect(provider.id).toBe("openai"); expect(result.fallbackFrom).toBe("local"); expect(result.fallbackReason).toContain("node-llama-cpp"); }); @@ -365,7 +383,8 @@ describe("local embedding normalization", () => { const result = await createLocalProviderForTest(); - const embedding = await result.provider.embedQuery("test query"); + const provider = requireProvider(result); + const embedding = await provider.embedQuery("test query"); const magnitude = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0)); @@ -380,7 +399,8 @@ describe("local embedding normalization", () => { const result = await createLocalProviderForTest(); - const embedding = await result.provider.embedQuery("test"); + const provider = requireProvider(result); + const embedding = await provider.embedQuery("test"); expect(embedding).toEqual([0, 0, 0, 0]); expect(embedding.every((value) => Number.isFinite(value))).toBe(true); @@ -393,7 +413,8 @@ describe("local embedding normalization", () => { const result = await createLocalProviderForTest(); - const embedding = await result.provider.embedQuery("test"); + const provider = requireProvider(result); + const embedding = await provider.embedQuery("test"); expect(embedding).toEqual([1, 0, 0, 0]); expect(embedding.every((value) => Number.isFinite(value))).toBe(true); @@ -424,7 +445,8 @@ describe("local embedding normalization", () => { const result = await createLocalProviderForTest(); - const embeddings = await result.provider.embedBatch(["text1", "text2", "text3"]); + const provider = requireProvider(result); + const embeddings = await provider.embedBatch(["text1", "text2", "text3"]); for (const embedding of embeddings) { const magnitude = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0)); diff --git a/src/memory/qmd-manager.test.ts b/src/memory/qmd-manager.test.ts index af338e25427..cc47ddd38b3 100644 --- a/src/memory/qmd-manager.test.ts +++ b/src/memory/qmd-manager.test.ts @@ -3,6 +3,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { Mock } from "vitest"; const { logWarnMock, logDebugMock, logInfoMock } = vi.hoisted(() => ({ logWarnMock: vi.fn(), @@ -68,7 +69,7 @@ vi.mock("../logging/subsystem.js", () => ({ }, })); -vi.mock(import("node:child_process"), async (importOriginal) => { +vi.mock("node:child_process", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, @@ -81,7 +82,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolveMemoryBackendConfig } from "./backend-config.js"; import { QmdMemoryManager } from "./qmd-manager.js"; -const spawnMock = mockedSpawn as unknown as vi.Mock; +const spawnMock = mockedSpawn as unknown as Mock; describe("QmdMemoryManager", () => { let fixtureRoot: string; @@ -195,7 +196,7 @@ describe("QmdMemoryManager", () => { const { manager } = await createManager({ mode: "full" }); expect(releaseUpdate).not.toBeNull(); - releaseUpdate?.(); + (releaseUpdate as (() => void) | null)?.(); await manager?.close(); }); @@ -256,7 +257,7 @@ describe("QmdMemoryManager", () => { }); await new Promise((resolve) => setImmediate(resolve)); expect(created).toBe(false); - releaseUpdate?.(); + (releaseUpdate as (() => void) | null)?.(); const manager = await createPromise; await manager?.close(); }); @@ -340,7 +341,7 @@ describe("QmdMemoryManager", () => { expect(manager).toBeTruthy(); await manager?.close(); - const commands = spawnMock.mock.calls.map((call) => call[1] as string[]); + const commands = spawnMock.mock.calls.map((call: unknown[]) => call[1] as string[]); const removeSessions = commands.find( (args) => args[0] === "collection" && args[1] === "remove" && args[2] === sessionCollectionName, @@ -389,7 +390,7 @@ describe("QmdMemoryManager", () => { const { manager } = await createManager({ mode: "full" }); await manager.close(); - const commands = spawnMock.mock.calls.map((call) => call[1] as string[]); + const commands = spawnMock.mock.calls.map((call: unknown[]) => call[1] as string[]); const removeSessions = commands.find( (args) => args[0] === "collection" && args[1] === "remove" && args[2] === sessionCollectionName, @@ -485,12 +486,12 @@ describe("QmdMemoryManager", () => { await expect(manager.sync({ reason: "manual" })).resolves.toBeUndefined(); const removeCalls = spawnMock.mock.calls - .map((call) => call[1] as string[]) - .filter((args) => args[0] === "collection" && args[1] === "remove") + .map((call: unknown[]) => call[1] as string[]) + .filter((args: string[]) => args[0] === "collection" && args[1] === "remove") .map((args) => args[2]); const addCalls = spawnMock.mock.calls - .map((call) => call[1] as string[]) - .filter((args) => args[0] === "collection" && args[1] === "add") + .map((call: unknown[]) => call[1] as string[]) + .filter((args: string[]) => args[0] === "collection" && args[1] === "add") .map((args) => args[args.indexOf("--name") + 1]); expect(updateCalls).toBe(2); @@ -536,8 +537,8 @@ describe("QmdMemoryManager", () => { ); const removeCalls = spawnMock.mock.calls - .map((call) => call[1] as string[]) - .filter((args) => args[0] === "collection" && args[1] === "remove"); + .map((call: unknown[]) => call[1] as string[]) + .filter((args: string[]) => args[0] === "collection" && args[1] === "remove"); expect(removeCalls).toHaveLength(0); await manager.close(); @@ -575,7 +576,9 @@ describe("QmdMemoryManager", () => { manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }), ).resolves.toEqual([]); - const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search"); + const searchCall = spawnMock.mock.calls.find( + (call: unknown[]) => (call[1] as string[])?.[0] === "search", + ); expect(searchCall?.[1]).toEqual([ "search", "test", @@ -585,7 +588,9 @@ describe("QmdMemoryManager", () => { "-c", "workspace-main", ]); - expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false); + expect( + spawnMock.mock.calls.some((call: unknown[]) => (call[1] as string[])?.[0] === "query"), + ).toBe(false); expect(maxResults).toBeGreaterThan(0); await manager.close(); }); @@ -628,7 +633,7 @@ describe("QmdMemoryManager", () => { ).resolves.toEqual([]); const searchAndQueryCalls = spawnMock.mock.calls - .map((call) => call[1]) + .map((call: unknown[]) => call[1]) .filter( (args): args is string[] => Array.isArray(args) && ["search", "query"].includes(args[0]), ); @@ -684,7 +689,7 @@ describe("QmdMemoryManager", () => { if (!releaseFirstUpdate) { throw new Error("first update release missing"); } - releaseFirstUpdate(); + (releaseFirstUpdate as () => void)(); await Promise.all([inFlight, forced]); expect(updateCalls).toBe(2); @@ -744,7 +749,7 @@ describe("QmdMemoryManager", () => { if (!releaseFirstUpdate) { throw new Error("first update release missing"); } - releaseFirstUpdate(); + (releaseFirstUpdate as () => void)(); await secondUpdateSpawned.promise; const forcedTwo = manager.sync({ reason: "manual-again", force: true }); @@ -752,7 +757,7 @@ describe("QmdMemoryManager", () => { if (!releaseSecondUpdate) { throw new Error("second update release missing"); } - releaseSecondUpdate(); + (releaseSecondUpdate as () => void)(); await Promise.all([inFlight, forcedOne, forcedTwo]); expect(updateCalls).toBe(3); @@ -787,7 +792,9 @@ describe("QmdMemoryManager", () => { const { manager, resolved } = await createManager(); await manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }); - const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search"); + const searchCall = spawnMock.mock.calls.find( + (call: unknown[]) => (call[1] as string[])?.[0] === "search", + ); const maxResults = resolved.qmd?.limits.maxResults; if (!maxResults) { throw new Error("qmd maxResults missing"); @@ -843,8 +850,8 @@ describe("QmdMemoryManager", () => { ).resolves.toEqual([]); const queryCalls = spawnMock.mock.calls - .map((call) => call[1] as string[]) - .filter((args) => args[0] === "query"); + .map((call: unknown[]) => call[1] as string[]) + .filter((args: string[]) => args[0] === "query"); expect(queryCalls).toEqual([ ["query", "test", "--json", "-n", String(maxResults), "-c", "workspace-main"], ["query", "test", "--json", "-n", String(maxResults), "-c", "notes-main"], @@ -894,8 +901,8 @@ describe("QmdMemoryManager", () => { ).resolves.toEqual([]); const searchAndQueryCalls = spawnMock.mock.calls - .map((call) => call[1] as string[]) - .filter((args) => args[0] === "search" || args[0] === "query"); + .map((call: unknown[]) => call[1] as string[]) + .filter((args: string[]) => args[0] === "search" || args[0] === "query"); expect(searchAndQueryCalls).toEqual([ [ "search", @@ -931,7 +938,9 @@ describe("QmdMemoryManager", () => { const results = await manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }); expect(results).toEqual([]); - expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false); + expect( + spawnMock.mock.calls.some((call: unknown[]) => (call[1] as string[])?.[0] === "query"), + ).toBe(false); await manager.close(); }); @@ -1081,12 +1090,13 @@ describe("QmdMemoryManager", () => { "utf-8", ); + const currentMemory = cfg.memory; cfg = { ...cfg, memory: { - ...cfg.memory, + ...currentMemory, qmd: { - ...cfg.memory.qmd, + ...currentMemory?.qmd, sessions: { enabled: true, }, diff --git a/src/tui/tui-event-handlers.test.ts b/src/tui/tui-event-handlers.test.ts index ff0521284c4..10b619d0d01 100644 --- a/src/tui/tui-event-handlers.test.ts +++ b/src/tui/tui-event-handlers.test.ts @@ -1,19 +1,26 @@ -import type { TUI } from "@mariozechner/pi-tui"; import { describe, expect, it, vi } from "vitest"; -import type { ChatLog } from "./components/chat-log.js"; import { createEventHandlers } from "./tui-event-handlers.js"; import type { AgentEvent, ChatEvent, TuiStateAccess } from "./tui-types.js"; -type MockChatLog = Pick< - ChatLog, - | "startTool" - | "updateToolResult" - | "addSystem" - | "updateAssistant" - | "finalizeAssistant" - | "dropAssistant" ->; -type MockTui = Pick; +type MockFn = ReturnType; +type HandlerChatLog = { + startTool: (...args: unknown[]) => void; + updateToolResult: (...args: unknown[]) => void; + addSystem: (...args: unknown[]) => void; + updateAssistant: (...args: unknown[]) => void; + finalizeAssistant: (...args: unknown[]) => void; + dropAssistant: (...args: unknown[]) => void; +}; +type HandlerTui = { requestRender: (...args: unknown[]) => void }; +type MockChatLog = { + startTool: MockFn; + updateToolResult: MockFn; + addSystem: MockFn; + updateAssistant: MockFn; + finalizeAssistant: MockFn; + dropAssistant: MockFn; +}; +type MockTui = { requestRender: MockFn }; describe("tui-event-handlers: handleAgentEvent", () => { const makeState = (overrides?: Partial): TuiStateAccess => ({ @@ -40,15 +47,15 @@ describe("tui-event-handlers: handleAgentEvent", () => { }); const makeContext = (state: TuiStateAccess) => { - const chatLog: MockChatLog = { + const chatLog = { startTool: vi.fn(), updateToolResult: vi.fn(), addSystem: vi.fn(), updateAssistant: vi.fn(), finalizeAssistant: vi.fn(), dropAssistant: vi.fn(), - }; - const tui: MockTui = { requestRender: vi.fn() }; + } as unknown as MockChatLog & HandlerChatLog; + const tui = { requestRender: vi.fn() } as unknown as MockTui & HandlerTui; const setActivityStatus = vi.fn(); const loadHistory = vi.fn(); const localRunIds = new Set(); @@ -136,7 +143,8 @@ describe("tui-event-handlers: handleAgentEvent", () => { addSystem: vi.fn(), updateAssistant: vi.fn(), finalizeAssistant: vi.fn(), - }, + dropAssistant: vi.fn(), + } as unknown as HandlerChatLog, tui, state, setActivityStatus, diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index 08b6eb54ccc..a63f77260f0 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -1,12 +1,17 @@ -import type { TUI } from "@mariozechner/pi-tui"; -import type { ChatLog } from "./components/chat-log.js"; import { asString, extractTextFromMessage, isCommandMessage } from "./tui-formatters.js"; import { TuiStreamAssembler } from "./tui-stream-assembler.js"; import type { AgentEvent, ChatEvent, TuiStateAccess } from "./tui-types.js"; type EventHandlerContext = { - chatLog: ChatLog; - tui: TUI; + chatLog: { + startTool: (...args: unknown[]) => void; + updateToolResult: (...args: unknown[]) => void; + addSystem: (...args: unknown[]) => void; + updateAssistant: (...args: unknown[]) => void; + finalizeAssistant: (...args: unknown[]) => void; + dropAssistant: (...args: unknown[]) => void; + }; + tui: { requestRender: (...args: unknown[]) => void }; state: TuiStateAccess; setActivityStatus: (text: string) => void; refreshSessionInfo?: () => Promise;