Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/footer/__tests__/harness.test.ts

Raw
import path from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { visibleWidth } from "@earendil-works/pi-tui";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
import footerExtension, { __test } from "../index.ts";

describe("footer pi-test-harness", () => {
	let t: TestSession | undefined;

	afterEach(() => {
		vi.restoreAllMocks();
		t?.dispose();
		t = undefined;
	});

	async function mountTui(session: TestSession): Promise<void> {
		const runner = session.session.extensionRunner;
		runner.setUIContext(runner.getUIContext(), "tui");
		await runner.emit({ type: "session_start", reason: "startup" });
	}

	it("loads in real Pi extension runtime", async () => {
		const extensionPath = path.resolve(import.meta.dirname, "../index.ts");
		t = await createTestSession({ extensions: [extensionPath] });

		expect(t.session.extensionRunner.extensions).toHaveLength(1);
		expect(t.session.extensionRunner.extensions[0].path).toBe(extensionPath);
	});

	it("acknowledges and retains pre-mount updates without polling", async () => {
		let accepted = 0;
		let offer: ((id: string) => void) | undefined;
		const producer = (pi: ExtensionAPI) => {
			offer = (id) =>
				pi.events.emit("footer:segment", {
					id,
					text: "ready",
					zone: "llm",
					accept: () => accepted++,
				});
			offer("early");
		};
		const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
		t = await createTestSession({
			extensionFactories: [footerExtension, producer],
		});

		expect(accepted).toBe(1);
		expect(t.events.uiCallsFor("setFooter")).toHaveLength(0);

		const runner = t.session.extensionRunner;
		runner.setUIContext(runner.getUIContext(), "rpc");
		await runner.emit({ type: "session_start", reason: "startup" });
		expect(t.events.uiCallsFor("setFooter")).toHaveLength(0);
		offer?.("rpc");
		expect(accepted).toBe(1);

		await mountTui(t);
		expect(accepted).toBe(1);
		expect(setTimeoutSpy.mock.calls.some(([, delay]) => delay === 50)).toBe(
			false,
		);
		setTimeoutSpy.mockRestore();

		const factory = t.events.uiCallsFor("setFooter").at(-1)?.args[0] as
			| ((
					tui: { requestRender(): void },
					theme: { fg(style: string, text: string): string },
					data: {
						onBranchChange(callback: () => void): () => void;
						getExtensionStatuses(): ReadonlyMap<string, string>;
					},
			  ) => { render(width: number): string[] })
			| undefined;
		const component = factory?.(
			{ requestRender() {} },
			{ fg: (_style, text) => text },
			{
				onBranchChange: () => () => {},
				getExtensionStatuses: () => new Map(),
			},
		);
		expect(component?.render(80).join("\n")).toContain("ready");
	});

	it("isolates acknowledgements from a nested non-TUI runtime", async () => {
		let parentAccepted = 0;
		const producer = (pi: ExtensionAPI) => {
			pi.events.emit("footer:segment", {
				id: "parent",
				text: "ready",
				zone: "llm",
				accept: () => parentAccepted++,
			});
		};
		t = await createTestSession({
			extensionFactories: [footerExtension, producer],
		});
		await mountTui(t);
		expect(parentAccepted).toBe(1);

		const nested = await createTestSession({
			extensionFactories: [footerExtension],
		});
		try {
			const runner = nested.session.extensionRunner;
			runner.setUIContext(runner.getUIContext(), "rpc");
			await runner.emit({ type: "session_start", reason: "startup" });
			await runner.emit({ type: "session_shutdown", reason: "quit" });
			expect(parentAccepted).toBe(1);
		} finally {
			nested.dispose();
		}
	});

	it("keeps valid segments when a producer emits malformed data", async () => {
		let emitSegment: ((segment: unknown) => void) | undefined;
		const producer = (pi: ExtensionAPI) => {
			emitSegment = (segment) => pi.events.emit("footer:segment", segment);
		};
		t = await createTestSession({
			extensionFactories: [footerExtension, producer],
		});
		await mountTui(t);
		const factory = t.events.uiCallsFor("setFooter").at(-1)?.args[0] as
			| ((
					tui: { requestRender(): void },
					theme: { fg(style: string, text: string): string },
					data: {
						onBranchChange(callback: () => void): () => void;
						getExtensionStatuses(): ReadonlyMap<string, string>;
					},
			  ) => { render(width: number): string[] })
			| undefined;
		const component = factory?.(
			{ requestRender() {} },
			{
				fg: (style, text) => {
					if (style === "not-a-theme-color") throw new Error("bad color");
					return text;
				},
			},
			{
				onBranchChange: () => () => {},
				getExtensionStatuses: () => new Map(),
			},
		);

		let accepted = 0;
		emitSegment?.({
			id: "good",
			text: "healthy",
			zone: "llm",
			accept: () => accepted++,
		});
		emitSegment?.({
			id: "good",
			text: 42,
			zone: "llm",
			accept: () => accepted++,
		});
		expect(accepted).toBe(1);
		emitSegment?.({
			id: "good",
			text: "poison",
			zone: "llm",
			color: "not-a-theme-color",
			accept: () => accepted++,
		});
		expect(accepted).toBe(1);
		expect(component?.render(80).join("\n")).toContain("healthy");
		expect(component?.render(80).join("\n")).not.toContain("poison");
	});

	it("keeps later LLM components visible as earlier values grow", async () => {
		let emitSegment: ((segment: unknown) => void) | undefined;
		const producer = (pi: ExtensionAPI) => {
			emitSegment = (segment) => pi.events.emit("footer:segment", segment);
		};
		t = await createTestSession({
			extensionFactories: [footerExtension, producer],
		});
		await mountTui(t);

		const factory = t.events.uiCallsFor("setFooter").at(-1)?.args[0] as
			| ((
					tui: { requestRender(): void },
					theme: { fg(style: string, text: string): string },
					data: {
						onBranchChange(callback: () => void): () => void;
						getExtensionStatuses(): ReadonlyMap<string, string>;
					},
			  ) => {
					render(width: number): string[];
					dispose(): void;
			  })
			| undefined;
		expect(factory).toBeTypeOf("function");
		const component = factory?.(
			{ requestRender() {} },
			{ fg: (_style, text) => text },
			{
				onBranchChange: () => () => {},
				getExtensionStatuses: () => new Map(),
			},
		);
		expect(component).toBeDefined();

		emitSegment?.({ id: "model", text: "sol:medium", zone: "llm", order: 0 });
		emitSegment?.({ id: "tokens", text: "↑0", zone: "llm", order: 2 });
		emitSegment?.({ id: "tps", text: "50.0/s", zone: "llm", order: 4 });
		const initial = component?.render(32) ?? [];
		expect(initial).toHaveLength(2);
		expect(initial.join("\n")).toContain("50.0/s");

		emitSegment?.({
			id: "tokens",
			text: "↑12k ↓3.4k",
			zone: "llm",
			order: 2,
		});
		const grown = component?.render(32) ?? [];
		expect(grown).toHaveLength(3);
		expect(grown.join("\n")).toContain("50.0/s");
		expect(grown.every((line) => visibleWidth(line) <= 32)).toBe(true);

		emitSegment?.({ id: "tokens", text: "↑0", zone: "llm", order: 2 });
		const shrunk = component?.render(32) ?? [];
		expect(shrunk).toHaveLength(2);
		expect(shrunk.join("\n")).toContain("50.0/s");

		emitSegment?.({
			id: "provider",
			text: "provider",
			zone: "llm",
			group: "model-route",
			order: -0.5,
		});
		emitSegment?.({
			id: "model",
			text: "sol:medium",
			zone: "llm",
			group: "model-route",
			gap: 2,
			order: 0,
		});
		expect(component?.render(80).join("\n")).toContain("provider  sol:medium");
		component?.dispose();
	});

	it.each([
		[true, "tui", true],
		[true, "rpc", false],
		[false, "json", false],
		[false, "print", false],
		[false, "tui", false],
	] as const)(
		"requires UI and TUI mode when hasUI=%s and mode=%s",
		(hasUI, mode, expected) => {
			expect(__test.canRenderFooter({ hasUI, mode })).toBe(expected);
		},
	);

	it("keeps equal-priority order stable after removal and reinsertion", () => {
		const quota = {
			id: "quota:anthropic",
			text: "quota",
			zone: "llm",
			order: 4,
		} as const;
		const tps = {
			id: "tps",
			text: "tps",
			zone: "llm",
			order: 4,
		} as const;
		const segments = new Map([
			[tps.id, tps],
			[quota.id, quota],
		]);
		segments.delete(tps.id);
		segments.set(tps.id, tps);

		expect([...segments.values()].sort(__test.compareSegments)).toEqual([
			quota,
			tps,
		]);
	});

	it("renders nothing and schedules no retries when hidden", async () => {
		let emitSegment: ((segment: unknown) => void) | undefined;
		const producer = (pi: ExtensionAPI) => {
			emitSegment = (segment) => pi.events.emit("footer:segment", segment);
		};
		t = await createTestSession({
			extensionFactories: [footerExtension, producer],
		});
		await mountTui(t);
		const runner = t.session.extensionRunner;
		await runner.extensions[0].commands
			.get("footer")
			.handler("", runner.createContext());
		const factory = t.events.uiCallsFor("setFooter").at(-1)?.args[0] as
			| ((tui: { requestRender(force?: boolean): void }) => {
					render(width: number): string[];
			  })
			| undefined;
		expect(factory?.({ requestRender() {} }).render(80)).toEqual([]);

		const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
		emitSegment?.({ id: "tps", text: "50/s", zone: "llm" });
		expect(setTimeoutSpy).not.toHaveBeenCalled();
		setTimeoutSpy.mockRestore();
	});

	it("right-aligns statuses beside the workspace row", () => {
		expect(__test.alignLeftAndRight("workspace", "busy", 16)).toBe(
			"workspace   busy",
		);
	});

	it("preserves the workspace row when statuses exceed the remaining space", () => {
		const line = __test.alignLeftAndRight("workspace", "very long status", 12);

		expect(visibleWidth(line)).toBe(12);
		expect(line.startsWith("workspace ")).toBe(true);
	});

	it("uses the first row when its first group is oversized", () => {
		const [first, second] = __test.flowLines(
			["oversized-model-route", "tokens"],
			" | ",
			8,
		);

		expect(first).not.toBe("");
		expect(second).toBe("tokens");
	});

	it("keeps adjacent members of a named segment group atomic", () => {
		const model = { id: "model", text: "sol:medium", zone: "llm" } as const;
		const fast = {
			id: "fast",
			text: "",
			zone: "llm",
			group: "model-route",
		} as const;
		const angel = {
			id: "angel",
			text: "astra:max",
			zone: "llm",
			group: "model-route",
		} as const;
		const tokens = { id: "tokens", text: "1k", zone: "llm" } as const;

		expect(
			__test.groupSegments([
				{ ...model, group: "model-route" },
				fast,
				angel,
				tokens,
			]),
		).toEqual([[{ ...model, group: "model-route" }, fast, angel], [tokens]]);
	});
});