import { describe, expect, it } from "vitest"; import { errorMessage, formatRelativeTime, recencyGroup, titleFromPrompt, truncate, } from "../lib/utils"; describe("truncate", () => { it("collapses whitespace so list rows never wrap unexpectedly", () => { expect(truncate("hello\n\n world", 40)).toBe("hello world"); }); it("leaves short text alone", () => { expect(truncate("short", 40)).toBe("short"); }); it("clips with an ellipsis at the requested width", () => { const result = truncate("a".repeat(100), 10); expect(result).toBe(`${"a".repeat(9)}…`); expect(result).toHaveLength(10); }); }); describe("formatRelativeTime", () => { const now = 1_700_000_000_000; it("reports very recent timestamps as just now", () => { expect(formatRelativeTime(now - 5_000, now)).toBe("just now"); }); it("reports minutes and hours", () => { expect(formatRelativeTime(now - 12 * 60_000, now)).toBe("12m ago"); expect(formatRelativeTime(now - 3 * 3_600_000, now)).toBe("3h ago"); }); it("reports days within the last week", () => { expect(formatRelativeTime(now - 2 * 86_400_000, now)).toBe("2d ago"); }); it("treats clock skew as just now rather than a negative age", () => { expect(formatRelativeTime(now + 60_000, now)).toBe("just now"); }); }); describe("recencyGroup", () => { // Fixed local noon so the day boundaries are unambiguous on any machine. const now = new Date(2026, 9, 4, 12, 0, 0).getTime(); const DAY = 86_400_000; it("buckets today", () => { expect(recencyGroup(now - 60_000, now)).toBe("Today"); }); it("buckets yesterday", () => { expect(recencyGroup(new Date(2026, 9, 3, 12, 0, 0).getTime(), now)).toBe("Yesterday"); }); it("buckets the rest of the week", () => { expect(recencyGroup(now - 3 * DAY, now)).toBe("This week"); expect(recencyGroup(now - 6 * DAY, now)).toBe("This week"); }); it("buckets anything older", () => { expect(recencyGroup(now - 30 * DAY, now)).toBe("Older"); }); it("does not bucket the small hours of today as yesterday", () => { // Just after midnight is still today, even though it is under 24h ago. const justAfterMidnight = new Date(2026, 9, 4, 0, 5, 0).getTime(); expect(recencyGroup(justAfterMidnight, now)).toBe("Today"); }); }); describe("titleFromPrompt", () => { it("uses the first non-empty line, stripped of markdown decoration", () => { expect(titleFromPrompt("\n\n## How do I deploy?\nmore text", 60)).toBe("How do I deploy?"); }); it("falls back to a placeholder for an empty prompt", () => { expect(titleFromPrompt(" \n ", 60)).toBe("New chat"); }); }); describe("errorMessage", () => { it("unwraps Error instances", () => { expect(errorMessage(new Error("boom"))).toBe("boom"); }); it("passes strings through", () => { expect(errorMessage("plain failure")).toBe("plain failure"); }); it("survives values that cannot be serialised", () => { const circular: Record = {}; circular.self = circular; expect(typeof errorMessage(circular)).toBe("string"); }); });