| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { |
| 3 | DEFAULT_INDEX_TRANSITION_CONFIG, |
| 4 | carryIndexTransitionConfig, |
| 5 | ensureIndexAnimeScript, |
| 6 | ensureIndexPresentBackgroundStyle, |
| 7 | parseIndexTransitionConfig, |
| 8 | patchIndexTransitionConfig, |
| 9 | validateIndexShellHtml |
| 10 | } from '../../../src/main/session/index-transition' |
| 11 | |
| 12 | const baseIndexHtml = `<!doctype html> |
| 13 | <html lang="zh-CN"> |
| 14 | <head> |
| 15 | <meta charset="UTF-8" /> |
| 16 | <title>Deck</title> |
| 17 | </head> |
| 18 | <body> |
| 19 | <div id="frameViewport"><iframe class="ppt-preview-frame"></iframe></div> |
| 20 | <div class="ppt-controls"></div> |
| 21 | <script type="application/json" id="pages-data">[]</script> |
| 22 | <script src="./assets/index-runtime.js"></script> |
| 23 | </body> |
| 24 | </html>` |
| 25 | |
| 26 | describe('index transition config patching', () => { |
| 27 | it('inserts anime before index-runtime and writes normalized config', () => { |
| 28 | const next = patchIndexTransitionConfig(baseIndexHtml, { |
| 29 | type: 'flip', |
| 30 | durationMs: 520 |
| 31 | }) |
| 32 | |
| 33 | expect(next).toContain('<script id="ppt-index-transition-config" type="application/json">{"type":"flip","durationMs":520}</script>') |
| 34 | expect(next.indexOf('./assets/anime.v4.js')).toBeLessThan( |
| 35 | next.indexOf('./assets/index-runtime.js') |
| 36 | ) |
| 37 | expect(validateIndexShellHtml(next)).toEqual([]) |
| 38 | }) |
| 39 | |
| 40 | it('removes old CSS transition style and old config', () => { |
| 41 | const oldHtml = baseIndexHtml.replace( |
| 42 | '<script src="./assets/index-runtime.js"></script>', |
| 43 | `<style id="ppt-index-transition-style">.ppt-preview-frame{transition:opacity 420ms}</style> |
| 44 | <script id="ppt-index-transition-config" type="application/json">{"type":"zoom","durationMs":420}</script> |
| 45 | <script src="./assets/index-runtime.js"></script>` |
| 46 | ) |
| 47 | const next = patchIndexTransitionConfig(oldHtml, { |
| 48 | type: 'stack', |
| 49 | durationMs: 480 |
| 50 | }) |
| 51 | |
| 52 | expect(next).not.toContain('ppt-index-transition-style') |
| 53 | expect(next.match(/ppt-index-transition-config/g)).toHaveLength(1) |
| 54 | expect(parseIndexTransitionConfig(next)).toEqual({ type: 'stack', durationMs: 480 }) |
| 55 | }) |
| 56 | |
| 57 | it('keeps none at 0ms instead of clamping to the minimum duration', () => { |
| 58 | const next = patchIndexTransitionConfig(baseIndexHtml, { |
| 59 | type: 'none', |
| 60 | durationMs: 999 |
| 61 | }) |
| 62 | |
| 63 | expect(parseIndexTransitionConfig(next)).toEqual({ type: 'none', durationMs: 0 }) |
| 64 | }) |
| 65 | |
| 66 | it('writes distinctive transition types such as center reveal', () => { |
| 67 | const next = patchIndexTransitionConfig(baseIndexHtml, { |
| 68 | type: 'center-reveal', |
| 69 | durationMs: 580 |
| 70 | }) |
| 71 | |
| 72 | expect(parseIndexTransitionConfig(next)).toEqual({ type: 'center-reveal', durationMs: 580 }) |
| 73 | }) |
| 74 | |
| 75 | it('does not duplicate anime script', () => { |
| 76 | const withAnime = ensureIndexAnimeScript(baseIndexHtml) |
| 77 | const next = ensureIndexAnimeScript(withAnime) |
| 78 | |
| 79 | expect(next.match(/anime\.v4\.js/g)).toHaveLength(1) |
| 80 | }) |
| 81 | |
| 82 | it('adds present-mode black background styles without duplication', () => { |
| 83 | const withPresentBackground = ensureIndexPresentBackgroundStyle(baseIndexHtml) |
| 84 | const next = ensureIndexPresentBackgroundStyle(withPresentBackground) |
| 85 | |
| 86 | expect(next.match(/ppt-present-background-style/g)).toHaveLength(1) |
| 87 | expect(next).toContain('body.present { background: #000000 !important; }') |
| 88 | expect(next).toContain('body.present .ppt-preview-viewport') |
| 89 | expect(next.indexOf('ppt-present-background-style')).toBeLessThan(next.indexOf('</head>')) |
| 90 | }) |
| 91 | |
| 92 | it('falls back to default config when JSON is missing or invalid', () => { |
| 93 | expect(parseIndexTransitionConfig(baseIndexHtml)).toEqual(DEFAULT_INDEX_TRANSITION_CONFIG) |
| 94 | expect( |
| 95 | parseIndexTransitionConfig( |
| 96 | baseIndexHtml.replace( |
| 97 | '<script src="./assets/index-runtime.js"></script>', |
| 98 | '<script id="ppt-index-transition-config" type="application/json">{bad}</script><script src="./assets/index-runtime.js"></script>' |
| 99 | ) |
| 100 | ) |
| 101 | ).toEqual(DEFAULT_INDEX_TRANSITION_CONFIG) |
| 102 | }) |
| 103 | |
| 104 | it('carries old transition config into rebuilt index html', () => { |
| 105 | const previous = patchIndexTransitionConfig(baseIndexHtml, { |
| 106 | type: 'slide-up', |
| 107 | durationMs: 420 |
| 108 | }) |
| 109 | const rebuilt = baseIndexHtml.replace('Deck', 'Renamed Deck') |
| 110 | const next = carryIndexTransitionConfig(previous, rebuilt) |
| 111 | |
| 112 | expect(next).toContain('Renamed Deck') |
| 113 | expect(parseIndexTransitionConfig(next)).toEqual({ type: 'slide-up', durationMs: 420 }) |
| 114 | }) |
| 115 | }) |
| 116 |