import { expect, test } from "@playwright/test"; import { defaultVideoCanvasFunctionConfig, hasEnabledFixedVideoCanvasPointCosts, normalizeVideoCanvasFunctionConfig, } from "../../lib/video-canvas/config"; import { getVideoCanvasTutorialSupportQr } from "../../lib/video-canvas/tutorial-support"; test.describe("video canvas director configuration", () => { test("provides competitor-matched AI image import defaults", () => { expect(defaultVideoCanvasFunctionConfig.directorDesk.defaultAspectRatio).toBe( "9:16", ); expect(defaultVideoCanvasFunctionConfig.directorDesk.aiImport).toEqual({ cost: 48, enabled: true, functionKey: "director-ai-import", historyLimit: 24, localSamEndpoint: "", uploadAccept: "image/*", }); expect(defaultVideoCanvasFunctionConfig.functions["director-ai-import"]).toMatchObject({ enabled: true, kind: "multimodal", route: "director-ai-import", }); }); test("normalizes the configurable initial director aspect ratio", () => { expect( normalizeVideoCanvasFunctionConfig({ directorDesk: { defaultAspectRatio: "16:9" }, }).directorDesk.defaultAspectRatio, ).toBe("16:9"); expect( normalizeVideoCanvasFunctionConfig({ directorDesk: { defaultAspectRatio: "2:1" }, }).directorDesk.defaultAspectRatio, ).toBe("9:16"); }); test("normalizes administrator AI image import settings", () => { const config = normalizeVideoCanvasFunctionConfig({ directorDesk: { aiImport: { cost: 10000, enabled: false, functionKey: " custom-ai-import ", historyLimit: 0, localSamEndpoint: "javascript:alert(1)", uploadAccept: " image/png,image/jpeg ", }, }, }); expect(config.directorDesk.aiImport).toEqual({ cost: 9999, enabled: false, functionKey: "custom-ai-import", historyLimit: 1, localSamEndpoint: "", uploadAccept: "image/png,image/jpeg", }); }); test("provides competitor-matched crowd defaults", () => { expect(defaultVideoCanvasFunctionConfig.directorDesk.crowd).toEqual({ defaultColumns: 3, defaultRows: 3, defaultSpacing: 1.2, }); expect(normalizeVideoCanvasFunctionConfig({}).directorDesk.crowd).toEqual({ defaultColumns: 3, defaultRows: 3, defaultSpacing: 1.2, }); }); test("normalizes administrator crowd defaults before host delivery", () => { const config = normalizeVideoCanvasFunctionConfig({ directorDesk: { crowd: { defaultColumns: 0, defaultRows: 110, defaultSpacing: 1.234, }, }, }); expect(config.directorDesk.crowd).toEqual({ defaultColumns: 1, defaultRows: 100, defaultSpacing: 1.23, }); }); test("provides a configurable two-point video analysis cost", () => { expect(defaultVideoCanvasFunctionConfig.functions["video-analysis"].cost).toBe(2); expect(hasEnabledFixedVideoCanvasPointCosts(defaultVideoCanvasFunctionConfig)).toBe(true); expect( normalizeVideoCanvasFunctionConfig({ functions: { "video-analysis": { cost: 10_000 } }, }).functions["video-analysis"].cost, ).toBe(9999); expect( hasEnabledFixedVideoCanvasPointCosts( normalizeVideoCanvasFunctionConfig({ functions: { "video-analysis": { cost: 0 } }, }), ), ).toBe(false); }); test("keeps the element-mark generation prompt administrator-configurable", () => { expect( defaultVideoCanvasFunctionConfig.functions["element-mark"].generationPrompt, ).toBe("重点遵循已标记元素:{{labels}}"); expect( normalizeVideoCanvasFunctionConfig({ functions: { "element-mark": { generationPrompt: "锁定这些元素:{{plainLabels}}", }, }, }).functions["element-mark"].generationPrompt, ).toBe("锁定这些元素:{{plainLabels}}"); }); test("provides configurable smart subtitle erase defaults", () => { expect(defaultVideoCanvasFunctionConfig.functions["video-subtitle-erase"]).toMatchObject({ cost: 0, enabled: true, model: "volcano-subtitle-eraser", route: "local", }); expect(normalizeVideoCanvasFunctionConfig({ videoSubtitleErase: { blurRadius: 18, smartSearchStartRatio: 0.62 }, }).videoSubtitleErase).toMatchObject({ blurRadius: 18, smartSearchStartRatio: 0.62, }); }); test("provides configurable video audio separation defaults", () => { expect(defaultVideoCanvasFunctionConfig.functions["video-audio-separation"]).toMatchObject({ cost: 0, enabled: true, model: "center-channel-v1", route: "local", }); expect(normalizeVideoCanvasFunctionConfig({ videoAudioSeparation: { backgroundGain: 1.6, frameRate: 24 }, }).videoAudioSeparation).toMatchObject({ backgroundGain: 1.6, frameRate: 24 }); }); test("provides configurable video download watermark rules", () => { const config = normalizeVideoCanvasFunctionConfig({ videoDownload: { rulesTitle: "站点水印规则", welcomeText: "欢迎使用{{siteName}}", }, }); expect(config.videoDownload).toMatchObject({ rulesTitle: "站点水印规则", welcomeText: "欢迎使用{{siteName}}", }); }); test("provides normalized database-backed history asset behavior", () => { expect(defaultVideoCanvasFunctionConfig.historyAssets).toMatchObject({ defaultZoomPercent: 100, enabled: true, maxItems: 300, picker: { maxSelection: 10, mediaShortSide: 350, pageSize: 15, placementGap: 450, }, previewAutoplay: true, showWatermarkBadge: true, watermarkBadgeText: "AI", }); expect( normalizeVideoCanvasFunctionConfig({ historyAssets: { defaultZoomPercent: 110, enabled: false, maxItems: 9999, previewAutoplay: false, showWatermarkBadge: false, watermarkBadgeText: " LOCAL SITE MARK ", }, }).historyAssets, ).toMatchObject({ defaultZoomPercent: 100, enabled: false, maxItems: 500, picker: { maxSelection: 10, pageSize: 15, }, previewAutoplay: false, showWatermarkBadge: false, watermarkBadgeText: "LOCAL SITE M", }); }); test("provides configurable tutorial and contact surfaces", () => { expect(defaultVideoCanvasFunctionConfig.tutorialSupport).toEqual({ customerServiceUrl: "", officialAccountQrImageUrl: "", salesQrImageUrl: "", tutorialUrl: "https://resonate.feishu.cn/wiki/Loxfw6XHziYRk0kKzdjcFfp9nhb", }); const config = normalizeVideoCanvasFunctionConfig({ tutorialSupport: { customerServiceUrl: "/support/video-canvas", officialAccountQrImageUrl: "javascript:alert(1)", salesQrImageUrl: "https://assets.example.com/sales.png", tutorialUrl: "https://docs.example.com/video-canvas", }, }).tutorialSupport; expect(config).toEqual({ customerServiceUrl: "/support/video-canvas", officialAccountQrImageUrl: "", salesQrImageUrl: "https://assets.example.com/sales.png", tutorialUrl: "https://docs.example.com/video-canvas", }); expect(getVideoCanvasTutorialSupportQr(config, "sales")).toEqual({ alt: "联系销售", url: "https://assets.example.com/sales.png", }); expect(getVideoCanvasTutorialSupportQr(config, "official")).toEqual({ alt: "关注公众号", url: "", }); }); test("normalizes administrator video reference rules", () => { const config = normalizeVideoCanvasFunctionConfig({ videoReferenceSelection: { defaultRule: { maxImages: 99, mode: "unknown" }, modelRules: [ { autoExit: false, hideActionAtLimit: false, maxImages: 0, mode: "first-frame", modelPattern: " Custom Video Model ", }, { modelPattern: "" }, ], }, }); expect(config.videoReferenceSelection.defaultRule).toEqual({ autoExit: false, hideActionAtLimit: false, maxImages: 20, mode: "reference-images", }); expect(config.videoReferenceSelection.modelRules).toEqual([ { autoExit: false, hideActionAtLimit: false, maxImages: 1, mode: "first-frame", modelPattern: "Custom Video Model", }, ]); }); test("normalizes administrator video advanced-setting defaults", () => { expect(defaultVideoCanvasFunctionConfig.videoAdvancedSettings).toEqual({ defaultAutoValidateMaterial: true, defaultSearchEnabled: true, }); expect( normalizeVideoCanvasFunctionConfig({ videoAdvancedSettings: { defaultAutoValidateMaterial: false, defaultSearchEnabled: false, }, }).videoAdvancedSettings, ).toEqual({ defaultAutoValidateMaterial: false, defaultSearchEnabled: false, }); }); test("delivers normalized video generation parameter configuration", () => { expect(defaultVideoCanvasFunctionConfig.videoGenerationParameters).toMatchObject({ countOptions: [1, 2, 4], defaultResolution: "720P", maxDurationSeconds: 15, minDurationSeconds: 4, }); expect( normalizeVideoCanvasFunctionConfig({ videoGenerationParameters: { defaultGenerateAudio: false, defaultResolution: "4k", pointsPerSecondByResolution: { "4k": 155 }, }, }).videoGenerationParameters, ).toMatchObject({ defaultGenerateAudio: false, defaultResolution: "4K", pointsPerSecondByResolution: { "4K": 155 }, }); }); test("delivers backend-configurable new-node defaults", () => { expect(defaultVideoCanvasFunctionConfig.nodeCreation).toMatchObject({ audio: { advancedSettingsOpen: true }, image: { height: 350, ratio: "16:9", width: 622 }, video: { height: 350, modelCatalogId: "seedance-1-5-pro", resolution: "1080P", width: 622, }, }); expect( normalizeVideoCanvasFunctionConfig({ nodeCreation: { audio: { advancedSettingsOpen: false }, image: { ratio: "9:16", width: 700 }, video: { modelCatalogId: "custom-video", resolution: "4k" }, }, }).nodeCreation, ).toMatchObject({ audio: { advancedSettingsOpen: false }, image: { ratio: "9:16", width: 700 }, video: { modelCatalogId: "custom-video", resolution: "4K" }, }); }); test("provides the competitor public video role catalog in exact page order", () => { const roles = defaultVideoCanvasFunctionConfig.rolePresets; expect(roles).toHaveLength(23); expect(roles.map((role) => role.id)).toEqual([ "role-003", "role-001", "role-002", "role-004", "role-005", "role-006", "role-007", "role-010", "role-011", "role-012", "role-013", "role-014", "role-015", "role-016", "role-018", "role-022", "role-021", "role-008", "role-009", "role-019", "role-020", "role-017", "role-023", ]); expect(roles[0]).toMatchObject({ name: "甜妹/清新少女", tags: "女 · 青年 · 现代 · 女主", images: { expression: expect.stringContaining("003_expression_grid_1.png"), face: expect.stringContaining("003_face_closeup_1.png"), full: expect.stringContaining("003_full_body_1.png"), threeView: expect.stringContaining("003_character_sheet_1.png"), }, }); expect(roles[1].images).toMatchObject({ expression: expect.stringContaining("001_expression_grid.png"), face: expect.stringContaining("001_face_closeup.png"), full: expect.stringContaining("001_full_body.png"), threeView: expect.stringContaining("001_character_sheet.png"), }); expect(roles.slice(10, 13).map((role) => role.name)).toEqual([ "反派长辈/势利亲戚", "生活方式普通人", "生活方式普通人", ]); }); test("provides competitor-matched subject creation defaults", () => { expect(defaultVideoCanvasFunctionConfig.subjectLibrary).toMatchObject({ descriptionMaxLength: 100, imageAccept: "image/jpeg,image/png,image/webp", imageMaxCount: 4, imageMinCount: 2, historyLibraryEnabled: true, labels: ["无标签", "人物", "场景", "道具", "特效", "其他"], nameMaxLength: 20, requireClearFrontFace: true, smartCompletion: { candidateCount: 9, cost: 6, maxSelected: 3, }, smartDescription: { cost: 1 }, unselectedCategory: "其他", videoAccept: "video/mp4,video/quicktime", videoMaxDurationSeconds: 8, videoMinDurationSeconds: 3, videoUsage: { enabled: true, maxSubjects: 4, modelPatterns: ["Kling O3"], }, voiceHelpText: "仅主体为人物时支持音色绑定和使用", }); expect( defaultVideoCanvasFunctionConfig.subjectLibrary.voices.map( (voice) => voice.name, ), ).toEqual([ "无配音", "钓系女友", "温柔女声", "播报男声", "盐系少年", "撒娇女友", "视频原声", ]); }); test("normalizes administrator subject creation settings", () => { const subjectLibrary = normalizeVideoCanvasFunctionConfig({ subjectLibrary: { descriptionMaxLength: 5000, imageMaxCount: 1, imageMinCount: 3, labels: ["人物", "人物", "场景", ""], nameMaxLength: 0, smartDescription: { maxTokens: 1, prompt: " custom subject prompt ", temperature: 2, }, videoMaxDurationSeconds: 2, videoMinDurationSeconds: 5, voices: [{ id: "custom", name: "自定义预设", badge: "站点", previewUrl: "" }], videoUsage: { maxSubjects: 99, modelPatterns: [" Kling O3 ", "Kling O3", "", "Custom Kling"], promptTemplate: " 主体{{index}}={{name}} ", }, }, }).subjectLibrary; expect(subjectLibrary).toMatchObject({ descriptionMaxLength: 1000, imageMaxCount: 3, imageMinCount: 3, labels: ["人物", "场景"], nameMaxLength: 1, videoMaxDurationSeconds: 5, videoMinDurationSeconds: 5, smartDescription: { maxTokens: 32, prompt: "custom subject prompt", temperature: 1, }, voices: [{ id: "custom", name: "自定义预设", badge: "站点", previewUrl: "" }], videoUsage: { maxSubjects: 12, modelPatterns: ["Kling O3", "Custom Kling"], promptTemplate: "主体{{index}}={{name}}", }, }); }); });