/** * A video that follows the animation (#404): a sprite whose Video clock is driven shows the picture * at its Video time, a number an animation keys like any other. */ import { describe, expect, it } from 'vitest'; import { CreateVideoCommand } from '../../src/core/commands/assetCommands'; import { AddClipCommand } from '../../src/core/commands/clipCommands'; import { animatableDefs } from '../../src/core/model/animation'; import { drivenTimeOf, drivenVideoKey, isDrivenVideoKey, parseVideoKey } from '../../src/core/model/video'; import { isRowShown, schemaFor } from '../../src/core/properties/PropertySchema'; import { VIDEO_CLOCK_KEY, VIDEO_END_KEY, VIDEO_START_KEY, VIDEO_TIME_KEY, } from '../../src/core/properties/videos'; import { makeProject } from '../helpers'; const FILE = `a1b2c3d4e5f6/${'c7'.repeat(32)}.mp4`; const KEY = `video:${FILE}#2~4~1~1`; describe('where a driven video stands', () => { const clip = { start: 2, end: 4, loop: true }; it('counts from the start of its clip', () => { expect(drivenTimeOf(clip, 0.5, 10)).toBe(2.5); }); it('goes round past the end with Loop', () => { expect(drivenTimeOf(clip, 5, 10)).toBe(3); }); it('holds the last picture without Loop, and the first one before the start', () => { expect(drivenTimeOf({ ...clip, loop: false }, 9, 10)).toBe(3.999); expect(drivenTimeOf(clip, -1, 10)).toBe(2); }); it('measures a clip running to the end of the video by the file', () => { const tail = { start: 6, end: Infinity, loop: true }; expect(drivenTimeOf(tail, 5, 10)).toBe(7); expect(drivenTimeOf({ ...tail, loop: false }, 5, 10)).toBe(9.999); expect(drivenTimeOf(tail, 5, NaN)).toBe(11); }); }); describe('the player a wearer drives', () => { it('goes by the clip key and the name of its wearer', () => { const key = drivenVideoKey(KEY, 'view3:obj1'); expect(key).toBe(`${KEY}@view3:obj1`); expect(isDrivenVideoKey(key)).toBe(true); expect(isDrivenVideoKey(KEY)).toBe(false); expect(parseVideoKey(key)).toEqual(parseVideoKey(KEY)); }); }); describe('the Video clock and Video time rows', () => { function setup() { const f = makeProject(); const create = new CreateVideoCommand('Intro', FILE, { width: 320, height: 180, duration: 2, fps: 30, audio: false, }); f.stack.execute(create); const clip = new AddClipCommand(create.asset.id, { [VIDEO_START_KEY]: 0.5, [VIDEO_END_KEY]: 1 }); f.stack.execute(clip); return { project: f.project, videoId: create.asset.id, clipId: clip.clipId }; } const row = (key: string) => schemaFor('sprite').find((d) => d.key === key)!; it('are shown only while the texture is a video or a clip of one', () => { const { project, videoId, clipId } = setup(); const driven = { [VIDEO_CLOCK_KEY]: 'driven' }; for (const texture of [videoId, clipId]) { expect(isRowShown(row(VIDEO_CLOCK_KEY), { texture }, project)).toBe(true); expect(isRowShown(row(VIDEO_TIME_KEY), { texture, ...driven }, project)).toBe(true); } expect(isRowShown(row(VIDEO_CLOCK_KEY), { texture: '' }, project)).toBe(false); expect(isRowShown(row(VIDEO_TIME_KEY), { texture: '', ...driven }, project)).toBe(false); }); it('show the time only while the clock is driven', () => { const { project, videoId } = setup(); expect(isRowShown(row(VIDEO_TIME_KEY), { texture: videoId }, project)).toBe(false); }); it('can be keyed by an animation', () => { const keys = animatableDefs('sprite').map((d) => d.key); expect(keys).toContain(VIDEO_TIME_KEY); expect(keys).toContain(VIDEO_CLOCK_KEY); }); });