Aakansha Doshi 2 rokov pred
rodič
commit
7f6789780d

+ 3 - 1
src/components/MermaidToExcalidraw.tsx

@@ -15,7 +15,7 @@ import Spinner from "./Spinner";
 import "./MermaidToExcalidraw.scss";
 
 import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces";
-import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw";
+import type { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw";
 import { t } from "../i18n";
 import Trans from "./Trans";
 
@@ -49,6 +49,7 @@ const importMermaidDataFromStorage = () => {
 const ErrorComp = ({ error }: { error: string }) => {
   return (
     <div
+      data-testid="mermaid-error"
       style={{
         color: "red",
         fontWeight: 800,
@@ -185,6 +186,7 @@ const MermaidToExcalidraw = () => {
 
   return (
     <Dialog
+      className="dialog-mermaid"
       onCloseRequest={onClose}
       title={
         <>

+ 178 - 0
src/tests/MermaidToExcalidraw.test.tsx

@@ -0,0 +1,178 @@
+import {
+  act,
+  fireEvent,
+  getTextEditor,
+  render,
+  updateTextEditor,
+} from "./test-utils";
+import { Excalidraw } from "../packages/excalidraw/index";
+import React from "react";
+import { expect, vi } from "vitest";
+import * as MermaidToExcalidraw from "@excalidraw/mermaid-to-excalidraw";
+
+vi.mock("@excalidraw/mermaid-to-excalidraw", async (importActual) => {
+  const module = (await importActual()) as any;
+
+  return {
+    __esModule: true,
+    ...module,
+  };
+});
+const parseMermaidToExcalidrawSpy = vi.spyOn(
+  MermaidToExcalidraw,
+  "parseMermaidToExcalidraw",
+);
+
+parseMermaidToExcalidrawSpy.mockImplementation(
+  async (
+    definition: string,
+    options?: MermaidToExcalidraw.MermaidOptions | undefined,
+  ) => {
+    const firstLine = definition.split("\n")[0];
+    return new Promise((resolve, reject) => {
+      if (firstLine === "flowchart TD") {
+        resolve({
+          elements: [
+            {
+              id: "Start",
+              type: "rectangle",
+              groupIds: [],
+              x: 0,
+              y: 0,
+              width: 69.703125,
+              height: 44,
+              strokeWidth: 2,
+              label: {
+                groupIds: [],
+                text: "Start",
+                fontSize: 20,
+              },
+              link: null,
+            },
+            {
+              id: "Stop",
+              type: "rectangle",
+              groupIds: [],
+              x: 2.7109375,
+              y: 94,
+              width: 64.28125,
+              height: 44,
+              strokeWidth: 2,
+              label: {
+                groupIds: [],
+                text: "Stop",
+                fontSize: 20,
+              },
+              link: null,
+            },
+            {
+              id: "Start_Stop",
+              type: "arrow",
+              groupIds: [],
+              x: 34.852,
+              y: 44,
+              strokeWidth: 2,
+              points: [
+                [0, 0],
+                [0, 50],
+              ],
+              roundness: {
+                type: 2,
+              },
+              start: {
+                id: "Start",
+              },
+              end: {
+                id: "Stop",
+              },
+            },
+          ],
+        });
+      } else {
+        reject(new Error("ERROR"));
+      }
+    });
+  },
+);
+
+vi.spyOn(React, "useRef").mockReturnValue({
+  current: {
+    parseMermaidToExcalidraw: parseMermaidToExcalidrawSpy,
+  },
+});
+
+describe("Test <MermaidToExcalidraw/>", () => {
+  beforeEach(async () => {
+    await render(
+      <Excalidraw
+        initialData={{
+          appState: {
+            activeTool: {
+              type: "mermaid",
+              lastActiveTool: null,
+              locked: false,
+              customType: null,
+            },
+          },
+        }}
+      />,
+    );
+  });
+
+  it("should open mermaid popup when active tool is mermaid", async () => {
+    const dialog = document.querySelector(".dialog-mermaid")!;
+
+    expect(dialog.outerHTML).toMatchSnapshot();
+  });
+
+  it("should close the popup and set the tool to selection when close button clicked", () => {
+    const dialog = document.querySelector(".dialog-mermaid")!;
+    const closeBtn = dialog.querySelector(".Dialog__close")!;
+    fireEvent.click(closeBtn);
+    expect(document.querySelector(".dialog-mermaid")).toBe(null);
+    expect(window.h.state.activeTool).toStrictEqual({
+      customType: null,
+      lastActiveTool: null,
+      locked: false,
+      type: "selection",
+    });
+  });
+
+  it("should show error in preview when mermaid library throws error", async () => {
+    const dialog = document.querySelector(".dialog-mermaid")!;
+    const selector = ".mermaid-to-excalidraw-wrapper-text textarea";
+    const editor = getTextEditor(selector);
+
+    expect(dialog.querySelector('[data-testid="mermaid-error"]')).toBeNull();
+
+    expect(editor.textContent).toMatchInlineSnapshot(`
+      "flowchart TD
+       A[Christmas] -->|Get money| B(Go shopping)
+       B --> C{Let me think}
+       C -->|One| D[Laptop]
+       C -->|Two| E[iPhone]
+       C -->|Three| F[test]"
+    `);
+
+    await act(async () => {
+      updateTextEditor(editor, "flowchart TD1");
+      await new Promise((cb) => setTimeout(cb, 0));
+    });
+
+    expect(getTextEditor(selector).textContent).toBe("flowchart TD1");
+    expect(dialog.querySelector('[data-testid="mermaid-error"]'))
+      .toMatchInlineSnapshot(`
+        <div
+          data-testid="mermaid-error"
+          style="color: red; font-weight: 800; font-size: 30px; word-break: break-word; overflow: auto; max-height: 100%; text-align: center;"
+        >
+          Error! 
+          <p
+            style="font-size: 18px; font-weight: 600;"
+          >
+            ERROR
+          </p>
+        </div>
+      `);
+  });
+});

+ 10 - 0
src/tests/__snapshots__/MermaidToExcalidraw.test.tsx.snap

@@ -0,0 +1,10 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active tool is mermaid 1`] = `
+"<div class=\\"Modal Dialog dialog-mermaid\\" role=\\"dialog\\" aria-modal=\\"true\\" aria-labelledby=\\"dialog-title\\" data-prevent-outside-click=\\"true\\"><div class=\\"Modal__background\\"></div><div class=\\"Modal__content\\" style=\\"--max-width: 800px;\\" tabindex=\\"0\\"><div class=\\"Island\\"><h2 id=\\"test-id-dialog-title\\" class=\\"Dialog__title\\"><span class=\\"Dialog__titleContent\\"><p style=\\"margin-bottom: 5px; margin-top: 2px;\\">Mermaid to Excalidraw</p><span style=\\"font-size: 15px; font-style: italic; font-weight: 500;\\">Currently only <a href=\\"https://mermaid.js.org/syntax/flowchart.html\\">flowcharts</a> are supported. The other types will be rendered as image in Excalidraw.<br></span></span></h2><button class=\\"Dialog__close\\" title=\\"Close\\" aria-label=\\"Close\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><g clip-path=\\"url(#a)\\" stroke=\\"currentColor\\" stroke-width=\\"1.25\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"><path d=\\"M15 5 5 15M5 5l10 10\\"></path></g><defs><clipPath id=\\"a\\"><path fill=\\"#fff\\" d=\\"M0 0h20v20H0z\\"></path></clipPath></defs></svg></button><div class=\\"Dialog__content\\"><div class=\\"mermaid-to-excalidraw-wrapper\\"><div class=\\"mermaid-to-excalidraw-wrapper-text\\" style=\\"display: flex; flex-direction: column;\\"><label>Mermaid Syntax</label><textarea style=\\"padding: 0.85rem; border-radius: 8px; border: 1px solid #e4e4eb; white-space: pre-wrap;\\">flowchart TD
+ A[Christmas] --&gt;|Get money| B(Go shopping)
+ B --&gt; C{Let me think}
+ C --&gt;|One| D[Laptop]
+ C --&gt;|Two| E[iPhone]
+ C --&gt;|Three| F[test]</textarea></div><div class=\\"mermaid-to-excalidraw-wrapper-preview\\" style=\\"display: flex; flex-direction: column;\\"><label>Preview</label><div class=\\"mermaid-to-excalidraw-wrapper-preview-canvas\\"><div></div></div><button type=\\"button\\" class=\\"excalidraw-button mermaid-to-excalidraw-wrapper-preview-insert\\">Insert<span style=\\"padding-left: 8px; display: flex;\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" role=\\"img\\" viewBox=\\"0 0 20 20\\" class=\\"\\"><path d=\\"M4.16602 10H15.8327\\" stroke=\\"white\\" stroke-width=\\"1.25\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"></path><path d=\\"M12.5 13.3333L15.8333 10\\" stroke=\\"white\\" stroke-width=\\"1.25\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"></path><path d=\\"M12.5 6.66666L15.8333 9.99999\\" stroke=\\"white\\" stroke-width=\\"1.25\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\"></path></svg></span></button></div></div></div></div></div></div>"
+`;

+ 12 - 0
src/tests/test-utils.ts

@@ -265,3 +265,15 @@ expect.extend({
     };
   },
 });
+
+export const updateTextEditor = (
+  editor: HTMLTextAreaElement,
+  value: string,
+) => {
+  fireEvent.change(editor, { target: { value } });
+  editor.dispatchEvent(new Event("input"));
+};
+
+export const getTextEditor = (selector: string) => {
+  return document.querySelector(selector) as HTMLTextAreaElement;
+};