import { describe, expect, test } from "bun:test"; import { buildScene } from "@/services/renderer/scene-builder"; import { ImageNode } from "@/services/renderer/nodes/image-node"; import type { ImageElement, SceneTracks } from "@/timeline"; import type { MediaAsset } from "@/media/types"; import { mediaTime, type MediaTime, TICKS_PER_SECOND, ZERO_MEDIA_TIME, } from "@/wasm"; function imageElement({ id, mediaId, startTime, transitionDuration = 0, }: { id: string; mediaId: string; startTime: MediaTime; transitionDuration?: number; }): ImageElement { return { id, name: id, type: "image", mediaId, startTime, duration: mediaTime({ ticks: 2 * TICKS_PER_SECOND }), trimStart: ZERO_MEDIA_TIME, trimEnd: ZERO_MEDIA_TIME, params: { "transform.positionX": 0, "transform.positionY": 0, "transform.scaleX": 1, "transform.scaleY": 1, "transform.rotate": 0, opacity: 1, blendMode: "normal", "transition.kind": "fade", "transition.inDuration": transitionDuration, "transition.outDuration": 0, }, }; } function imageAsset(id: string): MediaAsset { return { id, name: id, type: "image", file: new File([], `${id}.png`, { type: "image/png" }), url: `blob:${id}`, }; } describe("scene transition boundaries", () => { test("holds the outgoing slide through the incoming transition", () => { const first = imageElement({ id: "first", mediaId: "media-first", startTime: ZERO_MEDIA_TIME, }); const second = imageElement({ id: "second", mediaId: "media-second", startTime: mediaTime({ ticks: 3 * TICKS_PER_SECOND }), transitionDuration: 1, }); const tracks: SceneTracks = { overlay: [], main: { id: "main", name: "Main Track", type: "video", elements: [first, second], muted: false, hidden: false, }, audio: [], }; const scene = buildScene({ canvasSize: { width: 1920, height: 1080 }, tracks, mediaAssets: [imageAsset("media-first"), imageAsset("media-second")], duration: 5 * TICKS_PER_SECOND, background: { type: "color", color: "transparent" }, }); const imageNodes = scene.children.filter( (node): node is ImageNode => node instanceof ImageNode, ); expect(imageNodes).toHaveLength(2); expect(imageNodes[0].params.contentDuration).toBe(2 * TICKS_PER_SECOND); expect(imageNodes[0].params.duration).toBe(4 * TICKS_PER_SECOND); expect(imageNodes[1].params.transition?.inDuration).toBe(TICKS_PER_SECOND); }); });