import { describe, it, expect } from 'vitest'; import { spotlightV1 } from '@/lib/choreography'; import { compileVideoTimeline, emitHyperframes } from '@/lib/video-export'; import type { AssetMeta, CompilerScene } from '@/lib/video-export'; import { NO_ASSETS, NO_PROBE, interactive, slide, quiz, el, speech, spotlight, laser, playVideo, stubProbe, stubAssets, stubInteractiveHtml, } from './helpers'; const audioMeta = (id: string): AssetMeta => ({ id, format: 'mp3', present: true, durationMs: 2000, }); const videoMeta = (id: string): AssetMeta => ({ id, format: 'mp4', present: true }); /** A slide classroom exercising every emitted layer: base, narration, effects, video. */ function compileSample() { const scenes = [ slide( 'intro', [ speech('sp1', 'Welcome to the lesson', { audioId: 'a1' }), spotlight('l1', 'e1'), laser('la1', 'e1', '#00ff88'), playVideo('v1', 'e1'), ], { title: 'Intro', elements: [el('e1', { left: 250, top: 140, width: 500, height: 280 })] }, ), quiz('checkpoint', [], 1), ]; return compileVideoTimeline( { stage: { id: 'stage', name: 'Sample Lesson' }, scenes }, { timing: stubProbe({ sp1: 2000 }, { v1: 3000 }), assets: stubAssets({ sp1: audioMeta('a1') }, { e1: videoMeta('stage:e1') }), }, ); } describe('emitHyperframes', () => { const ir = compileSample(); const project = emitHyperframes(ir, { width: 1920, height: 1080 }); const html = project.files.find((f) => f.path === 'index.html')!.content; it('emits the self-contained project file set', () => { const paths = project.files.map((f) => f.path).sort(); expect(paths).toEqual( [ 'LICENSES/Inter-OFL-1.1.txt', 'README.md', 'index.html', 'openmaic-video-manifest.json', 'subtitles.srt', 'subtitles.vtt', ].sort(), ); expect( project.files.find((file) => file.path === 'LICENSES/Inter-OFL-1.1.txt')!.content, ).toContain('SIL OPEN FONT LICENSE Version 1.1'); }); it('builds one composition driven by one paused GSAP timeline', () => { expect(html).toContain('data-composition-id="openmaic"'); expect(html).toContain('data-width="1920"'); expect(html).toContain('data-height="1080"'); expect(html).toContain(`data-duration="${ir.totalDurationMs / 1000}"`); expect(html).toContain('gsap.timeline({ paused: true })'); expect(html).toContain('window.__timelines["openmaic"] = tl;'); }); it('lays out base / narration / video clips with clip attributes', () => { expect(html).toMatch(/]*class="clip"[^>]*src="assets\/frames\/[^"]+\.png"/); expect(html).toMatch(/