81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
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<SessionDeviceInput> & { 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);
|
|
});
|
|
});
|