Excluded running async result snapshots from live partial spinner intervals so finalized background tool rows do not repaint scrollback. Added regression coverage for async bash snapshots staying static.
104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
|
|
import { stripVTControlCharacters } from "node:util";
|
|
import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import type { TUI } from "@oh-my-pi/pi-tui";
|
|
|
|
// Contract under test: live tool previews that render a pending/running status
|
|
// must keep the spinner glyph tied to the shared tool-frame ticker. This covers
|
|
// both the shared ToolExecutionComponent interval and renderer-local caches that
|
|
// would otherwise keep serving the first pending frame.
|
|
describe("ToolExecutionComponent live preview spinners", () => {
|
|
beforeAll(async () => {
|
|
await initTheme();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("animates the eval pending cell while the call is live", () => {
|
|
vi.useFakeTimers();
|
|
const requestRender = vi.fn();
|
|
const component = new ToolExecutionComponent(
|
|
"eval",
|
|
{ language: "py", code: "import time\ntime.sleep(10)" },
|
|
{},
|
|
undefined,
|
|
{ requestRender } as unknown as TUI,
|
|
process.cwd(),
|
|
);
|
|
|
|
try {
|
|
const firstFrame = stripVTControlCharacters(component.render(80).join("\n"));
|
|
vi.advanceTimersByTime(120);
|
|
const secondFrame = stripVTControlCharacters(component.render(80).join("\n"));
|
|
|
|
expect(requestRender).toHaveBeenCalled();
|
|
expect(firstFrame).toContain("time.sleep(10)");
|
|
expect(secondFrame).toContain("time.sleep(10)");
|
|
expect(secondFrame).not.toBe(firstFrame);
|
|
} finally {
|
|
component.stopAnimation();
|
|
}
|
|
});
|
|
|
|
it("animates a shell pending header while the call is live", () => {
|
|
vi.useFakeTimers();
|
|
const requestRender = vi.fn();
|
|
const component = new ToolExecutionComponent(
|
|
"ssh",
|
|
{ host: "example.test", command: "sleep 10" },
|
|
{},
|
|
undefined,
|
|
{ requestRender } as unknown as TUI,
|
|
process.cwd(),
|
|
);
|
|
|
|
try {
|
|
const firstFrame = stripVTControlCharacters(component.render(80).join("\n"));
|
|
vi.advanceTimersByTime(120);
|
|
const secondFrame = stripVTControlCharacters(component.render(80).join("\n"));
|
|
|
|
expect(requestRender).toHaveBeenCalled();
|
|
expect(firstFrame).toContain("sleep 10");
|
|
expect(secondFrame).toContain("sleep 10");
|
|
expect(secondFrame).not.toBe(firstFrame);
|
|
} finally {
|
|
component.stopAnimation();
|
|
}
|
|
});
|
|
|
|
it("does not tick detached async bash result snapshots", () => {
|
|
vi.useFakeTimers();
|
|
const requestRender = vi.fn();
|
|
const component = new ToolExecutionComponent(
|
|
"bash",
|
|
{ command: "sleep 600", async: true },
|
|
{},
|
|
undefined,
|
|
{ requestRender } as unknown as TUI,
|
|
process.cwd(),
|
|
);
|
|
|
|
try {
|
|
component.updateResult(
|
|
{
|
|
content: [{ type: "text", text: "started background job" }],
|
|
details: {
|
|
command: "sleep 600",
|
|
async: { state: "running", jobId: "job-1", type: "bash" },
|
|
},
|
|
},
|
|
true,
|
|
);
|
|
requestRender.mockClear();
|
|
vi.advanceTimersByTime(500);
|
|
expect(requestRender).not.toHaveBeenCalled();
|
|
} finally {
|
|
component.stopAnimation();
|
|
}
|
|
});
|
|
});
|