11import { render , screen , waitFor } from "@testing-library/react" ;
2+ import userEvent from "@testing-library/user-event" ;
23import { Preview } from "../components/Preview" ;
34import { api } from "../services/api" ;
5+ import { useEditorStore } from "../store/editorStore" ;
6+ import mermaid from "mermaid" ;
7+
8+ vi . mock ( "mermaid" , ( ) => ( {
9+ default : {
10+ initialize : vi . fn ( ) ,
11+ render : vi . fn ( ) ,
12+ } ,
13+ } ) ) ;
414
515// Mock the api module
616vi . mock ( "../services/api" , ( ) => ( {
@@ -13,6 +23,22 @@ describe("Preview", () => {
1323 beforeEach ( ( ) => {
1424 vi . clearAllMocks ( ) ;
1525 vi . mocked ( api . getPage ) . mockResolvedValue ( { path : "test.md" , content : "" } ) ;
26+
27+ // Ensure the preview uses deterministic content in tests.
28+ useEditorStore . setState ( {
29+ content : "" ,
30+ scrollSource : null ,
31+ editorScrollPercent : 0 ,
32+ previewScrollPercent : 0 ,
33+ } ) ;
34+
35+ // Provide CSS variables used by Mermaid theme initialization in jsdom.
36+ document . documentElement . style . setProperty ( "--bg-primary" , "#18181b" ) ;
37+ document . documentElement . style . setProperty ( "--bg-secondary" , "#27272a" ) ;
38+ document . documentElement . style . setProperty ( "--bg-tertiary" , "#1f1f23" ) ;
39+ document . documentElement . style . setProperty ( "--text-primary" , "#fafaf9" ) ;
40+ document . documentElement . style . setProperty ( "--border-color" , "#3f3f46" ) ;
41+ document . documentElement . style . setProperty ( "--accent-color" , "#818cf8" ) ;
1642 } ) ;
1743
1844 it ( "shows empty state when no page is selected" , ( ) => {
@@ -32,4 +58,47 @@ describe("Preview", () => {
3258 expect ( screen . queryByLabelText ( / l o a d i n g / i) ) . not . toBeInTheDocument ( ) ,
3359 ) ;
3460 } ) ;
61+
62+ it ( "shows Export to PDF in export menu" , async ( ) => {
63+ vi . mocked ( api . getPage ) . mockResolvedValueOnce ( { path : "test.md" , content : "Hello" } ) ;
64+ render ( < Preview pagePath = "test.md" /> ) ;
65+
66+ await waitFor ( ( ) => expect ( api . getPage ) . toHaveBeenCalledWith ( "test.md" ) ) ;
67+
68+ const user = userEvent . setup ( ) ;
69+ await user . click ( screen . getByRole ( "button" , { name : / e x p o r t o p t i o n s / i } ) ) ;
70+ expect ( screen . getByRole ( "menuitem" , { name : / e x p o r t t o p d f / i } ) ) . toBeInTheDocument ( ) ;
71+ } ) ;
72+
73+ it ( "renders Mermaid code fences and avoids rerender when code is unchanged" , async ( ) => {
74+ vi . mocked ( mermaid . render ) . mockResolvedValue ( {
75+ svg : '<svg data-testid="mmd"><g><text>OK</text></g></svg>' ,
76+ bindFunctions : undefined ,
77+ } as unknown as { svg : string ; bindFunctions ?: ( element : Element ) => void } ) ;
78+
79+ document . documentElement . setAttribute ( "data-theme" , "dark" ) ;
80+
81+ const markdown = [
82+ "```mermaid" ,
83+ "flowchart LR" ,
84+ " A --> B" ,
85+ "```" ,
86+ ] . join ( "\n" ) ;
87+
88+ // Prevent the async API load from overwriting the live editor content during the test.
89+ vi . mocked ( api . getPage ) . mockResolvedValueOnce ( { path : "test.md" , content : markdown } ) ;
90+
91+ useEditorStore . setState ( { content : markdown } ) ;
92+
93+ const { rerender } = render ( < Preview pagePath = "test.md" /> ) ;
94+
95+ await waitFor ( ( ) => expect ( vi . mocked ( mermaid . render ) ) . toHaveBeenCalledTimes ( 1 ) ) ;
96+ expect (
97+ await screen . findByRole ( "button" , { name : / d o w n l o a d d i a g r a m a s s v g / i } ) ,
98+ ) . toBeInTheDocument ( ) ;
99+
100+ // Rerender parent with same content: Mermaid should not redraw.
101+ rerender ( < Preview pagePath = "test.md" /> ) ;
102+ await waitFor ( ( ) => expect ( vi . mocked ( mermaid . render ) ) . toHaveBeenCalledTimes ( 1 ) ) ;
103+ } ) ;
35104} ) ;
0 commit comments