Files
oh-my-pi/packages/coding-agent/test/tools/find-validate-paths.test.ts
T
roboomp 8580ba248c fix(tool): handled string paths in find renderer
Guarded find renderer path summaries so raw pre-validation string paths render instead of throwing. Added coverage for pending, fallback, empty, and detailed result render paths.\n\nFixes #1622
2026-06-01 06:22:27 +00:00

94 lines
3.4 KiB
TypeScript

import { beforeAll, describe, expect, it } from "bun:test";
import type { Component } from "@oh-my-pi/pi-tui";
import type { RenderResultOptions } from "../../src/extensibility/custom-tools/types";
import { getThemeByName, initTheme, type Theme } from "../../src/modes/theme/theme";
import { findToolRenderer, validateFindPathInputs } from "../../src/tools/find";
let uiTheme: Theme;
beforeAll(async () => {
await initTheme(false, undefined, undefined, "dark", "light");
const theme = await getThemeByName("dark");
if (!theme) throw new Error("Missing dark theme");
uiTheme = theme;
});
const renderOptions: RenderResultOptions = {
expanded: false,
isPartial: true,
};
function renderText(component: Component): string {
return Bun.stripANSI(component.render(160).join("\n"));
}
describe("validateFindPathInputs", () => {
it("accepts a normal array of glob entries", () => {
expect(() => validateFindPathInputs(["src/**/*.ts", "test/**/*.ts"])).not.toThrow();
});
it('rejects comma-joined entries (the `["a,b"]` shape)', () => {
expect(() => validateFindPathInputs(["a.py,b.py"])).toThrow(/paths is an array/);
});
it("allows commas inside brace expansion", () => {
expect(() => validateFindPathInputs(["src/{a,b}/*.ts"])).not.toThrow();
expect(() => validateFindPathInputs(["{foo,bar,baz}.md"])).not.toThrow();
});
it("allows backslash-escaped commas at top level (matches search.ts:containsTopLevelComma)", () => {
// Backslash-escapes a literal comma in a filename — must not trip the
// array-vs-string heuristic.
expect(() => validateFindPathInputs(["weird\\,name.txt"])).not.toThrow();
expect(() => validateFindPathInputs(["a\\,b\\,c"])).not.toThrow();
});
it("still rejects unescaped top-level commas mixed with escaped ones", () => {
// `a\,b,c` — the second comma is unescaped, so the heuristic should fire.
expect(() => validateFindPathInputs(["a\\,b,c"])).toThrow(/paths is an array/);
});
it("allows a trailing backslash without crashing", () => {
// `foo\\` is a backslash at end-of-string; the i+1<length guard must hold.
expect(() => validateFindPathInputs(["foo\\"])).not.toThrow();
});
it("treats `\\{a,b}` as an escaped brace, so the inner comma is still top-level", () => {
// Skip-next semantics: the backslash consumes the `{`, so braceDepth stays 0
// and the unescaped `,` between `a` and `b` rejects. This pins the literal
// behavior of the new escape-skip, which intentionally does NOT model glob
// brace semantics — it only mirrors search.ts's containsTopLevelComma.
expect(() => validateFindPathInputs(["\\{a,b}"])).toThrow(/paths is an array/);
});
});
describe("findToolRenderer", () => {
it("accepts a single string paths value before validation", async () => {
const args = { paths: "src/**/*.ts" };
const renderings = [
findToolRenderer.renderCall(args, renderOptions, uiTheme),
findToolRenderer.renderResult(
{ content: [{ type: "text", text: "src/index.ts\n" }] },
renderOptions,
uiTheme,
args,
),
findToolRenderer.renderResult(
{ content: [{ type: "text", text: "" }], details: { fileCount: 0, files: [] } },
renderOptions,
uiTheme,
args,
),
findToolRenderer.renderResult(
{ content: [{ type: "text", text: "src/index.ts" }], details: { fileCount: 1, files: ["src/index.ts"] } },
renderOptions,
uiTheme,
args,
),
];
for (const component of renderings) {
expect(renderText(component)).toContain("src/**/*.ts");
}
});
});