173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
import { defaultVideoCanvasFunctionConfig } from "../../lib/video-canvas/config";
|
||
import {
|
||
addVideoCanvasAttachedSubject,
|
||
buildVideoCanvasSubjectProviderReferences,
|
||
composeVideoCanvasSubjectPrompt,
|
||
getVideoCanvasAttachedSubjects,
|
||
normalizeGeneratedSubjectDescription,
|
||
removeVideoCanvasAttachedSubject,
|
||
resolveVideoCanvasSubjectCategory,
|
||
supportsVideoCanvasSubjectUsage,
|
||
toVideoCanvasAttachedSubject,
|
||
validateVideoCanvasSubjectDraft,
|
||
} from "../../lib/video-canvas/subjects";
|
||
|
||
const config = defaultVideoCanvasFunctionConfig.subjectLibrary;
|
||
|
||
test.describe("video canvas subject library", () => {
|
||
test("validates the exact image and video creation ranges", () => {
|
||
expect(
|
||
validateVideoCanvasSubjectDraft(
|
||
{
|
||
assetCount: 1,
|
||
category: "人物",
|
||
description: "古风女性人物,黑色长发,绿色衣裙。",
|
||
mediaMode: "images",
|
||
name: "古风女主",
|
||
voiceId: "none",
|
||
},
|
||
config,
|
||
),
|
||
).toBe("请至少添加2张角色图片");
|
||
expect(
|
||
validateVideoCanvasSubjectDraft(
|
||
{
|
||
assetCount: 2,
|
||
category: "人物",
|
||
description: "古风女性人物,黑色长发,绿色衣裙。",
|
||
mediaMode: "images",
|
||
name: "古风女主",
|
||
voiceId: "gentle-female",
|
||
},
|
||
config,
|
||
),
|
||
).toBeNull();
|
||
expect(
|
||
validateVideoCanvasSubjectDraft(
|
||
{
|
||
assetCount: 1,
|
||
category: "人物",
|
||
description: "古风女性人物,黑色长发,绿色衣裙。",
|
||
durationSeconds: 8.1,
|
||
mediaMode: "video",
|
||
name: "古风女主",
|
||
voiceId: "original-video",
|
||
},
|
||
config,
|
||
),
|
||
).toBe("角色视频时长需为3-8秒");
|
||
});
|
||
|
||
test("normalizes labels and smart descriptions", () => {
|
||
expect(resolveVideoCanvasSubjectCategory("场景", config)).toBe("场景");
|
||
expect(resolveVideoCanvasSubjectCategory("unknown", config)).toBe("其他");
|
||
expect(resolveVideoCanvasSubjectCategory("", config)).toBe("其他");
|
||
expect(
|
||
normalizeGeneratedSubjectDescription(
|
||
"```text\n主体描述:一位黑色长发、绿色衣裙的古风女性人物。\n```",
|
||
100,
|
||
),
|
||
).toBe("一位黑色长发、绿色衣裙的古风女性人物。");
|
||
});
|
||
|
||
test("accepts uploaded custom voice ids", () => {
|
||
expect(
|
||
validateVideoCanvasSubjectDraft(
|
||
{
|
||
assetCount: 2,
|
||
category: "人物",
|
||
description: "古风女性人物,黑色长发,绿色衣裙。",
|
||
mediaMode: "images",
|
||
name: "古风女主",
|
||
voiceId: "custom:25c7404b-80ae-4f11-a237-a6ec35c4ed6a",
|
||
},
|
||
config,
|
||
),
|
||
).toBeNull();
|
||
});
|
||
|
||
test("stores subject references without changing unrelated node draft values", () => {
|
||
const subject = toVideoCanvasAttachedSubject({
|
||
description: { description: "成年古装男性,黑发束冠,金黄色龙纹长袍。" },
|
||
displayName: "古装男性",
|
||
id: "subject-1",
|
||
metadata: {
|
||
category: "人物",
|
||
mediaMode: "images",
|
||
mediaUrls: ["https://example.com/front.png", "https://example.com/back.png"],
|
||
voiceId: "none",
|
||
voiceName: "无配音",
|
||
},
|
||
thumbnailUrl: "https://example.com/front.png",
|
||
});
|
||
expect(subject).not.toBeNull();
|
||
|
||
const attached = addVideoCanvasAttachedSubject(
|
||
{ params: { model: "kling-o3", prompt: "人物转身。" }, untouched: true },
|
||
subject!,
|
||
4,
|
||
);
|
||
expect(getVideoCanvasAttachedSubjects(attached)).toEqual([subject]);
|
||
expect(attached).toMatchObject({
|
||
params: { model: "kling-o3", prompt: "人物转身。", subjects: [subject] },
|
||
untouched: true,
|
||
videoSubjects: [subject],
|
||
});
|
||
|
||
const removed = removeVideoCanvasAttachedSubject(attached, "subject-1");
|
||
expect(getVideoCanvasAttachedSubjects(removed)).toEqual([]);
|
||
expect(removed).toMatchObject({
|
||
params: { model: "kling-o3", prompt: "人物转身。" },
|
||
untouched: true,
|
||
});
|
||
});
|
||
|
||
test("maps subject data to hidden prompt and structured provider references", () => {
|
||
const subject = toVideoCanvasAttachedSubject({
|
||
description: { description: "白色短发,黑色机能服。" },
|
||
displayName: "测试主体",
|
||
id: "subject-2",
|
||
metadata: {
|
||
category: "人物",
|
||
mediaMode: "images",
|
||
mediaUrls: ["https://example.com/a.png", "https://example.com/b.png"],
|
||
voiceId: "broadcast-male",
|
||
voiceName: "播报男声",
|
||
},
|
||
originalUrl: "https://example.com/a.png",
|
||
})!;
|
||
const prompt = composeVideoCanvasSubjectPrompt(
|
||
"主体走向镜头。",
|
||
[subject],
|
||
"主体{{index}}「{{name}}」({{category}}):{{description}},音色{{voiceName}}。",
|
||
);
|
||
|
||
expect(prompt).toBe(
|
||
"主体走向镜头。 主体1「测试主体」(人物):白色短发,黑色机能服。,音色播报男声。",
|
||
);
|
||
expect(buildVideoCanvasSubjectProviderReferences([subject])).toEqual([
|
||
{
|
||
category: "人物",
|
||
description: "白色短发,黑色机能服。",
|
||
name: "测试主体",
|
||
referenceImages: ["https://example.com/a.png", "https://example.com/b.png"],
|
||
referenceVideo: undefined,
|
||
subjectId: "subject-2",
|
||
voiceId: "broadcast-male",
|
||
},
|
||
]);
|
||
});
|
||
|
||
test("shows subject usage only for administrator-configured video models", () => {
|
||
expect(supportsVideoCanvasSubjectUsage(config, "Kling O3")).toBe(true);
|
||
expect(supportsVideoCanvasSubjectUsage(config, "Seedance 2.0 VIP")).toBe(false);
|
||
expect(
|
||
supportsVideoCanvasSubjectUsage(
|
||
{ ...config, videoUsage: { ...config.videoUsage, enabled: false } },
|
||
"Kling O3",
|
||
),
|
||
).toBe(false);
|
||
});
|
||
});
|