import { describe, expect, it } from "vitest"; import { buildSessionListVM, type SessionDeviceInput } from "./session-list.vm.js"; const NOW = Date.UTC(2026, 5, 18, 12, 0, 0); function session(overrides: Partial & { id: string }): SessionDeviceInput { return { lastSeenAt: new Date(NOW), ...overrides, }; } describe("buildSessionListVM", () => { it("returns an empty state when there are no sessions", () => { expect(buildSessionListVM({ sessions: [], currentSessionId: "s1", nowMs: NOW })).toEqual({ status: "empty" }); }); it("flags the current device, forbids revoking it, and counts the others", () => { const vm = buildSessionListVM({ sessions: [ session({ id: "s1", userAgent: "Firefox", approxLocation: "Germany" }), session({ id: "s2", deviceLabel: "iPhone", trustedAt: new Date(NOW) }), ], currentSessionId: "s1", nowMs: NOW, }); expect(vm.status).toBe("ready"); if (vm.status !== "ready") return; const current = vm.data.devices.find((d) => d.id === "s1")!; const other = vm.data.devices.find((d) => d.id === "s2")!; expect(current.isCurrent).toBe(true); expect(current.canRevoke).toBe(false); expect(current.deviceLabel).toBe("Firefox"); expect(other.isCurrent).toBe(false); expect(other.canRevoke).toBe(true); expect(other.trusted).toBe(true); expect(vm.data.otherActiveCount).toBe(1); expect(vm.data.canRevokeOthers).toBe(true); }); it("orders the current device first", () => { const vm = buildSessionListVM({ sessions: [session({ id: "s2" }), session({ id: "s1" })], currentSessionId: "s1", nowMs: NOW, }); if (vm.status !== "ready") throw new Error("expected ready"); expect(vm.data.devices[0].id).toBe("s1"); }); it("falls back to an i18n key when no device label or user agent is present", () => { const vm = buildSessionListVM({ sessions: [session({ id: "s1" })], currentSessionId: null, nowMs: NOW }); if (vm.status !== "ready") throw new Error("expected ready"); expect(vm.data.devices[0].deviceLabel).toBeUndefined(); expect(vm.data.devices[0].deviceLabelFallbackKey).toBe("settings.security.sessions.unknownDevice"); }); it.each([ [30 * 1000, "justNow", undefined], [5 * 60 * 1000, "minutes", 5], [3 * 60 * 60 * 1000, "hours", 3], [2 * 24 * 60 * 60 * 1000, "days", 2], ])("buckets last-seen %ims ago as %s", (ago, bucket, count) => { const vm = buildSessionListVM({ sessions: [session({ id: "s1", lastSeenAt: new Date(NOW - (ago as number)) })], currentSessionId: "s1", nowMs: NOW, }); if (vm.status !== "ready") throw new Error("expected ready"); expect(vm.data.devices[0].lastSeenBucket).toBe(bucket); expect(vm.data.devices[0].lastSeenCount).toBe(count); }); it("has no revocable others when only the current device is active", () => { const vm = buildSessionListVM({ sessions: [session({ id: "s1" })], currentSessionId: "s1", nowMs: NOW }); if (vm.status !== "ready") throw new Error("expected ready"); expect(vm.data.otherActiveCount).toBe(0); expect(vm.data.canRevokeOthers).toBe(false); }); });