import { Container } from "@mariozechner/pi-tui"; import { beforeAll, describe, expect, test, vi } from "vitest"; import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js"; import { initTheme } from "../src/modes/interactive/theme/theme.js"; function renderLastLine(container: Container, width = 120): string { const last = container.children[container.children.length - 1]; if (!last) return ""; return last.render(width).join("\n"); } function renderAll(container: Container, width = 120): string { return container.children.flatMap((child) => child.render(width)).join("\n"); } describe("InteractiveMode.showStatus", () => { beforeAll(() => { // showStatus uses the global theme instance initTheme("dark"); }); test("coalesces immediately-sequential status messages", () => { const fakeThis: any = { chatContainer: new Container(), ui: { requestRender: vi.fn() }, lastStatusSpacer: undefined, lastStatusText: undefined, }; (InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_ONE"); expect(fakeThis.chatContainer.children).toHaveLength(2); expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_ONE"); (InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_TWO"); // second status updates the previous line instead of appending expect(fakeThis.chatContainer.children).toHaveLength(2); expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO"); expect(renderLastLine(fakeThis.chatContainer)).not.toContain("STATUS_ONE"); }); test("appends a new status line if something else was added in between", () => { const fakeThis: any = { chatContainer: new Container(), ui: { requestRender: vi.fn() }, lastStatusSpacer: undefined, lastStatusText: undefined, }; (InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_ONE"); expect(fakeThis.chatContainer.children).toHaveLength(2); // Something else gets added to the chat in between status updates fakeThis.chatContainer.addChild({ render: () => ["OTHER"], invalidate: () => {} }); expect(fakeThis.chatContainer.children).toHaveLength(3); (InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_TWO"); // adds spacer + text expect(fakeThis.chatContainer.children).toHaveLength(5); expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO"); }); }); describe("InteractiveMode.showLoadedResources", () => { beforeAll(() => { initTheme("dark"); }); function createShowLoadedResourcesThis(options: { quietStartup: boolean; verbose?: boolean; skills?: Array<{ filePath: string }>; skillDiagnostics?: Array<{ type: "warning" | "error" | "collision"; message: string }>; }) { const fakeThis: any = { options: { verbose: options.verbose ?? false }, chatContainer: new Container(), settingsManager: { getQuietStartup: () => options.quietStartup, }, session: { promptTemplates: [], extensionRunner: undefined, resourceLoader: { getPathMetadata: () => new Map(), getAgentsFiles: () => ({ agentsFiles: [] }), getSkills: () => ({ skills: options.skills ?? [], diagnostics: options.skillDiagnostics ?? [], }), getPrompts: () => ({ prompts: [], diagnostics: [] }), getExtensions: () => ({ errors: [] }), getThemes: () => ({ themes: [], diagnostics: [] }), }, }, formatDisplayPath: (p: string) => p, buildScopeGroups: () => [], formatScopeGroups: () => "resource-list", getShortPath: (p: string) => p, formatDiagnostics: () => "diagnostics", }; return fakeThis; } test("does not show verbose listing on quiet startup during reload", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: true, skills: [{ filePath: "/tmp/skill/SKILL.md" }], }); (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { extensionPaths: ["/tmp/ext/index.ts"], force: false, showDiagnosticsWhenQuiet: true, }); expect(fakeThis.chatContainer.children).toHaveLength(0); }); test("still shows diagnostics on quiet startup when requested", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: true, skills: [{ filePath: "/tmp/skill/SKILL.md" }], skillDiagnostics: [{ type: "warning", message: "duplicate skill name" }], }); (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { force: false, showDiagnosticsWhenQuiet: true, }); const output = renderAll(fakeThis.chatContainer); expect(output).toContain("[Skill conflicts]"); expect(output).not.toContain("[Skills]"); }); });