mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 08:54:42 +00:00
* Keep relative timestamps current in agent, schedule, and import-session rows Agent list rows, schedule rows, and import-session rows formatted their "5m ago" labels once at render, so an idle row kept its first label until something else re-rendered it. They now use useTimeAgo, which rides the same shared ticker the sidebar and desktop tabs already use. Closes #4625 * Say "just now" for the whole first minute so fresh labels stay honest The shared ticker wakes once a minute, so a seconds label could only go stale. The prose and compact formatters now share one age description, and the schedule spec anchors its clock to the schedule's creation time. * Run every cleanup in the schedule relative-time spec and name its steps * Keep relative labels current with focused subscriptions * Update usage card after relative-time hook rename * Anchor import-session age spec to fixture time --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { expect, test, type Page } from "../support/fixtures";
|
|
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
|
import { buildSchedulesRoute } from "../../src/utils/host-routes";
|
|
|
|
interface ScheduleSeedClient {
|
|
scheduleCreate(input: {
|
|
prompt: string;
|
|
name?: string;
|
|
cadence: { type: "cron"; expression: string };
|
|
target: {
|
|
type: "new-agent";
|
|
config: {
|
|
provider: "mock";
|
|
cwd: string;
|
|
model: string;
|
|
modeId: string;
|
|
title: string;
|
|
};
|
|
};
|
|
runOnCreate: boolean;
|
|
}): Promise<{ schedule: { id: string; createdAt: string } | null; error: string | null }>;
|
|
schedulePause(input: { id: string }): Promise<{ error: string | null }>;
|
|
scheduleDelete(input: { id: string }): Promise<{ error: string | null }>;
|
|
}
|
|
|
|
interface SeededSchedule {
|
|
id: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
async function seedPausedSchedule(
|
|
workspace: SeededWorkspace,
|
|
name: string,
|
|
): Promise<SeededSchedule> {
|
|
const client = workspace.client as unknown as ScheduleSeedClient;
|
|
const result = await client.scheduleCreate({
|
|
prompt: "Say hello from the scheduled agent.",
|
|
name,
|
|
cadence: { type: "cron", expression: "0 9 * * *" },
|
|
target: {
|
|
type: "new-agent",
|
|
config: {
|
|
provider: "mock",
|
|
cwd: workspace.repoPath,
|
|
model: "ten-second-stream",
|
|
modeId: "load-test",
|
|
title: name,
|
|
},
|
|
},
|
|
runOnCreate: false,
|
|
});
|
|
if (!result.schedule) {
|
|
throw new Error(result.error ?? "Failed to seed schedule");
|
|
}
|
|
const { id, createdAt } = result.schedule;
|
|
const paused = await client.schedulePause({ id });
|
|
if (paused.error) {
|
|
throw new Error(paused.error);
|
|
}
|
|
return { id, createdAt: Date.parse(createdAt) };
|
|
}
|
|
|
|
async function deleteSeededSchedule(workspace: SeededWorkspace, id: string): Promise<void> {
|
|
const client = workspace.client as unknown as ScheduleSeedClient;
|
|
const result = await client.scheduleDelete({ id });
|
|
if (result.error) {
|
|
throw new Error(result.error);
|
|
}
|
|
}
|
|
|
|
/** Opens Schedules with the browser clock frozen at `time`, so the page cannot age it. */
|
|
async function openSchedulesAt(page: Page, time: number): Promise<void> {
|
|
await page.clock.install({ time });
|
|
await page.goto(buildSchedulesRoute());
|
|
}
|
|
|
|
async function letTimePass(page: Page, duration: string): Promise<void> {
|
|
await page.clock.fastForward(duration);
|
|
}
|
|
|
|
async function expectScheduleRowText(page: Page, scheduleId: string, text: string): Promise<void> {
|
|
await expect(page.getByTestId(`schedule-row-${scheduleId}`)).toContainText(text, {
|
|
timeout: 30_000,
|
|
});
|
|
}
|
|
|
|
test.describe("Schedule relative timestamps", () => {
|
|
const cleanupTasks: Array<() => Promise<void>> = [];
|
|
|
|
test.afterEach(async () => {
|
|
const failures: unknown[] = [];
|
|
for (const cleanup of cleanupTasks.toReversed()) {
|
|
try {
|
|
await cleanup();
|
|
} catch (error) {
|
|
failures.push(error);
|
|
}
|
|
}
|
|
cleanupTasks.length = 0;
|
|
if (failures.length > 0) {
|
|
throw new AggregateError(failures, "Test cleanup failed");
|
|
}
|
|
});
|
|
|
|
test("an idle schedule row keeps its created age current", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "schedule-relative-time-", git: false });
|
|
cleanupTasks.push(() => workspace.cleanup());
|
|
const schedule = await seedPausedSchedule(workspace, `Relative time ${Date.now()}`);
|
|
cleanupTasks.push(() => deleteSeededSchedule(workspace, schedule.id));
|
|
|
|
await openSchedulesAt(page, schedule.createdAt);
|
|
await expectScheduleRowText(page, schedule.id, "Created just now");
|
|
|
|
await letTimePass(page, "03:00");
|
|
|
|
await expectScheduleRowText(page, schedule.id, "Created 3m ago");
|
|
});
|
|
});
|