import assert from "node:assert/strict"; import test from "node:test"; import { beginSessionPageLoad, groupSessionsByRecency, mergeSessionPage, recencyKeyFor, sortSessions, } from "../src/lib/session-groups.ts"; import { sessionSidebarSubtitle } from "../src/lib/session-sidebar-row.ts"; import { shanghaiDateTimeLabel } from "../src/lib/session-shanghai-clock.ts"; import type { ChatSession } from "../src/lib/home-types.ts"; function session(id: string, updatedAt: number, pinned = false) { return { id, pinned, updatedAt }; } test("sortSessions puts pinned ahead of a newer unpinned row", () => { const olderPinned = session("pin", 100, true); const newer = session("new", 500); assert.deepEqual(sortSessions([newer, olderPinned]).map((item) => item.id), ["pin", "new"]); }); test("sortSessions orders same pin state by updatedAt descending", () => { const early = session("early", 100); const late = session("late", 400); assert.deepEqual(sortSessions([early, late]).map((item) => item.id), ["late", "early"]); }); test("sortSessions keeps the original order when pin and time are equal", () => { const first = session("a", 200); const second = session("b", 200); assert.deepEqual(sortSessions([first, second]).map((item) => item.id), ["a", "b"]); }); test("sortSessions does not drop archived rows", () => { const archived = { ...session("arc", 50), archivedAt: "2026-09-01T00:00:00.000Z" }; const live = session("live", 80); assert.deepEqual(sortSessions([archived, live]).map((item) => item.id), ["live", "arc"]); }); test("groupSessionsByRecency uses local midnight for yesterday across 23:59 to 00:01", () => { const now = new Date(2026, 8, 6, 0, 1, 0).getTime(); const justYesterday = new Date(2026, 8, 5, 23, 59, 0).getTime(); const justToday = new Date(2026, 8, 6, 0, 0, 0).getTime(); assert.equal(recencyKeyFor(justYesterday, now), "yesterday"); assert.equal(recencyKeyFor(justToday, now), "today"); }); test("groupSessionsByRecency splits day 7 and day 8, and day 30 and day 31", () => { const now = new Date(2026, 8, 6, 12, 0, 0).getTime(); const todayStart = new Date(2026, 8, 6).getTime(); const dayMs = 24 * 60 * 60 * 1000; const day7 = todayStart - 6 * dayMs + 12 * 60 * 60 * 1000; const day8 = todayStart - 7 * dayMs + 12 * 60 * 60 * 1000; const day30 = todayStart - 29 * dayMs + 12 * 60 * 60 * 1000; const day31 = todayStart - 30 * dayMs + 12 * 60 * 60 * 1000; assert.equal(recencyKeyFor(day7, now), "week"); assert.equal(recencyKeyFor(day8, now), "month"); assert.equal(recencyKeyFor(day30, now), "month"); assert.equal(recencyKeyFor(day31, now), "older"); const groups = groupSessionsByRecency([ session("today", now), session("week", day7), session("older", day31), ], now); assert.deepEqual(groups.map((group) => group.label), ["今天", "最近 7 天", "更早"]); }); test("mergeSessionPage keeps the local row and skips a duplicate id", () => { const local = session("keep", 900); const incoming = [session("keep", 100), session("next", 80)]; assert.deepEqual(mergeSessionPage([local], incoming).map((item) => item.id), ["keep", "next"]); assert.equal(mergeSessionPage([local], incoming)[0]?.updatedAt, 900); }); test("sidebar subtitle follows updatedAt so the visible clock matches sortSessions", () => { const createdEarly = Date.parse("2026-09-07T05:42:00.000Z"); const activeLate = Date.parse("2026-09-16T10:01:00.000Z"); const mid = Date.parse("2026-09-17T14:53:00.000Z"); const newest = Date.parse("2026-09-18T00:01:00.000Z"); function row(id: string, createdAt: number, updatedAt: number): ChatSession { return { id, title: id, theme: "general", modelId: "m", messages: [{ role: "user", text: "问一句" }], createdAt, updatedAt, sessionType: "consultation", rectificationCaseId: null, chartProfileId: null, chartProfileName: null, chartProfileRole: null, pinned: false, archivedAt: null, messagesHydrated: true, }; } const earlyCreatedLateActive = row("early-created", createdEarly, activeLate); const sessions = [ earlyCreatedLateActive, row("mid", mid, mid), row("newest", newest, newest), ]; assert.deepEqual(sortSessions(sessions).map((item) => item.id), ["newest", "mid", "early-created"]); const clocks = sortSessions(sessions).map((item) => sessionSidebarSubtitle(item)); const expected = sortSessions(sessions).map((item) => shanghaiDateTimeLabel(new Date(item.updatedAt))); assert.deepEqual(clocks, expected); assert.equal(sessionSidebarSubtitle(earlyCreatedLateActive), shanghaiDateTimeLabel(new Date(activeLate))); assert.notEqual(sessionSidebarSubtitle(earlyCreatedLateActive), shanghaiDateTimeLabel(new Date(createdEarly))); }); test("beginSessionPageLoad only starts one in-flight request", () => { const inFlight = { current: false }; assert.equal(beginSessionPageLoad(inFlight, "cursor"), true); assert.equal(beginSessionPageLoad(inFlight, "cursor"), false); assert.equal(beginSessionPageLoad({ current: false }, null), false); });