Files
fusion/packages/dashboard/app/utils/__tests__/todoAging.test.ts
Fusion 32e616c6bd feat(FN-4316): complete Step 1 — add todo aging utility
Fusion-Task-Id: FN-4316
Fusion-Task-Lineage: 6b2b78b4-effa-479c-9a22-5a53d0f74894
2026-05-13 18:14:22 -07:00

103 lines
3.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { Task } from "@fusion/core";
import {
TODO_AGING_THRESHOLDS_MS,
getTodoAgeBucket,
getTodoAgeMs,
summarizeTodoAging,
} from "../todoAging";
const createTask = (overrides: Partial<Task> = {}): Task =>
({
id: "FN-001",
description: "Test task",
column: "todo",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00Z",
updatedAt: "2026-01-01T00:00:00Z",
columnMovedAt: "2026-01-01T00:00:00Z",
...overrides,
}) as Task;
describe("todoAging", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-04T12:00:00Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it.each([
{ ageMs: 0, expected: "fresh" },
{ ageMs: TODO_AGING_THRESHOLDS_MS.aging - 1, expected: "fresh" },
{ ageMs: TODO_AGING_THRESHOLDS_MS.aging, expected: "fresh" },
{ ageMs: TODO_AGING_THRESHOLDS_MS.aging + 1, expected: "aging" },
{ ageMs: TODO_AGING_THRESHOLDS_MS.stale, expected: "aging" },
{ ageMs: TODO_AGING_THRESHOLDS_MS.stale + 1, expected: "stale" },
{ ageMs: 90 * 24 * 60 * 60 * 1000, expected: "stale" },
])("maps age $ageMs to $expected", ({ ageMs, expected }) => {
const task = createTask({ columnMovedAt: new Date(Date.now() - ageMs).toISOString() });
expect(getTodoAgeBucket(task)).toBe(expected);
});
it("returns undefined for non-todo tasks", () => {
expect(getTodoAgeBucket(createTask({ column: "in-progress" }))).toBeUndefined();
});
it("falls back from columnMovedAt to createdAt to updatedAt", () => {
const createdFallback = createTask({
columnMovedAt: undefined,
createdAt: new Date(Date.now() - (TODO_AGING_THRESHOLDS_MS.aging + 1)).toISOString(),
updatedAt: new Date(Date.now() - 1000).toISOString(),
});
expect(getTodoAgeBucket(createdFallback)).toBe("aging");
const updatedFallback = createTask({
columnMovedAt: undefined,
createdAt: "",
updatedAt: new Date(Date.now() - (TODO_AGING_THRESHOLDS_MS.stale + 1)).toISOString(),
});
expect(getTodoAgeBucket(updatedFallback)).toBe("stale");
const missingAll = createTask({ columnMovedAt: undefined, createdAt: "", updatedAt: "" });
expect(getTodoAgeBucket(missingAll)).toBeUndefined();
});
it("summarizeTodoAging counts only todo tasks and returns total", () => {
const tasks = [
createTask({ id: "FN-1", columnMovedAt: new Date(Date.now() - 1000).toISOString() }),
createTask({ id: "FN-2", columnMovedAt: new Date(Date.now() - (TODO_AGING_THRESHOLDS_MS.aging + 1)).toISOString() }),
createTask({ id: "FN-3", columnMovedAt: new Date(Date.now() - (TODO_AGING_THRESHOLDS_MS.stale + 1)).toISOString() }),
createTask({ id: "FN-4", column: "done" }),
];
expect(summarizeTodoAging(tasks)).toEqual({
fresh: 1,
aging: 1,
stale: 1,
total: 3,
});
});
it("uses dataAsOfMs for freshness-aware age calculation", () => {
vi.setSystemTime(new Date(1000));
const task = createTask({ columnMovedAt: new Date(-8 * 24 * 60 * 60 * 1000).toISOString() });
const dataAsOfMs = -2 * 24 * 60 * 60 * 1000;
expect(getTodoAgeMs(task, dataAsOfMs)).toBe(6 * 24 * 60 * 60 * 1000);
expect(getTodoAgeBucket(task, dataAsOfMs)).toBe("fresh");
expect(getTodoAgeBucket(task)).toBe("aging");
});
it("returns undefined for malformed timestamps", () => {
const task = createTask({ columnMovedAt: "not-a-date", createdAt: "also-not-a-date", updatedAt: "" });
expect(getTodoAgeMs(task)).toBeUndefined();
expect(getTodoAgeBucket(task)).toBeUndefined();
});
});