From 31cccf87a10e39680458288133a882dd9f659853 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 27 May 2026 10:27:49 +0800 Subject: [PATCH] test: cover canvas chat helper behavior --- .../canvas/chat-panel/canvas-chat-media.ts | 2 +- tests/routes/canvas-chat-helpers.test.ts | 186 ++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 tests/routes/canvas-chat-helpers.test.ts diff --git a/components/canvas/chat-panel/canvas-chat-media.ts b/components/canvas/chat-panel/canvas-chat-media.ts index 67073cc1d..3789f3c66 100644 --- a/components/canvas/chat-panel/canvas-chat-media.ts +++ b/components/canvas/chat-panel/canvas-chat-media.ts @@ -225,7 +225,7 @@ export function extractAssistantImageUrls( break; } - if (!selected) { + if (!selected && !referenceUrlKeys?.size) { selected = candidates.find((candidate) => normalizeMediaUrl(candidate)) || ""; } if (!selected) continue; diff --git a/tests/routes/canvas-chat-helpers.test.ts b/tests/routes/canvas-chat-helpers.test.ts new file mode 100644 index 000000000..7f1edd679 --- /dev/null +++ b/tests/routes/canvas-chat-helpers.test.ts @@ -0,0 +1,186 @@ +import { expect, test } from "@playwright/test"; +import { + collectMessageImageUrls, + extractAssistantImageUrls, + extractVideoUrls, + normalizeMediaUrl, + stripMediaMarkup, +} from "@/components/canvas/chat-panel/canvas-chat-media"; +import { + buildDetailResultMarkdown, + buildDetailReviewAppendMarkdown, + buildDetailReviewPlans, +} from "@/components/canvas/chat-panel/canvas-detail-markdown"; +import type { ChatMessage } from "@/lib/types"; + +test.describe("canvas chat pure helpers", () => { + test("normalizes media urls without query strings or trailing slashes", () => { + expect(normalizeMediaUrl(" HTTPS://EXAMPLE.COM/assets/image.png?sig=secret#preview ")).toBe( + "https://example.com/assets/image.png" + ); + expect(normalizeMediaUrl("not-a-url?cache=1#hash/")).toBe("not-a-url"); + }); + + test("collects message image urls from parts, attachments, and legacy content", () => { + const message = { + id: "message-1", + role: "user", + parts: [ + { + type: "file", + url: "https://cdn.example.com/upload-a.png?x=1", + mediaType: "image/png", + }, + { + type: "text", + text: "参考 ![a](https://cdn.example.com/markdown-a.webp) 和 https://cdn.example.com/plain-a.jpg?token=1", + }, + ], + attachments: [ + { + name: "attachment", + url: "https://cdn.example.com/attachment-a.jpeg?download=1", + contentType: "image/jpeg", + }, + ], + content: "legacy https://cdn.example.com/legacy-a.png", + } as unknown as ChatMessage; + + expect(collectMessageImageUrls(message)).toEqual([ + "https://cdn.example.com/upload-a.png?x=1", + "https://cdn.example.com/markdown-a.webp", + "https://cdn.example.com/plain-a.jpg?token=1", + "https://cdn.example.com/attachment-a.jpeg?download=1", + "https://cdn.example.com/legacy-a.png", + ]); + }); + + test("filters assistant result images that match nearest user references", () => { + const referenceKeys = new Set(["https://cdn.example.com/reference.png"]); + const urls = extractAssistantImageUrls( + [ + "![ref](https://cdn.example.com/reference.png?token=secret)", + "![result](https://cdn.example.com/result.png?token=secret)", + ].join("\n"), + referenceKeys + ); + + expect(urls).toEqual(["https://cdn.example.com/result.png?token=secret"]); + }); + + test("extracts and strips video markup without removing surrounding text", () => { + const text = [ + "生成完成", + '', + "[下载视频](https://cdn.example.com/video-b.mp4)", + "继续优化", + ].join("\n"); + + expect(extractVideoUrls(text)).toEqual([ + "https://cdn.example.com/video-a.mp4?sig=1", + "https://cdn.example.com/video-b.mp4", + ]); + expect(stripMediaMarkup(text)).toBe("生成完成\n\n\n继续优化"); + }); + + test("builds detail review plans with clamped count and localized fallbacks", () => { + const plans = buildDetailReviewPlans({ + language: "zh", + count: 9, + summary: " 已验证卖点 ", + brief: { + productSpu: "", + demandType: "", + productName: "无线吸尘器", + targetAudience: "养宠家庭", + mainUser: "", + usageScenario: "客厅清洁", + triggerMoment: "", + coreNeed: "快速清理毛发", + riskConcern: "吸力不足", + expectedResult: "地面干净", + coreSellingPoint: "强吸力,轻量,低噪音", + positioningStatement: "", + userVoice: "", + competitorReference: "", + priceBand: "", + brandTone: "", + bannedWords: "", + }, + }); + + expect(plans).toHaveLength(5); + expect(plans[0]).toMatchObject({ + index: 1, + title: "真实需求反馈1", + }); + expect(plans[0]?.reviewText).toContain("养宠家庭"); + expect(plans[0]?.reviewText).toContain("已验证卖点"); + expect(plans[0]?.imagePrompt).toContain("无线吸尘器"); + }); + + test("builds stable detail result and appended review markdown", () => { + const markdown = buildDetailResultMarkdown({ + generationMode: "agent_layout", + summary: "详情页生成成功", + images: [ + { + originalUrl: "https://cdn.example.com/detail-1.png", + thumbnailUrl: "https://cdn.example.com/detail-1-thumb.webp", + }, + ], + prompts: ["商业摄影质感"], + skillFlow: { + name: "详情页生成", + version: "1.0", + steps: ["拆解卖点", "生成分镜"], + }, + qualityChecks: { + passed: true, + }, + screens: [ + { + screenType: "A", + title: "核心卖点", + useModel: true, + fabe: { + b: "收益", + }, + }, + ], + reviewGroups: [ + { + reviewText: "真实好用", + images: [ + { + originalUrl: "https://cdn.example.com/review-1.png", + thumbnailUrl: "https://cdn.example.com/review-1-thumb.webp", + }, + ], + }, + ], + }); + + expect(markdown).toContain("生成路线:AGENT排版(画面+可编辑文字层)"); + expect(markdown).toContain("质检结果:通过"); + expect(markdown).toContain("![thumb|||https://cdn.example.com/detail-1.png|||2K]"); + expect(markdown).toContain("评价 1:真实好用"); + + expect( + buildDetailReviewAppendMarkdown({ + failedCount: 1, + groups: [ + { + reviewText: "追加评价", + images: [ + { + originalUrl: "https://cdn.example.com/review-2.png", + thumbnailUrl: "https://cdn.example.com/review-2-thumb.webp", + }, + ], + }, + ], + }) + ).toContain("失败 1 条,已跳过。"); + }); +});