1
2
8import { beforeEach, describe, expect, it } from "vitest";
9import { useBlockPatchStore } from "../block-patch-store";
10import type { Presence } from "@/lib/types";
11
12const peer = (userId: string, extra?: Partial<Presence>): Presence => ({
13 userId,
14 name: `Name ${userId}`,
15 color: "#123456",
16 cursor: null,
17 lastSeen: 1_700_000_000_000,
18 ...extra,
19});
20
21const state = () => useBlockPatchStore.getState();
22
23describe("block-patch-store — presence fold (per-peer frames, TASK-0093)", () => {
24 beforeEach(() => {
25 useBlockPatchStore.setState({ patches: {}, upserts: {}, deleted: {}, presence: {} });
26 });
27
28 it("upsertPresence adds a peer to the page's roster", () => {
29 state().upsertPresence("pg_1", peer("u1", { cursor: { blockId: "blk_1", offset: 3 } }));
30 expect(state().presence["pg_1"]).toEqual({
31 u1: peer("u1", { cursor: { blockId: "blk_1", offset: 3 } }),
32 });
33 });
34
35 it("a later presence frame UPDATES the same peer in place (no duplicate)", () => {
36 state().upsertPresence("pg_1", peer("u1"));
37 state().upsertPresence(
38 "pg_1",
39 peer("u1", { cursor: { blockId: "blk_2", offset: 9 }, lastSeen: 1_700_000_000_500 }),
40 );
41 expect(Object.keys(state().presence["pg_1"] ?? {})).toEqual(["u1"]);
42 expect(state().presence["pg_1"]?.u1).toMatchObject({
43 cursor: { blockId: "blk_2", offset: 9 },
44 lastSeen: 1_700_000_000_500,
45 });
46 });
47
48 it("different peers on the same page are both kept", () => {
49 state().upsertPresence("pg_1", peer("u1"));
50 state().upsertPresence("pg_1", peer("u2"));
51 expect(Object.keys(state().presence["pg_1"] ?? {})).toEqual(["u1", "u2"]);
52 });
53
54 it("removePresence (presence-leave) drops exactly that peer", () => {
55 state().upsertPresence("pg_1", peer("u1"));
56 state().upsertPresence("pg_1", peer("u2"));
57 state().removePresence("pg_1", "u1");
58 expect(Object.keys(state().presence["pg_1"] ?? {})).toEqual(["u2"]);
59 });
60
61 it("removePresence for an unknown peer is a no-op (idempotent)", () => {
62 state().upsertPresence("pg_1", peer("u1"));
63 state().removePresence("pg_1", "ghost");
64 state().removePresence("pg_1", "u1");
65 state().removePresence("pg_1", "u1");
66 expect(state().presence["pg_1"]).toEqual({});
67 });
68
69 it("presence state is per-page (a leave on one page never touches another)", () => {
70 state().upsertPresence("pg_1", peer("u1"));
71 state().upsertPresence("pg_2", peer("u1"));
72 state().removePresence("pg_1", "u1");
73 expect(state().presence["pg_1"]).toEqual({});
74 expect(Object.keys(state().presence["pg_2"] ?? {})).toEqual(["u1"]);
75 });
76
77 it("reset drops the page's presence (page unmount)", () => {
78 state().upsertPresence("pg_1", peer("u1"));
79 state().reset("pg_1");
80 expect(state().presence["pg_1"]).toEqual({});
81 });
82
83 it("clearBlocks keeps presence (a block change must not wipe who's online)", () => {
84 state().upsertPresence("pg_1", peer("u1"));
85 state().clearBlocks("pg_1");
86 expect(Object.keys(state().presence["pg_1"] ?? {})).toEqual(["u1"]);
87 });
88});
89