返回 oh-my-ppt
session-importer.test.ts
根目录 / tests / unit / session-import / session-importer.test.ts
1 import fs from 'fs'
2 import os from 'os'
3 import path from 'path'
4 import { afterEach, describe, expect, it, vi } from 'vitest'
5 import { strToU8, zipSync } from 'fflate'
6 import { requireSlideSizePreset } from '../../../src/shared/slide-size'
7
8 const mocks = vi.hoisted(() => ({
9 recordHistoryOperationStrict: vi.fn(),
10 logInfo: vi.fn(),
11 logWarn: vi.fn(),
12 logError: vi.fn()
13 }))
14
15 vi.mock('electron-log/main.js', () => ({
16 default: {
17 info: mocks.logInfo,
18 warn: mocks.logWarn,
19 error: mocks.logError
20 }
21 }))
22
23 vi.mock('../../../src/main/history/git-history-service', () => ({
24 recordHistoryOperationStrict: mocks.recordHistoryOperationStrict
25 }))
26
27 vi.mock('../../../src/main/session/template-builder', () => ({
28 buildProjectIndexHtml: (
29 title: string,
30 pages: Array<{ pageNumber: number; pageId: string; title: string; htmlPath: string }>,
31 slideSize: { id: string; width: number; height: number }
32 ) => `<!doctype html>
33 <html lang="zh-CN">
34 <head>
35 <meta charset="UTF-8" />
36 <title>${title} · Preview</title>
37 </head>
38 <body>
39 <script type="application/json" id="pages-data">${JSON.stringify(
40 pages.map((page) => ({
41 pageNumber: page.pageNumber,
42 pageId: page.pageId,
43 title: page.title,
44 htmlPath: page.htmlPath
45 }))
46 )}</script>
47 <script type="application/json" id="deck-metadata">${JSON.stringify({
48 slideSizeId: slideSize.id,
49 width: slideSize.width,
50 height: slideSize.height
51 })}</script>
52 </body>
53 </html>`,
54 extractPagesDataFromIndex: (html: string) => {
55 const pagesMatch = html.match(
56 /<script type="application\/json" id="pages-data">([\s\S]*?)<\/script>/i
57 )
58 if (!pagesMatch?.[1]) return []
59 return JSON.parse(pagesMatch[1]) as Array<{
60 pageNumber: number
61 pageId: string
62 title: string
63 htmlPath: string
64 }>
65 }
66 }))
67
68 vi.mock('../../../src/main/styles/catalog', () => ({
69 resolveUsableStyleId: () => 'minimal-white'
70 }))
71
72 import { importSessionFile } from '../../../src/main/session-import/session-importer'
73
74 describe('importSessionFile', () => {
75 const roots: string[] = []
76
77 afterEach(async () => {
78 vi.clearAllMocks()
79 for (const root of roots.splice(0)) {
80 await fs.promises.rm(root, { recursive: true, force: true })
81 }
82 })
83
84 it('persists the slide size recovered from imported deck metadata', async () => {
85 const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'session-importer-test-'))
86 roots.push(root)
87 const storageDir = path.join(root, 'storage')
88 const zipPath = path.join(root, 'square-session.zip')
89 const slideSize = requireSlideSizePreset('square-1-1')
90 const indexHtml = `<!doctype html>
91 <html lang="zh-CN">
92 <head>
93 <meta charset="UTF-8" />
94 <title>Square import</title>
95 </head>
96 <body>
97 <script type="application/json" id="pages-data">${JSON.stringify([
98 {
99 pageNumber: 1,
100 pageId: 'page-1',
101 title: 'Square page',
102 htmlPath: 'page-1.html'
103 }
104 ])}</script>
105 <script type="application/json" id="deck-metadata">${JSON.stringify({
106 slideSizeId: slideSize.id,
107 width: slideSize.width,
108 height: slideSize.height
109 })}</script>
110 </body>
111 </html>`
112 await fs.promises.writeFile(
113 zipPath,
114 Buffer.from(
115 zipSync({
116 'index.html': strToU8(indexHtml),
117 'page-1.html': strToU8(
118 '<!doctype html><html><body><main class="ppt-page-root">Square</main></body></html>'
119 )
120 })
121 )
122 )
123
124 let sessionRecord: Record<string, unknown> | null = null
125 let projectRecord: Record<string, unknown> | null = null
126 let generationRun: Record<string, unknown> | null = null
127 const sessionPages: Array<Record<string, unknown>> = []
128 const db = {
129 createSession: vi.fn(async (data: Record<string, unknown>) => {
130 sessionRecord = {
131 ...data,
132 status: 'active',
133 currentCommit: 'commit-1'
134 }
135 return String(data.id)
136 }),
137 updateSessionDesignContract: vi.fn(),
138 createProject: vi.fn(async (data: Record<string, unknown>) => {
139 projectRecord = { id: 'project-1', ...data }
140 return 'project-1'
141 }),
142 createGenerationRun: vi.fn(async (data: Record<string, unknown>) => {
143 generationRun = { id: 'run-1', ...data, status: 'running' }
144 return 'run-1'
145 }),
146 upsertGenerationPage: vi.fn(),
147 upsertSessionPage: vi.fn(async (data: Record<string, unknown>) => {
148 sessionPages.push({
149 ...data,
150 html_path: data.htmlPath,
151 status: data.status
152 })
153 }),
154 updateGenerationRunStatus: vi.fn(async (_runId: string, status: string) => {
155 generationRun = generationRun ? { ...generationRun, status } : generationRun
156 }),
157 updateSessionMetadata: vi.fn(),
158 updateProjectStatus: vi.fn(),
159 updateSessionStatus: vi.fn(async (_sessionId: string, status: string) => {
160 sessionRecord = sessionRecord ? { ...sessionRecord, status } : sessionRecord
161 }),
162 getSession: vi.fn(async () => sessionRecord),
163 getProject: vi.fn(async () => ({
164 id: projectRecord?.id,
165 root_path: projectRecord?.root_path
166 })),
167 listSessionPages: vi.fn(async () => sessionPages),
168 getLatestGenerationRun: vi.fn(async () => generationRun),
169 listSessionOperations: vi.fn(async () => [{ id: 'op-1', type: 'import' }]),
170 deleteSession: vi.fn()
171 }
172 const ctx = {
173 db,
174 resolveStoragePath: vi.fn(async () => storageDir),
175 ensureSessionAssets: vi.fn()
176 }
177
178 await expect(importSessionFile(ctx as never, zipPath)).resolves.toMatchObject({
179 success: true,
180 pageCount: 1,
181 title: 'Square import'
182 })
183
184 expect(db.createSession).toHaveBeenCalledWith(
185 expect.objectContaining({
186 slideSizeId: 'square-1-1',
187 slideWidth: 1200,
188 slideHeight: 1200
189 })
190 )
191 expect(mocks.recordHistoryOperationStrict).toHaveBeenCalledTimes(1)
192 })
193 })
194
194 lines TYPESCRIPT