| 1 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 2 | |
| 3 | const state = vi.hoisted(() => { |
| 4 | const handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>() |
| 5 | return { |
| 6 | handlers, |
| 7 | generateStylePreviewHtml: vi.fn(), |
| 8 | getStylePackageDirectory: vi.fn(), |
| 9 | saveGeneratedStylePreview: vi.fn(), |
| 10 | enqueueHtmlThumbnail: vi.fn(), |
| 11 | waitForHtmlThumbnailTask: vi.fn(), |
| 12 | resolveGlobalModelTimeouts: vi.fn(), |
| 13 | resolveModelConfigForTask: vi.fn(), |
| 14 | ipcMain: { |
| 15 | handle: vi.fn((channel: string, handler: (...args: unknown[]) => Promise<unknown>) => { |
| 16 | handlers.set(channel, handler) |
| 17 | }) |
| 18 | } |
| 19 | } |
| 20 | }) |
| 21 | |
| 22 | vi.mock('electron', () => ({ ipcMain: state.ipcMain })) |
| 23 | vi.mock('electron-log/main.js', () => ({ default: { info: vi.fn() } })) |
| 24 | vi.mock('../../../src/main/styles/catalog', () => ({ |
| 25 | getStylePackageDirectory: state.getStylePackageDirectory, |
| 26 | saveGeneratedStylePreview: state.saveGeneratedStylePreview |
| 27 | })) |
| 28 | vi.mock('../../../src/main/styles/preview/generator', () => ({ |
| 29 | generateStylePreviewHtml: state.generateStylePreviewHtml |
| 30 | })) |
| 31 | vi.mock('../../../src/main/io/thumbnails/html-thumbnail-service', () => ({ |
| 32 | enqueueHtmlThumbnail: state.enqueueHtmlThumbnail, |
| 33 | waitForHtmlThumbnailTask: state.waitForHtmlThumbnailTask |
| 34 | })) |
| 35 | vi.mock('../../../src/main/config/model-config-utils', () => ({ |
| 36 | resolveGlobalModelTimeouts: state.resolveGlobalModelTimeouts, |
| 37 | resolveModelConfigForTask: state.resolveModelConfigForTask |
| 38 | })) |
| 39 | |
| 40 | describe('registerStylePreviewHandlers', () => { |
| 41 | beforeEach(() => { |
| 42 | vi.resetModules() |
| 43 | state.handlers.clear() |
| 44 | state.ipcMain.handle.mockClear() |
| 45 | state.generateStylePreviewHtml.mockReset() |
| 46 | state.getStylePackageDirectory.mockReset() |
| 47 | state.saveGeneratedStylePreview.mockReset() |
| 48 | state.enqueueHtmlThumbnail.mockReset() |
| 49 | state.waitForHtmlThumbnailTask.mockReset() |
| 50 | state.resolveGlobalModelTimeouts.mockReset() |
| 51 | state.resolveModelConfigForTask.mockReset() |
| 52 | }) |
| 53 | |
| 54 | it('registers an independent IPC channel and generates the requested style preview', async () => { |
| 55 | state.getStylePackageDirectory.mockReturnValue('/styles/user/paper-story') |
| 56 | state.resolveModelConfigForTask.mockResolvedValue({ |
| 57 | provider: 'openai', |
| 58 | apiKey: 'key', |
| 59 | model: 'model', |
| 60 | baseUrl: '', |
| 61 | maxTokens: 4096 |
| 62 | }) |
| 63 | state.resolveGlobalModelTimeouts.mockResolvedValue({ document: 600000 }) |
| 64 | state.generateStylePreviewHtml.mockResolvedValue('<!doctype html><html></html>') |
| 65 | state.saveGeneratedStylePreview.mockResolvedValue({ |
| 66 | previewPath: '/styles/user/paper-story/preview.html' |
| 67 | }) |
| 68 | state.enqueueHtmlThumbnail.mockResolvedValue({ |
| 69 | resourceType: 'style', |
| 70 | resourceId: 'paper-story', |
| 71 | variant: 'default', |
| 72 | status: 'queued', |
| 73 | thumbnailPath: null |
| 74 | }) |
| 75 | state.waitForHtmlThumbnailTask.mockResolvedValue({ |
| 76 | resourceType: 'style', |
| 77 | resourceId: 'paper-story', |
| 78 | variant: 'default', |
| 79 | status: 'completed', |
| 80 | thumbnailPath: '/thumbnail-cache/paper-story.png' |
| 81 | }) |
| 82 | |
| 83 | const { registerStylePreviewHandlers } = |
| 84 | await import('../../../src/main/styles/preview/handlers') |
| 85 | registerStylePreviewHandlers({ db: {}, decryptApiKey: vi.fn() } as never) |
| 86 | |
| 87 | const handler = state.handlers.get('styles:generatePreview') |
| 88 | const result = await handler?.({}, { styleId: 'paper-story' }) |
| 89 | |
| 90 | expect(result).toEqual({ |
| 91 | success: true, |
| 92 | previewPath: '/styles/user/paper-story/preview.html', |
| 93 | thumbnailPath: '/thumbnail-cache/paper-story.png' |
| 94 | }) |
| 95 | expect(state.resolveModelConfigForTask).toHaveBeenCalledWith( |
| 96 | expect.anything(), |
| 97 | expect.objectContaining({ purpose: 'styles:generatePreview' }) |
| 98 | ) |
| 99 | expect(state.generateStylePreviewHtml).toHaveBeenCalledWith( |
| 100 | expect.objectContaining({ |
| 101 | styleId: 'paper-story', |
| 102 | stylePackageDir: '/styles/user/paper-story', |
| 103 | modelTimeoutMs: 600000 |
| 104 | }) |
| 105 | ) |
| 106 | expect(state.saveGeneratedStylePreview).toHaveBeenCalledWith( |
| 107 | 'paper-story', |
| 108 | '<!doctype html><html></html>' |
| 109 | ) |
| 110 | expect(state.enqueueHtmlThumbnail).toHaveBeenCalledWith( |
| 111 | { |
| 112 | resourceType: 'style', |
| 113 | resourceId: 'paper-story', |
| 114 | sourcePath: '/styles/user/paper-story/preview.html' |
| 115 | }, |
| 116 | { force: true } |
| 117 | ) |
| 118 | expect(state.waitForHtmlThumbnailTask).toHaveBeenCalledWith('style', 'paper-story') |
| 119 | }) |
| 120 | }) |
| 121 |