// @vitest-environment jsdom import { act } from "react"; import { createRoot } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MarkdownEditor } from "./MarkdownEditor"; const mdxEditorMockState = vi.hoisted(() => ({ emitMountEmptyReset: false, })); vi.mock("@mdxeditor/editor", async () => { const React = await import("react"); function setForwardedRef(ref: React.ForwardedRef, value: T | null) { if (typeof ref === "function") { ref(value); return; } if (ref) { (ref as React.MutableRefObject).current = value; } } const MDXEditor = React.forwardRef(function MockMDXEditor( { markdown, placeholder, onChange, }: { markdown: string; placeholder?: string; onChange?: (value: string) => void; }, forwardedRef: React.ForwardedRef<{ setMarkdown: (value: string) => void; focus: () => void } | null>, ) { const [content, setContent] = React.useState(markdown); const handle = React.useMemo(() => ({ setMarkdown: (value: string) => setContent(value), focus: () => {}, }), []); React.useEffect(() => { setForwardedRef(forwardedRef, null); const timer = window.setTimeout(() => { setForwardedRef(forwardedRef, handle); if (mdxEditorMockState.emitMountEmptyReset) { setContent(""); onChange?.(""); } }, 0); return () => { window.clearTimeout(timer); setForwardedRef(forwardedRef, null); }; }, []); return
{content || placeholder || ""}
; }); return { CodeMirrorEditor: () => null, MDXEditor, codeBlockPlugin: () => ({}), codeMirrorPlugin: () => ({}), createRootEditorSubscription$: Symbol("createRootEditorSubscription$"), headingsPlugin: () => ({}), imagePlugin: () => ({}), linkDialogPlugin: () => ({}), linkPlugin: () => ({}), listsPlugin: () => ({}), markdownShortcutPlugin: () => ({}), quotePlugin: () => ({}), realmPlugin: (plugin: unknown) => plugin, tablePlugin: () => ({}), thematicBreakPlugin: () => ({}), }; }); vi.mock("../lib/mention-deletion", () => ({ mentionDeletionPlugin: () => ({}), })); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; async function flush() { await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); } describe("MarkdownEditor", () => { let container: HTMLDivElement; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); }); afterEach(() => { container.remove(); vi.clearAllMocks(); mdxEditorMockState.emitMountEmptyReset = false; }); it("applies async external value updates once the editor ref becomes ready", async () => { const root = createRoot(container); await act(async () => { root.render( {}} placeholder="Markdown body" />, ); }); await act(async () => { root.render( {}} placeholder="Markdown body" />, ); }); await flush(); expect(container.textContent).toContain("Loaded plan body"); await act(async () => { root.unmount(); }); }); it("keeps the external value when the unfocused editor emits an empty mount reset", async () => { mdxEditorMockState.emitMountEmptyReset = true; const handleChange = vi.fn(); const root = createRoot(container); await act(async () => { root.render( , ); }); await flush(); expect(container.textContent).toContain("Loaded plan body"); expect(handleChange).not.toHaveBeenCalled(); await act(async () => { root.unmount(); }); }); });