Files
openmaic/OpenMAIC/tests/edit/round-trip/text-content.test.ts
2026-08-16 14:58:47 +08:00

70 lines
2.7 KiB
TypeScript

import JSZip from 'jszip';
import { describe, expect, it } from 'vitest';
import { buildPptxBlob } from '@/lib/export/use-export-pptx';
import { applySlideEditOperation } from '@/lib/edit/slide-ops';
import type { Scene, SlideContent } from '@/lib/types/stage';
import {
makeSlideFixture,
RATIO_PX2_INCH,
RATIO_PX2_PT,
VIEWPORT_RATIO,
VIEWPORT_SIZE,
} from './fixtures';
/**
* Round-trip harness — apply ops to a fixture SlideContent, run the
* existing export pipeline (`buildPptxBlob`), and inspect the resulting
* PPTX bytes via JSZip to verify the ops survived serialization.
*
* Caveat (documented for future PRs): the OpenMAIC codebase has no
* PPTX → Slide reimport path, so this harness is one-way (export side
* only). The "round-trip" property at the design-principle level is
* verified end-to-end by opening exports in a desktop tool; CI here
* catches export-pipeline regressions for each op as slide-surface PRs
* land. The shape `(fixture + ops) → blob → XML assertion` is the
* contract slide-surface PRs extend with per-op cases.
*/
async function exportSlideContent(content: SlideContent, scene: Scene): Promise<Blob> {
return buildPptxBlob(
[content.canvas],
[scene],
VIEWPORT_RATIO,
VIEWPORT_SIZE,
RATIO_PX2_INCH,
RATIO_PX2_PT,
);
}
async function readPptxEntry(blob: Blob, path: string): Promise<string> {
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
const entry = zip.file(path);
if (!entry) throw new Error(`PPTX did not contain entry: ${path}`);
return entry.async('string');
}
describe('round-trip harness (export side)', () => {
it('exports a noop fixture to a non-empty PPTX with the slide1 entry present', async () => {
const { scene, content } = makeSlideFixture();
const blob = await exportSlideContent(content, scene);
expect(blob.size).toBeGreaterThan(0);
// PPTX format always emits ppt/slides/slide1.xml for the first slide.
const slideXml = await readPptxEntry(blob, 'ppt/slides/slide1.xml');
expect(slideXml.length).toBeGreaterThan(0);
});
it('captures the new text after applying text.updateContent', async () => {
const { scene, content, textElementId } = makeSlideFixture();
// A distinctive needle so the assertion does not accidentally match
// pptxgenjs boilerplate or the original default text.
const NEEDLE = 'roundtrip-needle-abc123';
const after = applySlideEditOperation(content, {
type: 'text.updateContent',
elementId: textElementId,
content: `<p>${NEEDLE}</p>`,
});
const blob = await exportSlideContent(after, scene);
const slideXml = await readPptxEntry(blob, 'ppt/slides/slide1.xml');
expect(slideXml).toContain(NEEDLE);
});
});