134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
errorMessage,
|
|
formatBytes,
|
|
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<string, unknown> = {};
|
|
circular.self = circular;
|
|
expect(typeof errorMessage(circular)).toBe("string");
|
|
});
|
|
});
|
|
|
|
describe("formatBytes", () => {
|
|
it("matches what `ollama list` prints for the same bytes", () => {
|
|
// Captured from this machine: `ollama list` shows ornith-1.5:9b as 6.6 GB,
|
|
// and a size that disagreed with the terminal would read as our bug.
|
|
expect(formatBytes(6_550_813_657)).toBe("6.6 GB");
|
|
});
|
|
|
|
it("keeps small counts in bytes", () => {
|
|
expect(formatBytes(0)).toBe("0 B");
|
|
expect(formatBytes(326)).toBe("326 B");
|
|
expect(formatBytes(999)).toBe("999 B");
|
|
});
|
|
|
|
it("steps up one unit at a time", () => {
|
|
expect(formatBytes(1_500)).toBe("1.5 kB");
|
|
expect(formatBytes(2_500_000)).toBe("2.5 MB");
|
|
expect(formatBytes(3_000_000_000_000)).toBe("3.0 TB");
|
|
});
|
|
|
|
it("stops at terabytes rather than inventing a unit", () => {
|
|
expect(formatBytes(9_000_000_000_000_000)).toBe("9000.0 TB");
|
|
});
|
|
|
|
it("has no answer for a size that is absent or nonsense", () => {
|
|
// undefined rather than "NaN B", so a caller leaves the field out.
|
|
expect(formatBytes(undefined)).toBeUndefined();
|
|
expect(formatBytes(-1)).toBeUndefined();
|
|
expect(formatBytes(Number.NaN)).toBeUndefined();
|
|
expect(formatBytes(Number.POSITIVE_INFINITY)).toBeUndefined();
|
|
});
|
|
});
|