Files
oh-my-pi/packages/coding-agent/test/task/task-progress-render.test.ts
T
can1357 16f9c199da ux(coding-agent/task): adjusted running task progress output to animate descriptions
- Reworked running-task rendering so shimmer animation is applied to descriptions instead of IDs.
- Added accent coloring for the separator and description text to keep the status line formatting consistent.
2026-06-08 01:46:03 +02:00

105 lines
3.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import type { RenderResultOptions } from "@oh-my-pi/pi-agent-core";
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { getThemeByName } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import { taskToolRenderer } from "@oh-my-pi/pi-coding-agent/task/render";
import type { AgentProgress, TaskToolDetails } from "@oh-my-pi/pi-coding-agent/task/types";
function runningProgress(overrides: Partial<AgentProgress> = {}): AgentProgress {
return {
index: 0,
id: "KeySettingsHotPaths",
agent: "task",
agentSource: "bundled",
status: "running",
task: "investigate hot paths",
recentTools: [],
recentOutput: [],
toolCount: 0,
tokens: 0,
cost: 0,
durationMs: 0,
...overrides,
};
}
function detailsFor(progress: AgentProgress): TaskToolDetails {
return { projectAgentsDir: null, results: [], totalDurationMs: 0, progress: [progress] };
}
function findRow(component: { render: (w: number) => string[] }, needle: string): string {
const row = component
.render(120)
.join("\n")
.split("\n")
.find(line => Bun.stripANSI(line).includes(needle));
expect(row).toBeDefined();
return row!;
}
describe("task progress rendering", () => {
beforeEach(async () => {
resetSettingsForTest();
await Settings.init({ inMemory: true });
});
afterEach(() => {
vi.restoreAllMocks();
resetSettingsForTest();
});
it("keeps the subagent label solid and shimmers the running description", async () => {
const theme = (await getThemeByName("dark"))!;
expect(theme).toBeDefined();
const options: RenderResultOptions = { expanded: false, isPartial: true, spinnerFrame: 0 };
const progress = runningProgress({ id: "CountPackages", description: "List workspace packages" });
const renderRow = (timeMs: number): string => {
vi.spyOn(Date, "now").mockReturnValue(timeMs);
return findRow(
taskToolRenderer.renderResult(
{ content: [{ type: "text", text: "" }], details: detailsFor(progress) },
options,
theme,
),
"CountPackages",
);
};
const rawRow0 = renderRow(0);
const rawRow1 = renderRow(700);
const strippedRow = Bun.stripANSI(rawRow0);
expect(strippedRow).toContain("• CountPackages: List workspace packages");
expect(strippedRow).not.toContain(theme.status.running);
expect(strippedRow).not.toContain(theme.getSpinnerFrames("status")[0]);
// The label is one solid bold-accent run, identical across shimmer frames.
const label = theme.fg("accent", theme.bold("CountPackages"));
expect(rawRow0).toContain(label);
expect(rawRow1).toContain(label);
// The description shimmers, so the row as a whole animates between frames.
expect(rawRow0).not.toBe(rawRow1);
});
it("keeps the bullet replacement when shimmer is disabled", async () => {
const theme = (await getThemeByName("dark"))!;
resetSettingsForTest();
await Settings.init({ inMemory: true, overrides: { "display.shimmer": "disabled" } });
const options: RenderResultOptions = { expanded: false, isPartial: true, spinnerFrame: 0 };
const strippedRow = Bun.stripANSI(
findRow(
taskToolRenderer.renderResult(
{ content: [{ type: "text", text: "" }], details: detailsFor(runningProgress()) },
options,
theme,
),
"KeySettingsHotPaths",
),
);
expect(strippedRow).toContain("• KeySettingsHotPaths");
expect(strippedRow).not.toContain(theme.status.running);
expect(strippedRow).not.toContain(theme.getSpinnerFrames("status")[0]);
});
});