1import { describe, expect, it } from "vitest";
2
3import { PRESENCE_PALETTE, fnv1a32, presenceColor } from "./presence.js";
4
5describe("fnv1a32 (the deterministic hash)", () => {
6 it("matches the FNV-1a 32-bit reference vectors (pins the algorithm)", () => {
7
8
9 expect(fnv1a32("")).toBe(0x811c9dc5);
10 expect(fnv1a32("a")).toBe(0xe40c292c);
11 expect(fnv1a32("foobar")).toBe(0xbf9cf968);
12 });
13
14 it("is deterministic across calls", () => {
15 const a = fnv1a32("user-123");
16 const b = fnv1a32("user-123");
17 expect(a).toBe(b);
18 });
19});
20
21describe("PRESENCE_PALETTE (the 20-color design-token set)", () => {
22 it("has exactly 20 distinct, non-empty color names", () => {
23 expect(PRESENCE_PALETTE).toHaveLength(20);
24 expect(new Set(PRESENCE_PALETTE).size).toBe(20);
25 for (const name of PRESENCE_PALETTE) {
26 expect(name.length).toBeGreaterThan(0);
27
28
29 expect(name.startsWith("#")).toBe(false);
30 }
31 });
32});
33
34describe("presenceColor (TASK-0093 R1)", () => {
35 it("is deterministic: the same user is the same color everywhere", () => {
36
37
38 for (const id of ["u-alice", "u-bob", "u-carol", "user-0424"]) {
39 expect(presenceColor(id)).toBe(presenceColor(id));
40 }
41 });
42
43 it("always returns a palette member", () => {
44 for (const id of ["a", "b", "c", "user-1", "user-2", "user-1000"]) {
45 expect(PRESENCE_PALETTE).toContain(presenceColor(id));
46 }
47 });
48
49 it("1,000-user fixture: no color is assigned to more than 10 % of users (the distribution gate)", () => {
50
51
52 const users = Array.from({ length: 1000 }, (_, i) => `user-${String(i + 1).padStart(4, "0")}`);
53 const counts = new Map<string, number>();
54 for (const id of users) {
55 const color = presenceColor(id);
56 counts.set(color, (counts.get(color) ?? 0) + 1);
57 }
58 let max = 0;
59 for (const n of counts.values()) max = Math.max(max, n);
60 expect(max).toBeLessThanOrEqual(users.length * 0.1);
61 });
62
63 it("covers more than half the palette over 1,000 users (no hash collapse)", () => {
64 const users = Array.from({ length: 1000 }, (_, i) => `user-${i}`);
65 const seen = new Set(users.map(presenceColor));
66
67
68 expect(seen.size).toBeGreaterThanOrEqual(11);
69 });
70});
71