import { vi } from "vitest"; import Logger from "~/utils/Logger"; import { isModelContextSupported, registerModelContextTool, type ModelContextTool, } from "./ModelContext"; const makeTool = (name: string): ModelContextTool => ({ name, description: "A test tool", execute: async () => ({ content: [{ type: "text", text: "ok" }] }), }); describe("ModelContext", () => { afterEach(() => { delete window.document.modelContext; }); describe("#isModelContextSupported", () => { it("should return false when the API is unavailable", () => { expect(isModelContextSupported()).toBe(false); }); it("should return true when the API is available", () => { window.document.modelContext = { registerTool: vi.fn() }; expect(isModelContextSupported()).toBe(true); }); }); describe("#registerModelContextTool", () => { it("should return false when the API is unavailable", () => { const controller = new AbortController(); expect(registerModelContextTool(makeTool("one"), controller.signal)).toBe( false ); }); it("should register a tool and skip duplicate names", () => { const registerTool = vi.fn(); window.document.modelContext = { registerTool }; const controller = new AbortController(); expect(registerModelContextTool(makeTool("two"), controller.signal)).toBe( true ); expect(registerModelContextTool(makeTool("two"), controller.signal)).toBe( false ); expect(registerTool).toHaveBeenCalledTimes(1); controller.abort(); }); it("should allow re-registration after the signal aborts", () => { const registerTool = vi.fn(); window.document.modelContext = { registerTool }; const first = new AbortController(); expect(registerModelContextTool(makeTool("three"), first.signal)).toBe( true ); first.abort(); const second = new AbortController(); expect(registerModelContextTool(makeTool("three"), second.signal)).toBe( true ); expect(registerTool).toHaveBeenCalledTimes(2); second.abort(); }); it("should free the name when async registration rejects", async () => { const registerTool = vi.fn().mockRejectedValue(new Error("rejected")); window.document.modelContext = { registerTool }; const first = new AbortController(); expect(registerModelContextTool(makeTool("five"), first.signal)).toBe( true ); await new Promise((resolve) => setTimeout(resolve, 0)); const second = new AbortController(); expect(registerModelContextTool(makeTool("five"), second.signal)).toBe( true ); second.abort(); }); it("should not warn when a pending registration is aborted", async () => { const warn = vi.spyOn(Logger, "warn").mockImplementation(() => {}); const registerTool = vi .fn() .mockRejectedValue( new DOMException("signal is aborted without reason", "AbortError") ); window.document.modelContext = { registerTool }; const controller = new AbortController(); expect(registerModelContextTool(makeTool("six"), controller.signal)).toBe( true ); controller.abort(); await new Promise((resolve) => setTimeout(resolve, 0)); expect(warn).not.toHaveBeenCalled(); warn.mockRestore(); }); it("should not register when the signal is already aborted", () => { const registerTool = vi.fn(); window.document.modelContext = { registerTool }; const controller = new AbortController(); controller.abort(); expect( registerModelContextTool(makeTool("four"), controller.signal) ).toBe(false); expect(registerTool).not.toHaveBeenCalled(); }); }); });