diff --git a/src/browser/pw-ai.test.ts b/src/browser/pw-ai.test.ts index 75e52c3dd8..7f05e95cb2 100644 --- a/src/browser/pw-ai.test.ts +++ b/src/browser/pw-ai.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; vi.mock("playwright-core", () => ({ chromium: { @@ -54,26 +54,28 @@ function createBrowser(pages: unknown[]) { }; } -async function importModule() { - return await import("./pw-ai.js"); -} +let mod: typeof import("./pw-ai.js"); +let chromiumMock: typeof import("playwright-core").chromium; + +beforeAll(async () => { + const pw = await import("playwright-core"); + chromiumMock = pw.chromium; + mod = await import("./pw-ai.js"); +}); afterEach(async () => { - const mod = await importModule(); await mod.closePlaywrightBrowserConnection(); vi.clearAllMocks(); }); describe("pw-ai", () => { it("captures an ai snapshot via Playwright for a specific target", async () => { - const { chromium } = await import("playwright-core"); const p1 = createPage({ targetId: "T1", snapshotFull: "ONE" }); const p2 = createPage({ targetId: "T2", snapshotFull: "TWO" }); const browser = createBrowser([p1.page, p2.page]); - (chromium.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); + (chromiumMock.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); - const mod = await importModule(); const res = await mod.snapshotAiViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", targetId: "T2", @@ -85,14 +87,12 @@ describe("pw-ai", () => { }); it("registers aria refs from ai snapshots for act commands", async () => { - const { chromium } = await import("playwright-core"); const snapshot = ['- button "OK" [ref=e1]', '- link "Docs" [ref=e2]'].join("\n"); const p1 = createPage({ targetId: "T1", snapshotFull: snapshot }); const browser = createBrowser([p1.page]); - (chromium.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); + (chromiumMock.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); - const mod = await importModule(); const res = await mod.snapshotAiViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", targetId: "T1", @@ -114,14 +114,12 @@ describe("pw-ai", () => { }); it("truncates oversized snapshots", async () => { - const { chromium } = await import("playwright-core"); const longSnapshot = "A".repeat(20); const p1 = createPage({ targetId: "T1", snapshotFull: longSnapshot }); const browser = createBrowser([p1.page]); - (chromium.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); + (chromiumMock.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); - const mod = await importModule(); const res = await mod.snapshotAiViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", targetId: "T1", @@ -134,12 +132,10 @@ describe("pw-ai", () => { }); it("clicks a ref using aria-ref locator", async () => { - const { chromium } = await import("playwright-core"); const p1 = createPage({ targetId: "T1" }); const browser = createBrowser([p1.page]); - (chromium.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); + (chromiumMock.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); - const mod = await importModule(); await mod.clickViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", targetId: "T1", @@ -151,12 +147,10 @@ describe("pw-ai", () => { }); it("fails with a clear error when _snapshotForAI is missing", async () => { - const { chromium } = await import("playwright-core"); const p1 = createPage({ targetId: "T1", hasSnapshotForAI: false }); const browser = createBrowser([p1.page]); - (chromium.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); + (chromiumMock.connectOverCDP as unknown as ReturnType).mockResolvedValue(browser); - const mod = await importModule(); await expect( mod.snapshotAiViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", @@ -166,13 +160,11 @@ describe("pw-ai", () => { }); it("reuses the CDP connection for repeated calls", async () => { - const { chromium } = await import("playwright-core"); const p1 = createPage({ targetId: "T1", snapshotFull: "ONE" }); const browser = createBrowser([p1.page]); - const connect = vi.spyOn(chromium, "connectOverCDP"); + const connect = vi.spyOn(chromiumMock, "connectOverCDP"); connect.mockResolvedValue(browser); - const mod = await importModule(); await mod.snapshotAiViaPlaywright({ cdpUrl: "http://127.0.0.1:18792", targetId: "T1", diff --git a/src/memory/index.test.ts b/src/memory/index.test.ts index 98eba4cc79..f1c81bbe35 100644 --- a/src/memory/index.test.ts +++ b/src/memory/index.test.ts @@ -236,110 +236,6 @@ describe("memory index", () => { expect(results[0]?.path).toContain("memory/2026-01-12.md"); }); - it("hybrid weights shift ranking between vector and keyword matches", async () => { - const manyAlpha = Array.from({ length: 50 }, () => "Alpha").join(" "); - await fs.writeFile( - path.join(workspaceDir, "memory", "vector-only.md"), - "Alpha beta. Alpha beta. Alpha beta. Alpha beta.", - ); - await fs.writeFile( - path.join(workspaceDir, "memory", "keyword-only.md"), - `${manyAlpha} beta id123.`, - ); - - const vectorWeightedCfg = { - agents: { - defaults: { - workspace: workspaceDir, - memorySearch: { - provider: "openai", - model: "mock-embed", - store: { path: indexPath, vector: { enabled: false } }, - sync: { watch: false, onSessionStart: false, onSearch: true }, - query: { - minScore: 0, - maxResults: 200, - hybrid: { - enabled: true, - vectorWeight: 0.99, - textWeight: 0.01, - candidateMultiplier: 10, - }, - }, - }, - }, - list: [{ id: "main", default: true }], - }, - }; - const vectorWeighted = await getMemorySearchManager({ - cfg: vectorWeightedCfg, - agentId: "main", - }); - expect(vectorWeighted.manager).not.toBeNull(); - if (!vectorWeighted.manager) { - throw new Error("manager missing"); - } - manager = vectorWeighted.manager; - - const status = manager.status(); - if (!status.fts?.available) { - return; - } - - await manager.sync({ force: true }); - const vectorResults = await manager.search("alpha beta id123"); - expect(vectorResults.length).toBeGreaterThan(0); - const vectorPaths = vectorResults.map((r) => r.path); - expect(vectorPaths).toContain("memory/vector-only.md"); - expect(vectorPaths).toContain("memory/keyword-only.md"); - const vectorOnly = vectorResults.find((r) => r.path === "memory/vector-only.md"); - const keywordOnly = vectorResults.find((r) => r.path === "memory/keyword-only.md"); - expect((vectorOnly?.score ?? 0) > (keywordOnly?.score ?? 0)).toBe(true); - - await manager.close(); - manager = null; - - const textWeightedCfg = { - agents: { - defaults: { - workspace: workspaceDir, - memorySearch: { - provider: "openai", - model: "mock-embed", - store: { path: indexPath, vector: { enabled: false } }, - sync: { watch: false, onSessionStart: false, onSearch: false }, - query: { - minScore: 0, - maxResults: 200, - hybrid: { - enabled: true, - vectorWeight: 0.01, - textWeight: 0.99, - candidateMultiplier: 10, - }, - }, - }, - }, - list: [{ id: "main", default: true }], - }, - }; - - const textWeighted = await getMemorySearchManager({ cfg: textWeightedCfg, agentId: "main" }); - expect(textWeighted.manager).not.toBeNull(); - if (!textWeighted.manager) { - throw new Error("manager missing"); - } - manager = textWeighted.manager; - const keywordResults = await manager.search("alpha beta id123"); - expect(keywordResults.length).toBeGreaterThan(0); - const keywordPaths = keywordResults.map((r) => r.path); - expect(keywordPaths).toContain("memory/vector-only.md"); - expect(keywordPaths).toContain("memory/keyword-only.md"); - const vectorOnlyAfter = keywordResults.find((r) => r.path === "memory/vector-only.md"); - const keywordOnlyAfter = keywordResults.find((r) => r.path === "memory/keyword-only.md"); - expect((keywordOnlyAfter?.score ?? 0) > (vectorOnlyAfter?.score ?? 0)).toBe(true); - }); - it("reports vector availability after probe", async () => { const cfg = { agents: { diff --git a/src/tui/theme/theme.test.ts b/src/tui/theme/theme.test.ts index 9a9bd2d0cd..8bef3b19bb 100644 --- a/src/tui/theme/theme.test.ts +++ b/src/tui/theme/theme.test.ts @@ -1,38 +1,54 @@ -import { describe, expect, it } from "vitest"; -import { markdownTheme } from "./theme.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const cliHighlightMocks = vi.hoisted(() => ({ + highlight: vi.fn((code: string) => code), + supportsLanguage: vi.fn((_lang: string) => true), +})); + +vi.mock("cli-highlight", () => cliHighlightMocks); + +const { markdownTheme } = await import("./theme.js"); + +const stripAnsi = (str: string) => + str.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), ""); describe("markdownTheme", () => { describe("highlightCode", () => { - it("returns highlighted lines for common language inputs", () => { - const code = `const x = 42;`; - const js = markdownTheme.highlightCode!(code, "javascript"); - - expect(js).toBeInstanceOf(Array); - expect(js).toHaveLength(1); - expect(js[0]).toContain("const"); - expect(js[0]).toContain("42"); + beforeEach(() => { + cliHighlightMocks.highlight.mockReset(); + cliHighlightMocks.supportsLanguage.mockReset(); + cliHighlightMocks.highlight.mockImplementation((code: string) => code); + cliHighlightMocks.supportsLanguage.mockReturnValue(true); }); - it("handles unknown or missing language and preserves content", () => { - const code = `echo "hello"`; - const unknown = markdownTheme.highlightCode!(code, "not-a-real-language"); - const missing = markdownTheme.highlightCode!(code, undefined); - expect(unknown).toBeInstanceOf(Array); - expect(missing).toBeInstanceOf(Array); - expect(unknown).toHaveLength(1); - expect(missing).toHaveLength(1); - expect(unknown[0]).toContain("echo"); - expect(missing[0]).toContain("echo"); - const codeBlock = `const message = "Hello, World!"; -console.log(message);`; - const result = markdownTheme.highlightCode!(codeBlock, "javascript"); - const empty = markdownTheme.highlightCode!("", "javascript"); + it("passes supported language through to the highlighter", () => { + markdownTheme.highlightCode!("const x = 42;", "javascript"); + expect(cliHighlightMocks.supportsLanguage).toHaveBeenCalledWith("javascript"); + expect(cliHighlightMocks.highlight).toHaveBeenCalledWith( + "const x = 42;", + expect.objectContaining({ language: "javascript" }), + ); + }); - const stripAnsi = (str: string) => - str.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), ""); - expect(stripAnsi(result[0])).toBe(`const message = "Hello, World!";`); - expect(stripAnsi(result[1])).toBe("console.log(message);"); - expect(empty).toEqual([""]); + it("falls back to auto-detect for unknown language and preserves lines", () => { + cliHighlightMocks.supportsLanguage.mockReturnValue(false); + cliHighlightMocks.highlight.mockImplementation((code: string) => `${code}\nline-2`); + const result = markdownTheme.highlightCode!(`echo "hello"`, "not-a-real-language"); + expect(cliHighlightMocks.highlight).toHaveBeenCalledWith( + `echo "hello"`, + expect.objectContaining({ language: undefined }), + ); + expect(stripAnsi(result[0] ?? "")).toContain("echo"); + expect(stripAnsi(result[1] ?? "")).toBe("line-2"); + }); + + it("returns plain highlighted lines when highlighting throws", () => { + cliHighlightMocks.highlight.mockImplementation(() => { + throw new Error("boom"); + }); + const result = markdownTheme.highlightCode!("echo hello", "javascript"); + expect(result).toHaveLength(1); + expect(stripAnsi(result[0] ?? "")).toBe("echo hello"); }); }); }); diff --git a/src/web/media.test.ts b/src/web/media.test.ts index 83e663d098..0fa4146b45 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -10,9 +10,14 @@ import { loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg } from "./media.js"; let fixtureRoot = ""; let fixtureFileCount = 0; let largeJpegBuffer: Buffer; +let largeJpegFile = ""; let tinyPngBuffer: Buffer; +let tinyPngFile = ""; +let tinyPngWrongExtFile = ""; let alphaPngBuffer: Buffer; +let alphaPngFile = ""; let fallbackPngBuffer: Buffer; +let fallbackPngFile = ""; let fallbackPngCap = 0; async function writeTempFile(buffer: Buffer, ext: string): Promise { @@ -32,8 +37,7 @@ function buildDeterministicBytes(length: number): Buffer { } async function createLargeTestJpeg(): Promise<{ buffer: Buffer; file: string }> { - const file = await writeTempFile(largeJpegBuffer, ".jpg"); - return { buffer: largeJpegBuffer, file }; + return { buffer: largeJpegBuffer, file: largeJpegFile }; } beforeAll(async () => { @@ -48,11 +52,14 @@ beforeAll(async () => { }) .jpeg({ quality: 95 }) .toBuffer(); + largeJpegFile = await writeTempFile(largeJpegBuffer, ".jpg"); tinyPngBuffer = await sharp({ create: { width: 10, height: 10, channels: 3, background: "#00ff00" }, }) .png() .toBuffer(); + tinyPngFile = await writeTempFile(tinyPngBuffer, ".png"); + tinyPngWrongExtFile = await writeTempFile(tinyPngBuffer, ".bin"); alphaPngBuffer = await sharp({ create: { width: 64, @@ -63,11 +70,13 @@ beforeAll(async () => { }) .png() .toBuffer(); + alphaPngFile = await writeTempFile(alphaPngBuffer, ".png"); const size = 72; const raw = buildDeterministicBytes(size * size * 4); fallbackPngBuffer = await sharp(raw, { raw: { width: size, height: size, channels: 4 } }) .png() .toBuffer(); + fallbackPngFile = await writeTempFile(fallbackPngBuffer, ".png"); const smallestPng = await optimizeImageToPng(fallbackPngBuffer, 1); fallbackPngCap = Math.max(1, smallestPng.optimizedSize - 1); const jpegOptimized = await optimizeImageToJpeg(fallbackPngBuffer, fallbackPngCap); @@ -130,9 +139,7 @@ describe("web media loading", () => { }); it("sniffs mime before extension when loading local files", async () => { - const wrongExt = await writeTempFile(tinyPngBuffer, ".bin"); - - const result = await loadWebMedia(wrongExt, 1024 * 1024); + const result = await loadWebMedia(tinyPngWrongExtFile, 1024 * 1024); expect(result.kind).toBe("image"); expect(result.contentType).toBe("image/jpeg"); @@ -224,9 +231,7 @@ describe("web media loading", () => { }); it("preserves PNG alpha when under the cap", async () => { - const file = await writeTempFile(alphaPngBuffer, ".png"); - - const result = await loadWebMedia(file, 1024 * 1024); + const result = await loadWebMedia(alphaPngFile, 1024 * 1024); expect(result.kind).toBe("image"); expect(result.contentType).toBe("image/png"); @@ -235,9 +240,7 @@ describe("web media loading", () => { }); it("falls back to JPEG when PNG alpha cannot fit under cap", async () => { - const file = await writeTempFile(fallbackPngBuffer, ".png"); - - const result = await loadWebMedia(file, fallbackPngCap); + const result = await loadWebMedia(fallbackPngFile, fallbackPngCap); expect(result.kind).toBe("image"); expect(result.contentType).toBe("image/jpeg"); @@ -247,25 +250,19 @@ describe("web media loading", () => { describe("local media root guard", () => { it("rejects local paths outside allowed roots", async () => { - const file = await writeTempFile(tinyPngBuffer, ".png"); - // Explicit roots that don't contain the temp file. await expect( - loadWebMedia(file, 1024 * 1024, { localRoots: ["/nonexistent-root"] }), + loadWebMedia(tinyPngFile, 1024 * 1024, { localRoots: ["/nonexistent-root"] }), ).rejects.toThrow(/not under an allowed directory/i); }); it("allows local paths under an explicit root", async () => { - const file = await writeTempFile(tinyPngBuffer, ".png"); - - const result = await loadWebMedia(file, 1024 * 1024, { localRoots: [os.tmpdir()] }); + const result = await loadWebMedia(tinyPngFile, 1024 * 1024, { localRoots: [os.tmpdir()] }); expect(result.kind).toBe("image"); }); it("allows any path when localRoots is 'any'", async () => { - const file = await writeTempFile(tinyPngBuffer, ".png"); - - const result = await loadWebMedia(file, 1024 * 1024, { localRoots: "any" }); + const result = await loadWebMedia(tinyPngFile, 1024 * 1024, { localRoots: "any" }); expect(result.kind).toBe("image"); }); });