Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ephemlets/__tests__/tool-renderer.test.ts

Raw
import {
	type AgentToolResult,
	initTheme,
	type Theme,
} from "@earendil-works/pi-coding-agent";
import type { Component } from "@earendil-works/pi-tui";
import { beforeAll, describe, expect, it } from "vitest";
import { ephemletRenderers } from "../src/tool-renderer.ts";

beforeAll(() => initTheme("dark", false));

const theme = {
	fg: (_color: string, text: string) => text,
	bold: (text: string) => text,
} as Theme;
const source = "private-source-body";
const state = { selected: "private-state-body" };
const record = {
	id: "stable-id",
	title: "Counter",
	kind: "interactive",
	status: "ready",
	revision: 4,
	source,
	state,
	jobId: null,
	error: null,
};

function context(
	expanded: boolean,
	lastComponent?: Component,
): Parameters<NonNullable<typeof ephemletRenderers.renderCall>>[2] {
	return {
		args: {},
		toolCallId: "row",
		invalidate() {},
		lastComponent,
		state: undefined,
		cwd: "/fixture",
		executionStarted: true,
		argsComplete: true,
		isPartial: false,
		expanded,
		showImages: false,
		isError: false,
		durationMs: undefined,
		outputPad: 0,
	};
}
function output(component: Component): string {
	return component.render(120).join("\n");
}
function result(details: unknown): AgentToolResult<unknown> {
	return {
		content: [{ type: "text", text: "private-fallback-body" }],
		details,
	};
}

describe("native ephemlet tool rows", () => {
	it.each(["put", "build", "get", "state", "list", "delete", "show", "hide"])(
		"keeps %s calls compact, expands args, and reuses the native component",
		(action) => {
			const args = {
				action,
				id: "stable-id",
				source,
				state,
				brief: "private-brief-body",
			};
			const collapsed = ephemletRenderers.renderCall!(
				args,
				theme,
				context(false),
			);
			expect(output(collapsed)).toContain(`ephemlet ${action}`);
			expect(output(collapsed)).toContain("to expand");
			for (const hidden of [source, state.selected, args.brief])
				expect(output(collapsed)).not.toContain(hidden);
			const expanded = ephemletRenderers.renderCall!(
				args,
				theme,
				context(true, collapsed),
			);
			expect(expanded).toBe(collapsed);
			for (const visible of [source, state.selected, args.brief])
				expect(output(expanded)).toContain(visible);
			const folded = ephemletRenderers.renderCall!(
				args,
				theme,
				context(false, expanded),
			);
			expect(output(folded)).not.toContain(source);
		},
	);
	it.each([
		[record, "ready"],
		[{ ...record, status: "building", jobId: "job-id" }, "building"],
		[{ ...record, status: "failed", error: "private-error-body" }, "failed"],
		[{ ephemlets: [record], guidance: "/private-guidance-path" }, "1 ephemlet"],
		[{ deleted: "stable-id" }, "deleted stable-id"],
		[{ visible: false, selected: "stable-id" }, "hidden"],
		[{ visible: true, selected: "stable-id" }, "shown"],
	] as const)("keeps result bodies behind native expansion", (data, status) => {
		const collapsed = ephemletRenderers.renderResult!(
			result(data),
			{ expanded: false, isPartial: false },
			theme,
			context(false),
		);
		expect(output(collapsed)).toContain(status);
		expect(output(collapsed)).toContain("to expand");
		for (const hidden of [
			source,
			state.selected,
			"private-error-body",
			"/private-guidance-path",
		])
			expect(output(collapsed)).not.toContain(hidden);
		const expanded = ephemletRenderers.renderResult!(
			result(data),
			{ expanded: true, isPartial: false },
			theme,
			context(true, collapsed),
		);
		expect(expanded).toBe(collapsed);
		expect(output(expanded)).toContain("stable-id");
		if ("source" in data || "ephemlets" in data) {
			expect(output(expanded)).toContain(source);
			expect(output(expanded)).toContain(state.selected);
		}
	});
	it.each([
		undefined,
		null,
		42,
		"private-malformed-body",
		[source],
		{ unknown: source },
	])("honors expansion for malformed details %s", (data) => {
		const collapsed = ephemletRenderers.renderResult!(
			result(data),
			{ expanded: false, isPartial: false },
			theme,
			context(false),
		);
		expect(output(collapsed)).toContain("finished");
		expect(output(collapsed)).toContain("to expand");
		expect(output(collapsed)).not.toContain("private-");
		const expanded = ephemletRenderers.renderResult!(
			result(data),
			{ expanded: true, isPartial: false },
			theme,
			context(true),
		);
		const expected =
			data === undefined || data === null
				? "private-fallback-body"
				: typeof data === "string" || typeof data === "number"
					? String(data)
					: source;
		expect(output(expanded)).toContain(expected);
	});
	it("honors expansion for progress and errors, including empty error details", () => {
		const progress = ephemletRenderers.renderResult!(
			result(record),
			{ expanded: false, isPartial: true },
			theme,
			context(false),
		);
		expect(output(progress)).toContain("working");
		expect(output(progress)).not.toContain(source);
		const expandedProgress = ephemletRenderers.renderResult!(
			result(record),
			{ expanded: true, isPartial: true },
			theme,
			context(true),
		);
		expect(output(expandedProgress)).toContain(source);
		const collapsedErrorContext = context(false);
		collapsedErrorContext.isError = true;
		const error = ephemletRenderers.renderResult!(
			result({}),
			{ expanded: false, isPartial: false },
			theme,
			collapsedErrorContext,
		);
		expect(output(error)).toContain("failed");
		expect(output(error)).not.toContain("private-fallback-body");
		const expandedErrorContext = context(true, error);
		expandedErrorContext.isError = true;
		const expandedError = ephemletRenderers.renderResult!(
			result({}),
			{ expanded: true, isPartial: false },
			theme,
			expandedErrorContext,
		);
		expect(output(expandedError)).toContain("private-fallback-body");
	});
	it("bounds expanded previews and escapes controls without invoking custom inspectors", () => {
		let invoked = false;
		const data = {
			source: "x".repeat(128 * 1024),
			state,
			title: "Counter\x1b[2J\n",
			[Symbol.for("nodejs.util.inspect.custom")]() {
				invoked = true;
				return "untrusted";
			},
		};
		const expanded = ephemletRenderers.renderResult!(
			result(data),
			{ expanded: true, isPartial: false },
			theme,
			context(true),
		);
		const text = output(expanded);
		expect(text.length).toBeLessThan(18000);
		expect(text).toContain("truncated");
		expect(text).not.toContain("\x1b");
		expect(invoked).toBe(false);
		const cyclic: Record<string, unknown> = {};
		cyclic["self"] = cyclic;
		expect(() =>
			ephemletRenderers.renderResult!(
				result(cyclic),
				{ expanded: true, isPartial: false },
				theme,
				context(true),
			),
		).not.toThrow();
	});
});