Ctrl K
ComunidadGuías…

server/models/Comment.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { v4 as uuidv4 } from "uuid";
import { MentionType } from "@shared/types";
import { buildComment, buildDocument, buildUser } from "@server/test/factories";
import Comment from "./Comment";

describe("Comment", () => {
  describe("toPlainText", () => {
    it("should convert simple text to plain text", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      const text = comment.toPlainText();
      expect(text).toBe("test");
    });

    it("should convert comment with mention to plain text", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await Comment.create({
        documentId: document.id,
        createdById: user.id,
        data: {
          type: "doc",
          content: [
            {
              type: "paragraph",
              content: [
                {
                  type: "text",
                  text: "Hello ",
                },
                {
                  type: "mention",
                  attrs: {
                    type: MentionType.User,
                    label: "Jane",
                    modelId: uuidv4(),
                    id: uuidv4(),
                  },
                },
              ],
            },
          ],
        },
      });

      const text = comment.toPlainText();
      expect(text).toBe("Hello @Jane");
    });

    it("should convert comment with document mention to plain text", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await Comment.create({
        documentId: document.id,
        createdById: user.id,
        data: {
          type: "doc",
          content: [
            {
              type: "paragraph",
              content: [
                {
                  type: "text",
                  text: "See ",
                },
                {
                  type: "mention",
                  attrs: {
                    type: MentionType.Document,
                    label: "My Document",
                    modelId: uuidv4(),
                    id: uuidv4(),
                  },
                },
              ],
            },
          ],
        },
      });

      const text = comment.toPlainText();
      expect(text).toBe("See My Document");
    });
  });

  describe("resolve", () => {
    it("should resolve the comment", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      comment.resolve(user);

      expect(comment.isResolved).toBe(true);
      expect(comment.resolvedById).toBe(user.id);
      expect(comment.resolvedAt).toBeTruthy();
    });

    it("should throw if already resolved", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      comment.resolve(user);

      expect(() => comment.resolve(user)).toThrow();
    });

    it("should throw if comment is a reply", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const parent = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      const reply = await buildComment({
        userId: user.id,
        documentId: document.id,
        parentCommentId: parent.id,
      });

      expect(() => reply.resolve(user)).toThrow();
    });
  });

  describe("unresolve", () => {
    it("should unresolve the comment", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      comment.resolve(user);
      comment.unresolve();

      expect(comment.isResolved).toBe(false);
      expect(comment.resolvedById).toBeNull();
      expect(comment.resolvedAt).toBeNull();
    });

    it("should throw if not resolved", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const comment = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      expect(() => comment.unresolve()).toThrow();
    });
  });

  describe("cascade resolved state", () => {
    it("propagates resolvedAt to existing replies when the thread is resolved", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      const reply = await buildComment({
        userId: user.id,
        documentId: document.id,
        parentCommentId: thread.id,
      });

      thread.resolve(user);
      await thread.save();

      await reply.reload();
      expect(reply.resolvedAt).toEqual(thread.resolvedAt);
      expect(reply.resolvedById).toEqual(user.id);
    });

    it("clears resolvedAt on replies when the thread is unresolved", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      const reply = await buildComment({
        userId: user.id,
        documentId: document.id,
        parentCommentId: thread.id,
      });

      thread.resolve(user);
      await thread.save();
      thread.unresolve();
      await thread.save();

      await reply.reload();
      expect(reply.resolvedAt).toBeNull();
      expect(reply.resolvedById).toBeNull();
    });

    it("inherits resolved state when a reply is created on a resolved thread", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      thread.resolve(user);
      await thread.save();

      const reply = await buildComment({
        userId: user.id,
        documentId: document.id,
        parentCommentId: thread.id,
      });

      expect(reply.resolvedAt).toEqual(thread.resolvedAt);
      expect(reply.resolvedById).toEqual(user.id);
    });

    it("rejects replies to comments in a different document", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const otherDocument = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: otherDocument.id,
      });

      await expect(
        buildComment({
          userId: user.id,
          documentId: document.id,
          parentCommentId: thread.id,
        })
      ).rejects.toThrow("Parent comment must belong to the same document");
    });

    it("rejects replies to a reply", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      const reply = await buildComment({
        userId: user.id,
        documentId: document.id,
        parentCommentId: thread.id,
      });

      await expect(
        buildComment({
          userId: user.id,
          documentId: document.id,
          parentCommentId: reply.id,
        })
      ).rejects.toThrow("Cannot reply to a reply");
    });
  });

  describe("deleteChildComments", () => {
    it("deletes the replies when the thread is deleted", async () => {
      const user = await buildUser();
      const document = await buildDocument({
        userId: user.id,
        teamId: user.teamId,
      });
      const thread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });
      const replies = await Promise.all([
        buildComment({
          userId: user.id,
          documentId: document.id,
          parentCommentId: thread.id,
        }),
        buildComment({
          userId: user.id,
          documentId: document.id,
          parentCommentId: thread.id,
        }),
      ]);
      const otherThread = await buildComment({
        userId: user.id,
        documentId: document.id,
      });

      await thread.destroy();

      const remaining = await Comment.findAll({
        where: { documentId: document.id },
      });
      expect(remaining.map((comment) => comment.id)).toEqual([otherThread.id]);

      const deleted = await Comment.findAll({
        where: { id: replies.map((reply) => reply.id) },
        paranoid: false,
      });
      expect(deleted).toHaveLength(2);
      expect(deleted.every((comment) => comment.deletedAt)).toBe(true);
    });
  });
});