| 1 | import { afterEach, describe, expect, it, vi } from 'vitest' |
| 2 | import fs from 'fs' |
| 3 | import os from 'os' |
| 4 | import path from 'path' |
| 5 | import type { IpcContext } from '../../../src/main/ipc/context' |
| 6 | import { persistManagedPages, type ManagedPage } from '../../../src/main/session/page-management-service' |
| 7 | |
| 8 | const mocks = vi.hoisted(() => ({ |
| 9 | ensureSessionRuntimeCompatible: vi.fn(), |
| 10 | buildProjectIndexHtml: vi.fn(() => '<html><body>new index</body></html>'), |
| 11 | carryIndexTransitionConfig: vi.fn((_previous: string, next: string) => next) |
| 12 | })) |
| 13 | |
| 14 | vi.mock('../../../src/main/session/runtime-assets', () => ({ |
| 15 | ensureSessionRuntimeCompatible: mocks.ensureSessionRuntimeCompatible |
| 16 | })) |
| 17 | |
| 18 | vi.mock('../../../src/main/session/template-builder', () => ({ |
| 19 | buildProjectIndexHtml: mocks.buildProjectIndexHtml |
| 20 | })) |
| 21 | |
| 22 | vi.mock('../../../src/main/session/index-transition', () => ({ |
| 23 | carryIndexTransitionConfig: mocks.carryIndexTransitionConfig |
| 24 | })) |
| 25 | |
| 26 | const createdDirectories: string[] = [] |
| 27 | |
| 28 | const createProjectDirectory = async (): Promise<string> => { |
| 29 | const projectDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'ohmyppt-page-management-')) |
| 30 | createdDirectories.push(projectDir) |
| 31 | return projectDir |
| 32 | } |
| 33 | |
| 34 | afterEach(async () => { |
| 35 | await Promise.all(createdDirectories.splice(0).map((directory) => fs.promises.rm(directory, { recursive: true, force: true }))) |
| 36 | vi.clearAllMocks() |
| 37 | }) |
| 38 | |
| 39 | describe('persistManagedPages', () => { |
| 40 | it('restores page HTML when page-order persistence fails', async () => { |
| 41 | const projectDir = await createProjectDirectory() |
| 42 | const firstPath = path.join(projectDir, 'page-first.html') |
| 43 | const secondPath = path.join(projectDir, 'page-second.html') |
| 44 | const firstHtml = '<html><head></head><body><main class="ppt-page-root" data-ppt-guard-root="1"></main></body></html>' |
| 45 | const secondHtml = '<html><head></head><body><main class="ppt-page-root" data-ppt-guard-root="1"></main></body></html>' |
| 46 | await Promise.all([ |
| 47 | fs.promises.writeFile(firstPath, firstHtml, 'utf-8'), |
| 48 | fs.promises.writeFile(secondPath, secondHtml, 'utf-8') |
| 49 | ]) |
| 50 | const context = { |
| 51 | db: { |
| 52 | getSession: vi.fn().mockResolvedValue({ |
| 53 | slideSizeId: 'wide-16-9', |
| 54 | slideWidth: 1600, |
| 55 | slideHeight: 900, |
| 56 | metadata: '{}' |
| 57 | }), |
| 58 | persistSessionPageState: vi.fn().mockRejectedValue(new Error('database unavailable')) |
| 59 | } |
| 60 | } as unknown as IpcContext |
| 61 | const pages: ManagedPage[] = [ |
| 62 | { id: 'second', pageId: 'page-second', pageNumber: 2, title: 'Second', htmlPath: secondPath }, |
| 63 | { id: 'first', pageId: 'page-first', pageNumber: 1, title: 'First', htmlPath: firstPath } |
| 64 | ] |
| 65 | |
| 66 | await expect( |
| 67 | persistManagedPages(context, { |
| 68 | sessionId: 'session-1', |
| 69 | projectDir, |
| 70 | indexPath: path.join(projectDir, 'index.html'), |
| 71 | deckTitle: 'Deck', |
| 72 | pages, |
| 73 | operation: 'reorder', |
| 74 | prompt: 'reorder' |
| 75 | }) |
| 76 | ).rejects.toThrow('database unavailable') |
| 77 | |
| 78 | await expect(fs.promises.readFile(firstPath, 'utf-8')).resolves.toBe(firstHtml) |
| 79 | await expect(fs.promises.readFile(secondPath, 'utf-8')).resolves.toBe(secondHtml) |
| 80 | await expect(fs.promises.access(path.join(projectDir, 'index.html.tmp'))).rejects.toThrow() |
| 81 | }) |
| 82 | }) |
| 83 |