Files
kandev/apps/web/components/shared/mermaid-block.test.tsx
T
José Almeida a2d98f5d3e fix(web): replace mermaid bomb-icon error flood with toast notifications (#594)
* fix(web): replace mermaid bomb-icon error flood with toast notifications

mermaid.render() called without a container leaves orphaned error
elements (with bomb icons) in document.body on parse failures. Clean up
these orphans after every render and show an error toast instead.

* fix(web): address PR review — prevent toast flooding, include error detail, add hook tests

- Add latestRenderId guard in TipTap MermaidNodeView to skip stale
  renders, preventing toast spam on every keystroke
- Include mermaid error message in toast description via CustomEvent detail
- Add useMermaidErrorToast hook tests (event listener + cleanup)

* style(web): format mermaid-error-toast test with prettier

* fix(web): include error message in MermaidBlock toast description
2026-04-15 21:47:42 +01:00

54 lines
1.8 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { MermaidBlock } from "./mermaid-block";
import { cleanupMermaidOrphans } from "./mermaid-utils";
vi.mock("next-themes", () => ({
useTheme: () => ({ resolvedTheme: "dark" }),
}));
vi.mock("@/components/toast-provider", () => ({
useToast: () => ({ toast: vi.fn(), updateToast: vi.fn(), dismissToast: vi.fn() }),
}));
describe("MermaidBlock", () => {
it("renders a constrained outer container with a horizontal scroll viewport", () => {
const html = renderToStaticMarkup(<MermaidBlock code={"graph LR\nA-->B"} />);
expect(html).toContain("mermaid-block");
expect(html).toContain("block w-full max-w-full min-w-0");
expect(html).toContain("mermaid-scroll-region w-full overflow-x-auto overflow-y-hidden");
expect(html).not.toContain("inline-block");
});
});
const RENDER_ID_1 = "mermaid-test-1";
const RENDER_ID_2 = "mermaid-test-2";
const D_RENDER_ID_2 = `d${RENDER_ID_2}`;
describe("cleanupMermaidOrphans", () => {
it("removes elements matching the render id", () => {
const el = document.createElement("div");
el.id = RENDER_ID_1;
document.body.appendChild(el);
expect(document.getElementById(RENDER_ID_1)).not.toBeNull();
cleanupMermaidOrphans(RENDER_ID_1);
expect(document.getElementById(RENDER_ID_1)).toBeNull();
});
it("removes elements matching the d-prefixed id", () => {
const el = document.createElement("div");
el.id = D_RENDER_ID_2;
document.body.appendChild(el);
expect(document.getElementById(D_RENDER_ID_2)).not.toBeNull();
cleanupMermaidOrphans(RENDER_ID_2);
expect(document.getElementById(D_RENDER_ID_2)).toBeNull();
});
it("does nothing when no matching elements exist", () => {
expect(() => cleanupMermaidOrphans("nonexistent-id")).not.toThrow();
});
});