100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { classifyTask } from "./task-classify";
|
|
import { statePriority, sortByStateThenCreated, type TaskSwitcherItem } from "./task-switcher";
|
|
|
|
const EARLY = "2026-01-01T00:00:00Z";
|
|
|
|
function task(overrides: Partial<TaskSwitcherItem>): TaskSwitcherItem {
|
|
return { id: "t", title: "t", ...overrides };
|
|
}
|
|
|
|
describe("classifyTask", () => {
|
|
it("buckets WAITING_FOR_INPUT as review", () => {
|
|
expect(classifyTask("WAITING_FOR_INPUT")).toBe("review");
|
|
});
|
|
|
|
it("buckets RUNNING as in_progress", () => {
|
|
expect(classifyTask("RUNNING")).toBe("in_progress");
|
|
});
|
|
|
|
it("buckets STARTING with REVIEW task state as review", () => {
|
|
expect(classifyTask("STARTING", "REVIEW")).toBe("review");
|
|
});
|
|
});
|
|
|
|
describe("sortByStateThenCreated (regression: silent resume reorder)", () => {
|
|
it("sorts review (turn finished) above in_progress (running)", () => {
|
|
// Regression: when a task is auto-resumed after backend restart, its
|
|
// session transiently cycles WAITING_FOR_INPUT -> STARTING -> RUNNING ->
|
|
// WAITING_FOR_INPUT. The sidebar must NOT promote it above other
|
|
// turn-finished tasks during the RUNNING flicker.
|
|
const running = task({
|
|
id: "running",
|
|
sessionState: "RUNNING",
|
|
createdAt: EARLY,
|
|
});
|
|
const waiting = task({
|
|
id: "waiting",
|
|
sessionState: "WAITING_FOR_INPUT",
|
|
createdAt: EARLY,
|
|
});
|
|
|
|
expect(statePriority(waiting)).toBeLessThan(statePriority(running));
|
|
expect([running, waiting].sort(sortByStateThenCreated).map((t) => t.id)).toEqual([
|
|
"waiting",
|
|
"running",
|
|
]);
|
|
});
|
|
|
|
it("does not move a transiently-RUNNING task above older turn-finished tasks", () => {
|
|
// Older turn-finished task A and a newer turn-finished task B that gets
|
|
// resumed (so its sessionState briefly flips to RUNNING). With the
|
|
// pre-fix priority (in_progress=0), B would jump to the top. With the
|
|
// fix (review=0), B stays in its original position relative to A.
|
|
const a = task({
|
|
id: "a-old-waiting",
|
|
sessionState: "WAITING_FOR_INPUT",
|
|
createdAt: EARLY,
|
|
});
|
|
const b = task({
|
|
id: "b-newer-resuming",
|
|
sessionState: "RUNNING",
|
|
createdAt: "2026-01-02T00:00:00Z",
|
|
});
|
|
|
|
const sorted = [a, b].sort(sortByStateThenCreated).map((t) => t.id);
|
|
expect(sorted[0]).toBe("a-old-waiting");
|
|
expect(sorted[1]).toBe("b-newer-resuming");
|
|
});
|
|
|
|
it("orders backlog after both review and in_progress", () => {
|
|
const backlog = task({ id: "bk", sessionState: undefined });
|
|
const running = task({ id: "ru", sessionState: "RUNNING" });
|
|
const review = task({ id: "rv", sessionState: "WAITING_FOR_INPUT" });
|
|
|
|
expect([backlog, running, review].sort(sortByStateThenCreated).map((t) => t.id)).toEqual([
|
|
"rv",
|
|
"ru",
|
|
"bk",
|
|
]);
|
|
});
|
|
|
|
it("breaks ties within a bucket by createdAt descending", () => {
|
|
const older = task({
|
|
id: "older",
|
|
sessionState: "WAITING_FOR_INPUT",
|
|
createdAt: EARLY,
|
|
});
|
|
const newer = task({
|
|
id: "newer",
|
|
sessionState: "WAITING_FOR_INPUT",
|
|
createdAt: "2026-02-01T00:00:00Z",
|
|
});
|
|
|
|
expect([older, newer].sort(sortByStateThenCreated).map((t) => t.id)).toEqual([
|
|
"newer",
|
|
"older",
|
|
]);
|
|
});
|
|
});
|