HuggingFaceBio/carbon-tokenization
11
1/**2 * Comment Store Tests3 *4 * Covers the Yjs-backed comment store:5 * - add / addReply / remove / get / getAll6 * - resolve / unresolve7 * - observe8 * - collaboration (concurrent adds merge)9 */10import { describe, it, expect } from "vitest";11import * as Y from "yjs";12import { createCommentStore, type CommentData, type ReplyData } from "../src/editor/comments";13 14function makeStore() {15 const ydoc = new Y.Doc();16 return { ydoc, store: createCommentStore(ydoc) };17}18 19function baseComment(overrides: Partial<Omit<CommentData, "replies">> = {}) {20 return {21 id: "c1",22 author: "alice",23 authorColor: "#f00",24 text: "hello",25 createdAt: 1_000,26 resolved: false,27 ...overrides,28 } as Omit<CommentData, "replies">;29}30 31describe("comment-store — CRUD", () => {32 it("add() stores a comment with an empty replies array", () => {33 const { store } = makeStore();34 store.add(baseComment());35 const got = store.get("c1");36 expect(got).toBeDefined();37 expect(got!.text).toBe("hello");38 expect(got!.replies).toEqual([]);39 });40 41 it("getAll() returns comments sorted by createdAt", () => {42 const { store } = makeStore();43 store.add(baseComment({ id: "late", createdAt: 3000 }));44 store.add(baseComment({ id: "early", createdAt: 1000 }));45 store.add(baseComment({ id: "mid", createdAt: 2000 }));46 const all = store.getAll();47 expect(all.map((c) => c.id)).toEqual(["early", "mid", "late"]);48 });49 50 it("remove() deletes a comment", () => {51 const { store } = makeStore();52 store.add(baseComment());53 store.remove("c1");54 expect(store.get("c1")).toBeUndefined();55 });56 57 it("get() on a missing id returns undefined", () => {58 const { store } = makeStore();59 expect(store.get("missing")).toBeUndefined();60 });61});62 63describe("comment-store — replies", () => {64 const reply: ReplyData = {65 id: "r1",66 author: "bob",67 authorColor: "#00f",68 text: "ack",69 createdAt: 1500,70 };71 72 it("addReply() appends to the replies array", () => {73 const { store } = makeStore();74 store.add(baseComment());75 store.addReply("c1", reply);76 store.addReply("c1", { ...reply, id: "r2", text: "ack2" });77 const got = store.get("c1")!;78 expect(got.replies.map((r) => r.id)).toEqual(["r1", "r2"]);79 expect(got.replies[1].text).toBe("ack2");80 });81 82 it("addReply() on a missing comment is a no-op", () => {83 const { store } = makeStore();84 store.addReply("missing", reply);85 expect(store.get("missing")).toBeUndefined();86 });87});88 89describe("comment-store — resolve/unresolve", () => {90 it("resolve() marks the comment resolved and records the resolver", () => {91 const { store } = makeStore();92 store.add(baseComment());93 store.resolve("c1", "carol");94 const got = store.get("c1")!;95 expect(got.resolved).toBe(true);96 expect(got.resolvedBy).toBe("carol");97 expect(typeof got.resolvedAt).toBe("number");98 });99 100 it("unresolve() clears the resolved metadata", () => {101 const { store } = makeStore();102 store.add(baseComment());103 store.resolve("c1", "carol");104 store.unresolve("c1");105 const got = store.get("c1")!;106 expect(got.resolved).toBe(false);107 expect(got.resolvedBy).toBeUndefined();108 expect(got.resolvedAt).toBeUndefined();109 });110 111 it("resolve() on a missing comment is a no-op", () => {112 const { store } = makeStore();113 store.resolve("missing", "carol");114 expect(store.get("missing")).toBeUndefined();115 });116});117 118describe("comment-store — observe", () => {119 it("fires on add and remove, stops after unsubscribe", () => {120 const { store } = makeStore();121 let count = 0;122 const unsub = store.observe(() => { count++; });123 store.add(baseComment({ id: "a" }));124 store.add(baseComment({ id: "b" }));125 store.remove("a");126 expect(count).toBe(3);127 unsub();128 store.remove("b");129 expect(count).toBe(3);130 });131});132 133describe("comment-store — collaboration", () => {134 it("concurrent adds in different docs both survive after sync", () => {135 const docA = new Y.Doc();136 const docB = new Y.Doc();137 const a = createCommentStore(docA);138 const b = createCommentStore(docB);139 140 a.add(baseComment({ id: "from-a" }));141 b.add(baseComment({ id: "from-b", author: "bob", authorColor: "#0f0" }));142 143 Y.applyUpdate(docA, Y.encodeStateAsUpdate(docB));144 Y.applyUpdate(docB, Y.encodeStateAsUpdate(docA));145 146 expect(a.getAll().map((c) => c.id).sort()).toEqual(["from-a", "from-b"]);147 expect(b.getAll().map((c) => c.id).sort()).toEqual(["from-a", "from-b"]);148 });149});150 