Spaces:
Runtime error
Runtime error
| /** | |
| * Comment Store Tests | |
| * | |
| * Covers the Yjs-backed comment store: | |
| * - add / addReply / remove / get / getAll | |
| * - resolve / unresolve | |
| * - observe | |
| * - collaboration (concurrent adds merge) | |
| */ | |
| import { describe, it, expect } from "vitest"; | |
| import * as Y from "yjs"; | |
| import { createCommentStore, type CommentData, type ReplyData } from "../src/editor/comments"; | |
| function makeStore() { | |
| const ydoc = new Y.Doc(); | |
| return { ydoc, store: createCommentStore(ydoc) }; | |
| } | |
| function baseComment(overrides: Partial<Omit<CommentData, "replies">> = {}) { | |
| return { | |
| id: "c1", | |
| author: "alice", | |
| authorColor: "#f00", | |
| text: "hello", | |
| createdAt: 1_000, | |
| resolved: false, | |
| ...overrides, | |
| } as Omit<CommentData, "replies">; | |
| } | |
| describe("comment-store — CRUD", () => { | |
| it("add() stores a comment with an empty replies array", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment()); | |
| const got = store.get("c1"); | |
| expect(got).toBeDefined(); | |
| expect(got!.text).toBe("hello"); | |
| expect(got!.replies).toEqual([]); | |
| }); | |
| it("getAll() returns comments sorted by createdAt", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment({ id: "late", createdAt: 3000 })); | |
| store.add(baseComment({ id: "early", createdAt: 1000 })); | |
| store.add(baseComment({ id: "mid", createdAt: 2000 })); | |
| const all = store.getAll(); | |
| expect(all.map((c) => c.id)).toEqual(["early", "mid", "late"]); | |
| }); | |
| it("remove() deletes a comment", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment()); | |
| store.remove("c1"); | |
| expect(store.get("c1")).toBeUndefined(); | |
| }); | |
| it("get() on a missing id returns undefined", () => { | |
| const { store } = makeStore(); | |
| expect(store.get("missing")).toBeUndefined(); | |
| }); | |
| }); | |
| describe("comment-store — replies", () => { | |
| const reply: ReplyData = { | |
| id: "r1", | |
| author: "bob", | |
| authorColor: "#00f", | |
| text: "ack", | |
| createdAt: 1500, | |
| }; | |
| it("addReply() appends to the replies array", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment()); | |
| store.addReply("c1", reply); | |
| store.addReply("c1", { ...reply, id: "r2", text: "ack2" }); | |
| const got = store.get("c1")!; | |
| expect(got.replies.map((r) => r.id)).toEqual(["r1", "r2"]); | |
| expect(got.replies[1].text).toBe("ack2"); | |
| }); | |
| it("addReply() on a missing comment is a no-op", () => { | |
| const { store } = makeStore(); | |
| store.addReply("missing", reply); | |
| expect(store.get("missing")).toBeUndefined(); | |
| }); | |
| }); | |
| describe("comment-store — resolve/unresolve", () => { | |
| it("resolve() marks the comment resolved and records the resolver", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment()); | |
| store.resolve("c1", "carol"); | |
| const got = store.get("c1")!; | |
| expect(got.resolved).toBe(true); | |
| expect(got.resolvedBy).toBe("carol"); | |
| expect(typeof got.resolvedAt).toBe("number"); | |
| }); | |
| it("unresolve() clears the resolved metadata", () => { | |
| const { store } = makeStore(); | |
| store.add(baseComment()); | |
| store.resolve("c1", "carol"); | |
| store.unresolve("c1"); | |
| const got = store.get("c1")!; | |
| expect(got.resolved).toBe(false); | |
| expect(got.resolvedBy).toBeUndefined(); | |
| expect(got.resolvedAt).toBeUndefined(); | |
| }); | |
| it("resolve() on a missing comment is a no-op", () => { | |
| const { store } = makeStore(); | |
| store.resolve("missing", "carol"); | |
| expect(store.get("missing")).toBeUndefined(); | |
| }); | |
| }); | |
| describe("comment-store — observe", () => { | |
| it("fires on add and remove, stops after unsubscribe", () => { | |
| const { store } = makeStore(); | |
| let count = 0; | |
| const unsub = store.observe(() => { count++; }); | |
| store.add(baseComment({ id: "a" })); | |
| store.add(baseComment({ id: "b" })); | |
| store.remove("a"); | |
| expect(count).toBe(3); | |
| unsub(); | |
| store.remove("b"); | |
| expect(count).toBe(3); | |
| }); | |
| }); | |
| describe("comment-store — collaboration", () => { | |
| it("concurrent adds in different docs both survive after sync", () => { | |
| const docA = new Y.Doc(); | |
| const docB = new Y.Doc(); | |
| const a = createCommentStore(docA); | |
| const b = createCommentStore(docB); | |
| a.add(baseComment({ id: "from-a" })); | |
| b.add(baseComment({ id: "from-b", author: "bob", authorColor: "#0f0" })); | |
| Y.applyUpdate(docA, Y.encodeStateAsUpdate(docB)); | |
| Y.applyUpdate(docB, Y.encodeStateAsUpdate(docA)); | |
| expect(a.getAll().map((c) => c.id).sort()).toEqual(["from-a", "from-b"]); | |
| expect(b.getAll().map((c) => c.id).sort()).toEqual(["from-a", "from-b"]); | |
| }); | |
| }); | |