| 1 | import { describe, expect, it, vi } from 'vitest' |
| 2 | |
| 3 | vi.mock('electron', () => ({ app: { getPath: () => '/tmp' }, session: { defaultSession: {} } })) |
| 4 | vi.mock('@electron-toolkit/utils', () => ({ is: { dev: false, main: true } })) |
| 5 | |
| 6 | import { replacePageContentFragment } from '../../../src/main/presentation/html/page-writer-core' |
| 7 | |
| 8 | const SHELL_HTML = `<!doctype html> |
| 9 | <html lang="zh-CN"> |
| 10 | <head> |
| 11 | <meta charset="UTF-8" /> |
| 12 | <title>Target</title> |
| 13 | <link rel="stylesheet" href="./assets/fonts/inter.css" /> |
| 14 | <style id="ppt-page-guard-style">:root { --ppt-page-bg: #ffffff; }</style> |
| 15 | </head> |
| 16 | <body data-page-id="page-1"> |
| 17 | <main class="ppt-page-root" data-ppt-guard-root="1" data-ppt-slide-size-id="wide-16-9" data-ppt-width="1600" data-ppt-height="900"> |
| 18 | <div class="ppt-page-fit-scope"> |
| 19 | <div class="ppt-page-content"><h1>Target</h1></div> |
| 20 | </div> |
| 21 | </main> |
| 22 | <script src="./assets/ppt-page-fit.js"></script> |
| 23 | <script>window.__ppt = { pageId: 'page-1' }</script> |
| 24 | </body> |
| 25 | </html>` |
| 26 | |
| 27 | describe('replacePageContentFragment', () => { |
| 28 | it('swaps only .ppt-page-content and preserves the shell, head, fonts, and runtime scripts', () => { |
| 29 | const fragment = '<section class="grid"><h1>Target</h1><p>subtitle</p></section>' |
| 30 | const { html, content, repaired } = replacePageContentFragment({ |
| 31 | originalHtml: SHELL_HTML, |
| 32 | content: fragment, |
| 33 | pageId: 'page-1' |
| 34 | }) |
| 35 | |
| 36 | expect(content).toBe(fragment) |
| 37 | expect(repaired).toBe(false) |
| 38 | // Head, fonts, CSS, runtime scripts are untouched. |
| 39 | expect(html).toContain('<title>Target</title>') |
| 40 | expect(html).toContain('./assets/fonts/inter.css') |
| 41 | expect(html).toContain('ppt-page-guard-style') |
| 42 | expect(html).toContain('./assets/ppt-page-fit.js') |
| 43 | expect(html).toContain("window.__ppt = { pageId: 'page-1' }") |
| 44 | // Shell attributes on .ppt-page-root are preserved. |
| 45 | expect(html).toContain('data-ppt-slide-size-id="wide-16-9"') |
| 46 | expect(html).toContain('data-ppt-width="1600"') |
| 47 | expect(html).toContain('data-ppt-height="900"') |
| 48 | // The original inner fragment is gone, the new one is in place (the |
| 49 | // normalizer wraps it in a scaffold and decorates block-level children). |
| 50 | expect(html).not.toContain('<div class="ppt-page-content"><h1>Target</h1></div>') |
| 51 | expect(html).toContain('<div class="ppt-page-content">') |
| 52 | expect(html).toContain('<section class="grid">') |
| 53 | expect(html).toContain('subtitle</p>') |
| 54 | }) |
| 55 | |
| 56 | it('rejects fragments that reference remote CDN resources', () => { |
| 57 | expect(() => |
| 58 | replacePageContentFragment({ |
| 59 | originalHtml: SHELL_HTML, |
| 60 | content: '<section><script src="https://cdn.example/lib.js"></script></section>', |
| 61 | pageId: 'page-1' |
| 62 | }) |
| 63 | ).toThrow(/CDN\/远程资源引用/) |
| 64 | }) |
| 65 | |
| 66 | it('strips model-authored block ids before persisted validation', () => { |
| 67 | const { html } = replacePageContentFragment({ |
| 68 | originalHtml: SHELL_HTML, |
| 69 | content: ` |
| 70 | <section data-block-id="select-arcsin1-5kQfdkFj"><h1>Target</h1></section> |
| 71 | <section data-block-id="select-arcsin1-5kQfdkFj"><p>subtitle</p></section> |
| 72 | `, |
| 73 | pageId: 'page-1' |
| 74 | }) |
| 75 | |
| 76 | expect(html).not.toContain('data-block-id=') |
| 77 | }) |
| 78 | |
| 79 | it('throws when the original page no longer carries the ppt-page-content container', () => { |
| 80 | expect(() => |
| 81 | replacePageContentFragment({ |
| 82 | originalHtml: '<!doctype html><html><body><main class="something-else"></main></body></html>', |
| 83 | content: '<h1>Target</h1>', |
| 84 | pageId: 'page-1' |
| 85 | }) |
| 86 | ).toThrow(/页面主体容器/) |
| 87 | }) |
| 88 | }) |
| 89 |