import { createServer } from 'vite' import assert from 'node:assert/strict' import { writeFileSync } from 'node:fs' import { slideDesignFixture } from './fixtures/aiPptSlideDesign.mjs' const vite = await createServer({ server: { middlewareMode: true }, appType: 'custom', logLevel: 'error' }) try { const { aiCoursewareToSlides } = await vite.ssrLoadModule('/src/utils/aiCoursewareToSlides.ts') const { modelSlideDesignIssues } = await vite.ssrLoadModule('/src/utils/aiPptModelSlideDesign.ts') const { getAiPptImageAspectRatios, buildAiPptSlidePreviews } = await vite.ssrLoadModule('/src/utils/aiPptTemplateEngine.ts') const fixture = slideDesignFixture(), before = structuredClone(fixture) const result = aiCoursewareToSlides(fixture) assert.deepEqual(fixture, before, '渲染不得改写输入内容') assert.equal(result.slides[1].elements.find(e => e.name === '模型教学色块').left, 130 * 1.2) assert.equal(result.slides[1].elements.find(e => e.name === '模型教学色块').fill, '#FCE4CE') assert(result.slides[0].elements.find(e => e.type === 'text').content.includes('67.2px'), '模型字号按画布比例转换,不由前端重新选择') assert(result.slides.every(s => s.background.color === '#FFFDF5')) assert.equal(getAiPptImageAspectRatios('blank').model_design_v7, 1) for (const [name, mutate, reason] of [ ['overflow', p => p.pageDesign.elements[3].height = 10, /实际高度|投屏安全区/], ['missing', p => p.pageDesign.elements.pop(), /遗漏/], ['contrast', p => p.pageDesign.elements[3].color = '#D9EDF3', /对比度/], ['unknown reference', p => p.pageDesign.elements[3].textRef = 'interaction.answer', /引用/], ['required image', p => p.visualPlan.visualMode = 'reference_image', /槽位/], ['unknown element', p => p.pageDesign.elements[0].kind = 'script', /无效/] ]) { const broken = structuredClone(fixture); mutate(broken.pages[0]) assert.throws(() => aiCoursewareToSlides(broken), reason, name) const previews = buildAiPptSlidePreviews(broken) assert(previews[0].error && previews[1].slide, `${name}不能清空其他页`) } assert.deepEqual(modelSlideDesignIssues(fixture.pages[0]), []) const textOnly = structuredClone(fixture) textOnly.pages.forEach(page => { page.pageDesign.elements = page.pageDesign.elements.filter(e => e.kind === 'text') }) const textOnlySlides = aiCoursewareToSlides(textOnly).slides assert(textOnlySlides.every(slide => slide.elements.every(element => element.type === 'text')), '纯文字设计可通过,不得自动添加色块或分隔线') writeFileSync('artifacts/ai-ppt-tests/model-slide-design-fixture.json', JSON.stringify(fixture, null, 2)) writeFileSync('artifacts/ai-ppt-tests/model-slide-design-render.json', JSON.stringify(result, null, 2)) console.log('PASS: 模型坐标/颜色/字号忠实执行;内容不变;溢出、遗漏、低对比、未知引用、缺图片槽位和未知元素均阻断;单页错误保留其他预览。此测试使用协议夹具。') } finally { await vite.close() }