/** * Unit tests for the comment-event → `comment.event` frame builder * (TASK-0094 R1) + the notification-event → `notification.push` frame * builder (TASK-0094 R7). Both are pure — no sockets, no Redis. */ import { describe, expect, it } from "vitest"; import type { DomainEvent } from "@factory/shared"; import { commentEventFrameFor, COMMENT_PREVIEW_MAX } from "../src/comments.js"; import { notificationPushFor, NOTIFICATION_CREATED_EVENT } from "../src/notifications.js"; const evt = (name: string, payload: Record): DomainEvent => ({ id: "evt_x", name: name as DomainEvent["name"], payload, ts: "2026-09-27T00:00:00.000Z", }) as DomainEvent; describe("commentEventFrameFor (TASK-0094 R1)", () => { it("comment.created (no replyId) → kind 'created' + a body preview", () => { const f = commentEventFrameFor( evt("comment.created", { commentId: "c1", pageId: "pg_1", actorId: "u1", body: "hello world", }), ); expect(f).toEqual({ type: "comment.event", kind: "created", commentId: "c1", pageId: "pg_1", actorId: "u1", preview: "hello world", }); }); it("comment.created WITH a replyId → kind 'replied'", () => { const f = commentEventFrameFor( evt("comment.created", { commentId: "c1", replyId: "r1", pageId: "pg_1", actorId: "u2", body: "a reply", }), ); expect(f?.kind).toBe("replied"); expect(f?.preview).toBe("a reply"); }); it("truncates the preview to the max length", () => { const long = "x".repeat(COMMENT_PREVIEW_MAX + 50); const f = commentEventFrameFor( evt("comment.created", { commentId: "c", pageId: "p", actorId: "u", body: long }), ); expect(f?.preview).toHaveLength(COMMENT_PREVIEW_MAX); }); it("comment.resolved / reopened / reacted / deleted → the matching kind, empty preview", () => { for (const [name, kind] of [ ["comment.resolved", "resolved"], ["comment.reopened", "reopened"], ["comment.reaction", "reacted"], ["comment.deleted", "deleted"], ] as const) { const f = commentEventFrameFor(evt(name, { commentId: "c", pageId: "p", actorId: "u" })); expect(f?.kind).toBe(kind); expect(f?.preview).toBe(""); } }); it("an event without a pageId → null (no room to fan out to)", () => { expect( commentEventFrameFor(evt("comment.created", { commentId: "c", actorId: "u", body: "b" })), ).toBeNull(); }); it("a malformed payload → null (never a crash)", () => { expect(commentEventFrameFor(evt("comment.created", { pageId: "p", body: "b" }))).toBeNull(); // no commentId expect(commentEventFrameFor(evt("comment.created", {}))).toBeNull(); }); it("an unrelated event → null (the mapper skips it)", () => { expect(commentEventFrameFor(evt("page.updated", { pageId: "p" }))).toBeNull(); expect( commentEventFrameFor(evt("notification.created", { notificationId: "n", userId: "u" })), ).toBeNull(); }); it("a reaction event carries the emoji/active in the payload (validated, not in the frame)", () => { const f = commentEventFrameFor( evt("comment.reaction", { commentId: "c", pageId: "p", actorId: "u", emoji: "👍", active: true, }), ); expect(f?.kind).toBe("reacted"); // The frame is minimal (the panel refetches the reaction row) — no emoji. expect("emoji" in (f as object)).toBe(false); }); }); describe("notificationPushFor (TASK-0094 R7)", () => { const base = { notificationId: "n1", userId: "u1", type: "MENTION", title: "You were mentioned", deepLink: { type: "comment", id: "c1" }, }; it("notification.created → the push frame + the RECIPIENT as target", () => { const t = notificationPushFor(evt("notification.created", { ...base, actorName: "Ana" })); expect(t?.userId).toBe("u1"); expect(t?.frame).toEqual({ type: "notification.push", notificationId: "n1", kind: "mention", title: "You were mentioned", deepLink: { type: "comment", id: "c1" }, actorName: "Ana", }); }); it("the kind is the policy table's snake_case type (not a raw enum)", () => { expect( notificationPushFor(evt("notification.created", { ...base, type: "PAGE_SHARED" }))?.frame .kind, ).toBe("page_shared"); expect( notificationPushFor(evt("notification.created", { ...base, type: "COMMENT" }))?.frame.kind, ).toBe("comment"); expect( notificationPushFor(evt("notification.created", { ...base, type: "ASSIGNMENT" }))?.frame.kind, ).toBe("assignment"); }); it("a non-notification event → null", () => { expect(notificationPushFor(evt("comment.created", { commentId: "c", pageId: "p" }))).toBeNull(); }); it("an unknown type still produces a frame (conservative kind, no crash)", () => { const t = notificationPushFor( evt("notification.created", { ...base, type: "SOME_FUTURE_TYPE" }), ); expect(t?.frame.kind).toBe("some_future_type"); }); it("a malformed payload → null (no notificationId / userId / deepLink)", () => { expect( notificationPushFor( evt("notification.created", { userId: "u1", type: "MENTION", title: "t" }), ), ).toBeNull(); expect( notificationPushFor( evt("notification.created", { notificationId: "n", type: "MENTION", title: "t", deepLink: { type: "x", id: "y" }, }), ), ).toBeNull(); // no userId expect( notificationPushFor( evt("notification.created", { notificationId: "n", userId: "u", type: "MENTION", title: "t", }), ), ).toBeNull(); // no deepLink }); it("the event name constant matches the frame builder's filter", () => { expect(NOTIFICATION_CREATED_EVENT).toBe("notification.created"); }); });