feat(timeline): 支持拖拽调整时长与运镜/轨迹关键帧时间

This commit is contained in:
tongtong committed 2026-09-17 17:30:22 +08:00
1 parent bab92c05b8
commit 8d67f17a84
21 files changed
+2556 -194

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
import { beforeEach, expect, it } from "vitest";
import { usePlaybackStore } from "../runtime/playbackStore";
import { exportVideo } from "./videoExport";
beforeEach(() => {
usePlaybackStore.setState({ isPlaying: false, playhead: 0, duration: 5, loop: false, editingObjectId: null });
});
/**
* jsdom 里既没有视口画布也没有 WebCodecs,导出会立刻失败 ——
* 但这里要测的正是「导出期间 / 导出之后」的播放状态收口,与导出是否成功无关。
*/
it("turns the playback loop off while exporting and puts it back afterwards", async () => {
usePlaybackStore.getState().setLoop(true);
const loopHistory: boolean[] = [];
const unsubscribe = usePlaybackStore.subscribe((state) => loopHistory.push(state.loop));
await expect(exportVideo("director")).rejects.toThrow();
unsubscribe();
// 导出一开始就关掉循环:导出永远只播一遍,不会录到回卷的一段
expect(loopHistory[0]).toBe(false);
// 导出结束后循环设置原样还回去
expect(usePlaybackStore.getState().loop).toBe(true);
});
it("stops playback after an export instead of resuming an endless loop", async () => {
// 导出前正在播放,而且开着循环 —— 这就是「播完一直循环播放」的现场
usePlaybackStore.getState().setLoop(true);
usePlaybackStore.getState().setPlaying(true);
await expect(exportVideo("director")).rejects.toThrow();
const state = usePlaybackStore.getState();
expect(state.isPlaying).toBe(false);
expect(state.loop).toBe(true);
});
it("keeps the playhead where the export found it", async () => {
usePlaybackStore.getState().setPlayhead(2.5);
await expect(exportVideo("director")).rejects.toThrow();
expect(usePlaybackStore.getState().playhead).toBe(2.5);
});