| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 |
- import assert from "node:assert/strict"
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs"
- import vm from "node:vm"
- import { webcrypto } from "node:crypto"
- import { createServer } from "vite"
- const vite = await createServer({ server: { middlewareMode: true }, appType: "custom", logLevel: "error" })
- try {
- const { prepareMediaForExport } = await vite.ssrLoadModule("/src/utils/aiPptExport.ts")
- // 加载生产导出器,在测试副本暴露其转换函数;不改写或替换生产转换逻辑。
- const source = readFileSync("public/pptworker/pptJson.js", "utf8")
- .replace('r((r.s = "9a7e"))', 'globalThis.bundleRequire = r')
- .replace('Sr = function (e, t) {', 'Sr = globalThis.renderPptForTest = function (e, t) {')
- const context = vm.createContext({ console, setTimeout, clearTimeout, setInterval, clearInterval,
- TextEncoder, TextDecoder, Uint8Array, ArrayBuffer, Buffer, Blob, crypto: webcrypto,
- fetch: () => { throw new Error("离线导出不得请求网络") } })
- context.self = context
- vm.runInContext(source, context)
- context.bundleRequire("f5f5")
- assert.equal(typeof context.renderPptForTest, "function")
- const png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
- const wav = Buffer.alloc(44 + 8000 * 2)
- wav.write("RIFF"); wav.writeUInt32LE(wav.length - 8, 4); wav.write("WAVEfmt ", 8)
- wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20); wav.writeUInt16LE(1, 22)
- wav.writeUInt32LE(8000, 24); wav.writeUInt32LE(16000, 28); wav.writeUInt16LE(2, 32); wav.writeUInt16LE(16, 34)
- wav.write("data", 36); wav.writeUInt32LE(16000, 40)
- for (let i = 0; i < 8000; i++) wav.writeInt16LE(Math.round(Math.sin(i * Math.PI * 2 * 440 / 8000) * 5000), 44 + i * 2)
- const audio = `data:audio/wav;base64,${wav.toString("base64")}`
- const base = { left: 80, top: 180, width: 60, height: 60, rotate: 0 }
- const slides = [{ id: "offline-test", remark: "参考答案:轻声哼唱\n解析:比较声音的强弱。", elements: [
- { ...base, id: "title", type: "text", width: 1000, height: 90, content: '<div style="font-size:48px">离线媒体测试</div>', defaultFontName: "Microsoft Yahei", defaultColor: "#222222" },
- { ...base, top: 320, id: "audio-a", type: "elf", subtype: "elf-audio", src: audio, color: "#333333", autoplay: false, loop: false, fixedRatio: true },
- { ...base, left: 300, top: 320, id: "legacy-audio", type: "elf", subtype: "elf-enjoy", src: audio, enjoyList: [{ src: audio }] },
- { ...base, top: 500, id: "image", type: "image", src: png, fixedRatio: true }
- ] }]
- const prepared = await prepareMediaForExport(slides)
- assert.equal(prepared[0].elements.filter(element => element.subtype === "elf-audio").length, 2)
- const ppt = await context.renderPptForTest({ width: 1920, height: 1080, slides: prepared,
- theme: { fontName: "Microsoft Yahei", fontColor: "#222222", viewportRatio: 0.5625 } }, { viewportRatio: 0.5625 })
- const bytes = Buffer.from(await ppt.write({ outputType: "arraybuffer" }))
- assert.equal(bytes.subarray(0, 2).toString(), "PK")
- mkdirSync("artifacts/ai-ppt-tests", { recursive: true })
- writeFileSync("artifacts/ai-ppt-tests/offline-media.pptx", bytes)
- await assert.rejects(() => prepareMediaForExport([{ id: "score", elements: [{ ...base, id: "score", type: "elf", subtype: "elf-sing-play", sid: "1" }] }]), /静态|谱图/)
- console.log(JSON.stringify({ passed: true, bytes: bytes.length, output: "artifacts/ai-ppt-tests/offline-media.pptx", checks: ["native-audio", "legacy-audio", "offline-embedded-assets", "reject-online-only-score"] }))
- } finally { await vite.close() }
|