mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): MermaidLazy (loads mermaid on first use, error UI)
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mermaidModule: typeof import('mermaid').default | null = null;
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
async function getMermaid() {
|
||||||
|
if (!mermaidModule) {
|
||||||
|
const mod = await import('mermaid');
|
||||||
|
mermaidModule = mod.default;
|
||||||
|
}
|
||||||
|
if (!initialized) {
|
||||||
|
mermaidModule.initialize({ startOnLoad: false, theme: 'default' });
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
return mermaidModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MermaidLazy({ code }: Props) {
|
||||||
|
const [svg, setSvg] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const idRef = useRef(`mermaid-${Math.random().toString(36).slice(2)}`);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
getMermaid()
|
||||||
|
.then((m) => m.render(idRef.current, code))
|
||||||
|
.then((rendered) => {
|
||||||
|
if (!cancelled) setSvg(rendered);
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
if (!cancelled) setError(err.message);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [code]);
|
||||||
|
|
||||||
|
if (error) return <div className="rounded border border-destructive/40 bg-destructive/5 p-2 text-xs text-destructive">{error}</div>;
|
||||||
|
if (!svg) return <div className="text-xs text-muted-foreground">Loading diagram…</div>;
|
||||||
|
return <div data-testid="mermaid-output" dangerouslySetInnerHTML={{ __html: svg }} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { MermaidLazy } from '@/components/preview/MermaidLazy';
|
||||||
|
|
||||||
|
vi.mock('mermaid', () => ({
|
||||||
|
default: {
|
||||||
|
initialize: vi.fn(),
|
||||||
|
render: vi.fn().mockResolvedValue('<svg></svg>'),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('MermaidLazy', () => {
|
||||||
|
it('renders an svg after mermaid resolves', async () => {
|
||||||
|
render(<MermaidLazy code="graph TD; A-->B" />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('mermaid-output').innerHTML).toContain('<svg');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error message if mermaid throws', async () => {
|
||||||
|
const mermaid = (await import('mermaid')).default as any;
|
||||||
|
mermaid.render.mockRejectedValueOnce(new Error('mermaid failed'));
|
||||||
|
render(<MermaidLazy code="bad code" />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/mermaid failed/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user