carbon-tokenization / frontend /tests /comments-store.test.ts
tfrere's picture
tfrere HF Staff
feat(frontend): editor refresh (embed studio, comment popover, shiki, top bar, hooks, styles)
76fc93a
Raw
History Blame Contribute Delete
4.56 kB
/**
* 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"]);
});
});