200 lines
5.3 KiB
TypeScript
200 lines
5.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
createProjectData,
|
|
createProjectSnapshot,
|
|
hasProjectUnsavedChanges,
|
|
normalizeProjectEditor,
|
|
PROJECT_VERSION,
|
|
resolveProjectMedia,
|
|
validateProjectData,
|
|
} from "./projectPersistence";
|
|
|
|
describe("projectPersistence media compatibility", () => {
|
|
it("accepts legacy projects with a single videoPath", () => {
|
|
const project = {
|
|
version: 1,
|
|
videoPath: "/tmp/screen.webm",
|
|
editor: {},
|
|
};
|
|
|
|
expect(validateProjectData(project)).toBe(true);
|
|
expect(resolveProjectMedia(project)).toEqual({
|
|
screenVideoPath: "/tmp/screen.webm",
|
|
});
|
|
});
|
|
|
|
it("creates version 2 projects with explicit media", () => {
|
|
const project = createProjectData(
|
|
{
|
|
screenVideoPath: "/tmp/screen.webm",
|
|
webcamVideoPath: "/tmp/webcam.webm",
|
|
},
|
|
{
|
|
wallpaper: "/wallpapers/wallpaper1.jpg",
|
|
shadowIntensity: 0,
|
|
showBlur: false,
|
|
motionBlurAmount: 0,
|
|
borderRadius: 0,
|
|
padding: 50,
|
|
cropRegion: { x: 0, y: 0, width: 1, height: 1 },
|
|
zoomRegions: [],
|
|
trimRegions: [],
|
|
speedRegions: [],
|
|
annotationRegions: [],
|
|
aspectRatio: "16:9",
|
|
webcamLayoutPreset: "picture-in-picture",
|
|
webcamMaskShape: "circle",
|
|
webcamPosition: null,
|
|
exportQuality: "good",
|
|
exportFormat: "mp4",
|
|
gifFrameRate: 15,
|
|
gifLoop: true,
|
|
gifSizePreset: "medium",
|
|
},
|
|
);
|
|
|
|
expect(project.version).toBe(PROJECT_VERSION);
|
|
expect(project.media).toEqual({
|
|
screenVideoPath: "/tmp/screen.webm",
|
|
webcamVideoPath: "/tmp/webcam.webm",
|
|
});
|
|
expect(validateProjectData(project)).toBe(true);
|
|
});
|
|
|
|
it("normalizes webcam mask shape values safely", () => {
|
|
expect(normalizeProjectEditor({ webcamMaskShape: "rounded" }).webcamMaskShape).toBe("rounded");
|
|
expect(
|
|
normalizeProjectEditor({ webcamMaskShape: "not-a-real-shape" as never }).webcamMaskShape,
|
|
).toBe("rectangle");
|
|
});
|
|
|
|
it("normalizes blur region type and mosaic block size safely", () => {
|
|
const editor = normalizeProjectEditor({
|
|
annotationRegions: [
|
|
{
|
|
id: "annotation-1",
|
|
startMs: 0,
|
|
endMs: 500,
|
|
type: "blur",
|
|
content: "",
|
|
position: { x: 10, y: 10 },
|
|
size: { width: 20, height: 20 },
|
|
style: {
|
|
color: "#fff",
|
|
backgroundColor: "transparent",
|
|
fontSize: 32,
|
|
fontFamily: "Inter",
|
|
fontWeight: "bold",
|
|
fontStyle: "normal",
|
|
textDecoration: "none",
|
|
textAlign: "center",
|
|
},
|
|
zIndex: 1,
|
|
blurData: {
|
|
type: "mosaic",
|
|
shape: "rectangle",
|
|
color: "black",
|
|
intensity: 999,
|
|
blockSize: 999,
|
|
},
|
|
},
|
|
{
|
|
id: "annotation-2",
|
|
startMs: 0,
|
|
endMs: 500,
|
|
type: "blur",
|
|
content: "",
|
|
position: { x: 10, y: 10 },
|
|
size: { width: 20, height: 20 },
|
|
style: {
|
|
color: "#fff",
|
|
backgroundColor: "transparent",
|
|
fontSize: 32,
|
|
fontFamily: "Inter",
|
|
fontWeight: "bold",
|
|
fontStyle: "normal",
|
|
textDecoration: "none",
|
|
textAlign: "center",
|
|
},
|
|
zIndex: 2,
|
|
blurData: {
|
|
type: "invalid" as never,
|
|
shape: "rectangle",
|
|
color: "invalid" as never,
|
|
intensity: 10,
|
|
blockSize: 0,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(editor.annotationRegions[0].blurData?.type).toBe("mosaic");
|
|
expect(editor.annotationRegions[0].blurData?.color).toBe("black");
|
|
expect(editor.annotationRegions[0].blurData?.intensity).toBe(40);
|
|
expect(editor.annotationRegions[0].blurData?.blockSize).toBe(48);
|
|
expect(editor.annotationRegions[1].blurData?.type).toBe("blur");
|
|
expect(editor.annotationRegions[1].blurData?.color).toBe("white");
|
|
expect(editor.annotationRegions[1].blurData?.blockSize).toBe(4);
|
|
});
|
|
|
|
it("accepts the dual frame webcam layout preset", () => {
|
|
expect(normalizeProjectEditor({ webcamLayoutPreset: "dual-frame" }).webcamLayoutPreset).toBe(
|
|
"dual-frame",
|
|
);
|
|
});
|
|
|
|
it("falls back from dual frame to picture in picture for portrait aspect ratios", () => {
|
|
expect(
|
|
normalizeProjectEditor({
|
|
aspectRatio: "9:16",
|
|
webcamLayoutPreset: "dual-frame",
|
|
}).webcamLayoutPreset,
|
|
).toBe("picture-in-picture");
|
|
});
|
|
|
|
it("clears webcamPosition when the normalized preset is not picture in picture", () => {
|
|
expect(
|
|
normalizeProjectEditor({
|
|
webcamLayoutPreset: "dual-frame",
|
|
webcamPosition: { cx: 0.2, cy: 0.8 },
|
|
}).webcamPosition,
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
it("creates stable snapshots for identical project state", () => {
|
|
const media = {
|
|
screenVideoPath: "/tmp/screen.webm",
|
|
webcamVideoPath: "/tmp/webcam.webm",
|
|
};
|
|
const editor = normalizeProjectEditor({
|
|
wallpaper: "/wallpapers/wallpaper1.jpg",
|
|
shadowIntensity: 0,
|
|
showBlur: false,
|
|
motionBlurAmount: 0,
|
|
borderRadius: 0,
|
|
padding: 50,
|
|
cropRegion: { x: 0, y: 0, width: 1, height: 1 },
|
|
zoomRegions: [],
|
|
trimRegions: [],
|
|
speedRegions: [],
|
|
annotationRegions: [],
|
|
aspectRatio: "16:9",
|
|
webcamLayoutPreset: "picture-in-picture",
|
|
webcamMaskShape: "circle",
|
|
exportQuality: "good",
|
|
exportFormat: "mp4",
|
|
gifFrameRate: 15,
|
|
gifLoop: true,
|
|
gifSizePreset: "medium",
|
|
});
|
|
|
|
expect(createProjectSnapshot(media, editor)).toBe(createProjectSnapshot(media, editor));
|
|
});
|
|
|
|
it("detects unsaved changes from differing snapshots", () => {
|
|
expect(hasProjectUnsavedChanges(null, null)).toBe(false);
|
|
expect(hasProjectUnsavedChanges("same", "same")).toBe(false);
|
|
expect(hasProjectUnsavedChanges("current", "baseline")).toBe(true);
|
|
});
|