106 lines
4.9 KiB
TypeScript
106 lines
4.9 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
import { describe, expect, test } from "bun:test";
|
|
|
|
import { assetPickerItemsToInsertPayloads } from "@/components/canvas/asset-picker-modal";
|
|
import { pickerItemMediaKind, type AssetLibraryPickerItem } from "@/components/assets/asset-library-picker-modal";
|
|
import type { ImageAsset, TextAsset } from "@/stores/use-asset-store";
|
|
|
|
const timestamp = "2026-08-28T00:00:00.000Z";
|
|
|
|
describe("canvas asset picker", () => {
|
|
test("maps multiple local and external selections in the requested order", () => {
|
|
const image: ImageAsset = {
|
|
id: "image-1",
|
|
kind: "image",
|
|
title: "场景图",
|
|
coverUrl: "",
|
|
tags: [],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
data: { dataUrl: "data:image/png;base64,AAAA", storageKey: "resource:image-1", width: 1280, height: 720, bytes: 1024, mimeType: "image/png" },
|
|
};
|
|
const text: TextAsset = {
|
|
id: "text-1",
|
|
kind: "text",
|
|
title: "旁白",
|
|
coverUrl: "",
|
|
tags: [],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
data: { content: "夜色渐深。" },
|
|
};
|
|
const items: AssetLibraryPickerItem[] = [
|
|
{ id: image.id, title: image.title, category: "environment", kindLabel: "图片", asset: image },
|
|
{ id: text.id, title: text.title, category: "other", kindLabel: "文本", asset: text },
|
|
{
|
|
id: "external-video",
|
|
title: "外部视频",
|
|
category: "external:eagle",
|
|
kindLabel: "视频",
|
|
external: {
|
|
sourceId: "eagle",
|
|
sourceName: "Eagle",
|
|
item: { id: "video-1", title: "外部视频", kind: "video", fileUrl: "http://127.0.0.1/video.mp4", width: 1920, height: 1080, bytes: 2048, mimeType: "video/mp4" },
|
|
},
|
|
},
|
|
];
|
|
|
|
expect(assetPickerItemsToInsertPayloads(["external-video", image.id, text.id], items)).toEqual([
|
|
{ kind: "video", url: "http://127.0.0.1/video.mp4", title: "外部视频", width: 1920, height: 1080, bytes: 2048, mimeType: "video/mp4", assetId: "external:eagle:video-1" },
|
|
{ kind: "image", dataUrl: "data:image/png;base64,AAAA", storageKey: "resource:image-1", title: "场景图", width: 1280, height: 720, bytes: 1024, mimeType: "image/png", assetId: "image-1" },
|
|
{ kind: "text", content: "夜色渐深。", title: "旁白", assetId: "text-1" },
|
|
]);
|
|
});
|
|
|
|
test("rejects stale selections instead of silently dropping them", () => {
|
|
expect(() => assetPickerItemsToInsertPayloads(["missing"], [])).toThrow("所选素材已不存在");
|
|
});
|
|
});
|
|
|
|
describe("asset picker media kind", () => {
|
|
const base: AssetLibraryPickerItem = { id: "item", title: "素材", category: "other", kindLabel: "图片" };
|
|
const image: ImageAsset = {
|
|
id: "asset",
|
|
kind: "image",
|
|
title: "图",
|
|
coverUrl: "",
|
|
tags: [],
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
data: { dataUrl: "", width: 1, height: 1, bytes: 1, mimeType: "image/png" },
|
|
};
|
|
|
|
test("prefers the declared media kind over the underlying record", () => {
|
|
expect(pickerItemMediaKind({ ...base, mediaKind: "video", asset: image })).toBe("video");
|
|
expect(pickerItemMediaKind({ ...base, asset: image })).toBe("image");
|
|
});
|
|
|
|
test("resolves external plugin items and skips records without a media kind", () => {
|
|
const external: AssetLibraryPickerItem = {
|
|
...base,
|
|
external: { sourceId: "eagle", sourceName: "Eagle", item: { id: "audio-1", title: "外部音频", kind: "audio" } },
|
|
};
|
|
|
|
expect(pickerItemMediaKind(external)).toBe("audio");
|
|
// 角色卡、3D 模型这类条目没有媒体类型,媒体筛选生效时不应被当成图片留在列表里。
|
|
expect(pickerItemMediaKind(base)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("canvas context menu motion", () => {
|
|
test("keeps hover feedback stationary while retaining spotlight surfaces", () => {
|
|
const styles = readFileSync(resolve(import.meta.dir, "../src/styles/globals.css"), "utf8");
|
|
const menu = readFileSync(resolve(import.meta.dir, "../src/components/canvas/canvas-context-menu.tsx"), "utf8");
|
|
const menuRule = styles.match(/\.canvas-menu-item\s*\{([\s\S]*?)\}/)?.[1] || "";
|
|
|
|
expect(menuRule).not.toContain("transform");
|
|
expect(styles).not.toContain(".canvas-menu-item:not(:disabled):hover {");
|
|
expect(styles).not.toContain(".canvas-menu-item:not(:disabled):active");
|
|
expect(menu).not.toContain("group-hover:translate-x");
|
|
expect(menu).not.toContain("scale: 0.97");
|
|
expect(menu).toContain("<SpotlightSurface");
|
|
});
|
|
});
|