| 1 | import fs from 'node:fs' |
| 2 | import os from 'node:os' |
| 3 | import path from 'node:path' |
| 4 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
| 5 | |
| 6 | const state = vi.hoisted(() => { |
| 7 | const handlers = new Map<string, (event: unknown, payload: unknown) => Promise<unknown>>() |
| 8 | return { |
| 9 | handlers, |
| 10 | ipcMain: { |
| 11 | handle: vi.fn( |
| 12 | (channel: string, handler: (event: unknown, payload: unknown) => Promise<unknown>) => { |
| 13 | handlers.set(channel, handler) |
| 14 | } |
| 15 | ) |
| 16 | }, |
| 17 | log: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, |
| 18 | normalizeImportedHtml: vi.fn(({ html }: { html: string }) => ({ |
| 19 | html, |
| 20 | designWidth: 1280, |
| 21 | title: '' |
| 22 | })) |
| 23 | } |
| 24 | }) |
| 25 | |
| 26 | vi.mock('electron', () => ({ |
| 27 | app: { isPackaged: false }, |
| 28 | BrowserWindow: { fromWebContents: vi.fn(), getFocusedWindow: vi.fn() }, |
| 29 | dialog: { showOpenDialog: vi.fn(), showSaveDialog: vi.fn() }, |
| 30 | ipcMain: state.ipcMain, |
| 31 | shell: { openPath: vi.fn(), showItemInFolder: vi.fn() } |
| 32 | })) |
| 33 | vi.mock('electron-log/main.js', () => ({ default: state.log })) |
| 34 | vi.mock('../../../src/main/html-editor/html-editor-import', () => ({ |
| 35 | normalizeImportedHtml: state.normalizeImportedHtml |
| 36 | })) |
| 37 | vi.mock('../../../src/main/html-editor/html-editor-git', () => ({ |
| 38 | commitHtmlFile: vi.fn(), |
| 39 | ensureHtmlRepo: vi.fn(), |
| 40 | getHtmlRepoHead: vi.fn(), |
| 41 | readHtmlAtCommit: vi.fn(), |
| 42 | restoreHtmlFileAtCommit: vi.fn(), |
| 43 | restoreHtmlRepoHead: vi.fn() |
| 44 | })) |
| 45 | vi.mock('../../../src/main/html-editor/html-editor-thumbnail', () => ({ |
| 46 | refreshHtmlEditorCoverThumbnail: vi.fn(), |
| 47 | warmHtmlEditorCoverThumbnails: vi.fn() |
| 48 | })) |
| 49 | vi.mock('../../../src/main/html-editor/html-editor-media', () => ({ |
| 50 | getHtmlEditorMediaExtensions: vi.fn(), |
| 51 | importHtmlEditorMedia: vi.fn(), |
| 52 | listHtmlEditorMedia: vi.fn() |
| 53 | })) |
| 54 | |
| 55 | describe('html-editor:openDocument', () => { |
| 56 | let storagePath = '' |
| 57 | |
| 58 | beforeEach(async () => { |
| 59 | vi.resetModules() |
| 60 | state.handlers.clear() |
| 61 | state.ipcMain.handle.mockClear() |
| 62 | state.normalizeImportedHtml.mockClear() |
| 63 | state.normalizeImportedHtml.mockImplementation(({ html }: { html: string }) => ({ |
| 64 | html, |
| 65 | designWidth: 1280, |
| 66 | title: '' |
| 67 | })) |
| 68 | storagePath = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'html-editor-open-')) |
| 69 | }) |
| 70 | |
| 71 | afterEach(async () => { |
| 72 | vi.restoreAllMocks() |
| 73 | if (storagePath) await fs.promises.rm(storagePath, { recursive: true, force: true }) |
| 74 | }) |
| 75 | |
| 76 | it('reuses an unchanged disk version and revalidates after the file changes', async () => { |
| 77 | const docId = 'doc-1' |
| 78 | const workspaceDir = path.join(storagePath, 'html-editor', docId) |
| 79 | const htmlPath = path.join(workspaceDir, 'current.html') |
| 80 | await fs.promises.mkdir(workspaceDir, { recursive: true }) |
| 81 | await fs.promises.writeFile(htmlPath, '<html><body>first</body></html>', 'utf-8') |
| 82 | |
| 83 | const { registerHtmlEditorHandlers } = |
| 84 | await import('../../../src/main/html-editor/html-editor-handlers') |
| 85 | registerHtmlEditorHandlers({ |
| 86 | mainWindow: null, |
| 87 | db: { |
| 88 | getHtmlEditDocument: vi.fn().mockResolvedValue({ |
| 89 | id: docId, |
| 90 | title: 'Document', |
| 91 | sourcePath: null, |
| 92 | htmlPath, |
| 93 | designWidth: 1280 |
| 94 | }) |
| 95 | }, |
| 96 | resolveStoragePath: vi.fn().mockResolvedValue(storagePath) |
| 97 | } as never) |
| 98 | |
| 99 | const handler = state.handlers.get('html-editor:openDocument') |
| 100 | expect(handler).toBeDefined() |
| 101 | const readFileSpy = vi.spyOn(fs.promises, 'readFile') |
| 102 | |
| 103 | await handler!({}, { docId }) |
| 104 | await handler!({}, { docId }) |
| 105 | |
| 106 | expect(state.normalizeImportedHtml).toHaveBeenCalledTimes(1) |
| 107 | expect(readFileSpy.mock.calls.filter(([filePath]) => filePath === htmlPath)).toHaveLength(1) |
| 108 | |
| 109 | await fs.promises.writeFile(htmlPath, '<html><body>second version</body></html>', 'utf-8') |
| 110 | await handler!({}, { docId }) |
| 111 | |
| 112 | expect(state.normalizeImportedHtml).toHaveBeenCalledTimes(2) |
| 113 | expect(readFileSpy.mock.calls.filter(([filePath]) => filePath === htmlPath)).toHaveLength(2) |
| 114 | }) |
| 115 | }) |
| 116 |