import { describe, expect, it } from "vitest"; import { PRESENCE_PALETTE, fnv1a32, presenceColor } from "./presence.js"; describe("fnv1a32 (the deterministic hash)", () => { it("matches the FNV-1a 32-bit reference vectors (pins the algorithm)", () => { // Standard FNV-1a 32-bit test vectors (offset basis 0x811c9dc5, prime // 0x01000193) — a regression here changes EVERY user's color at once. expect(fnv1a32("")).toBe(0x811c9dc5); expect(fnv1a32("a")).toBe(0xe40c292c); expect(fnv1a32("foobar")).toBe(0xbf9cf968); }); it("is deterministic across calls", () => { const a = fnv1a32("user-123"); const b = fnv1a32("user-123"); expect(a).toBe(b); }); }); describe("PRESENCE_PALETTE (the 20-color design-token set)", () => { it("has exactly 20 distinct, non-empty color names", () => { expect(PRESENCE_PALETTE).toHaveLength(20); expect(new Set(PRESENCE_PALETTE).size).toBe(20); for (const name of PRESENCE_PALETTE) { expect(name.length).toBeGreaterThan(0); // Names, never hex (the component maps name → its own token class — // no hard-coded hex in the component, Phase-4 rule). expect(name.startsWith("#")).toBe(false); } }); }); describe("presenceColor (TASK-0093 R1)", () => { it("is deterministic: the same user is the same color everywhere", () => { // The doc §5.1 rule: a user's color is stable across rooms, tabs and // reconnects — a pure function of the userId. for (const id of ["u-alice", "u-bob", "u-carol", "user-0424"]) { expect(presenceColor(id)).toBe(presenceColor(id)); } }); it("always returns a palette member", () => { for (const id of ["a", "b", "c", "user-1", "user-2", "user-1000"]) { expect(PRESENCE_PALETTE).toContain(presenceColor(id)); } }); it("1,000-user fixture: no color is assigned to more than 10 % of users (the distribution gate)", () => { // Expected share per color is 1/20 = 5 %; the test asserts the FNV-1a // mix is uniform enough that the max bucket stays ≤ 10 % (100 users). const users = Array.from({ length: 1000 }, (_, i) => `user-${String(i + 1).padStart(4, "0")}`); const counts = new Map(); for (const id of users) { const color = presenceColor(id); counts.set(color, (counts.get(color) ?? 0) + 1); } let max = 0; for (const n of counts.values()) max = Math.max(max, n); expect(max).toBeLessThanOrEqual(users.length * 0.1); }); it("covers more than half the palette over 1,000 users (no hash collapse)", () => { const users = Array.from({ length: 1000 }, (_, i) => `user-${i}`); const seen = new Set(users.map(presenceColor)); // 1,000 draws over 20 colors: seeing ≥ 11 of them is a safe lower // bound (the probability of collapsing to ≤ 10 is ~0). expect(seen.size).toBeGreaterThanOrEqual(11); }); });