Files
clawhub/server/og/skillOgSvg.test.ts
Vyctor H. Brzezowski 6f537bf7ad fix(og): org profile images and Downloads metric in OG cards (#2840)
* fix(og): render org profile images in publisher OG cards

Pass publisher avatar, kind, and installs into OG meta URLs and allow
safely fetching public HTTPS org logos when generating profile images.

* fix: render org profile images in publisher OG cards

Org logos use public HTTPS URLs outside the GitHub/gravatar allowlist, so OG
generation fell back to the default mark. Allow SSRF-safe public fetches for
publisher profile avatars and embed avatar/kind metadata in OG URLs.

* fix(og): show Downloads instead of Installs on OG cards

Switch skill, plugin, and publisher OG image generators to read and
label download counts, with legacy installs query param fallback.

* fix(og): type skill API payload for canonical stat reads

Export SkillStatReadable so fetchSkillOgMeta can pass API skill stats
through readCanonicalStat without a TypeScript error.

* fix(og): format compact downloads in OG cards

Query-param download counts were rendered as raw integers on publisher
OG images. Reuse formatCompactStat, add download icon + lowercase label,
bump layout versions, and refresh org profile visual proof.

* fix(og): use Downloads label without icon on OG cards

Remove the download SVG from OG stat blocks and show only a muted
"Downloads" label above compact values. Bump skill/plugin/publisher
layout versions to bust cached social previews.
2026-06-24 14:08:49 -07:00

89 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { wrapText } from "./registryOgSvg";
import { buildSkillOgSvg } from "./skillOgSvg";
describe("skill OG SVG", () => {
it("includes title, description, and labels", () => {
const svg = buildSkillOgSvg({
markDataUrl: "data:image/png;base64,AAA=",
title: "Discord Doctor",
description: "Quick diagnosis and repair for Discord bot.",
ownerLabel: "@jhillock",
versionLabel: "v1.2.3",
installCommand: {
subject: "skills",
action: "install",
target: "discord-doctor",
},
stats: [
{ value: "1.2k", label: "Downloads" },
{ value: "PASS", label: "Audit" },
],
});
expect(svg).toContain("Discord Doctor");
expect(svg).toContain("Quick diagnosis and repair");
expect(svg).toContain("@jhillock");
expect(svg).toContain("PASS");
expect(svg).toContain("Audit");
expect(svg).toContain("openclaw");
expect(svg).toContain("skills");
expect(svg).toContain("install");
expect(svg).toContain("discord-doctor");
});
it("wraps long titles to avoid clipping", () => {
const svg = buildSkillOgSvg({
markDataUrl: "data:image/png;base64,AAA=",
title: "Excalidraw Flowchart Generator",
description: "Create Excalidraw flowcharts from descriptions.",
ownerLabel: "@swiftlysisngh",
versionLabel: "v1.0.2",
});
const titleBlock = svg.match(/<text x="72" y="(?:174|184)"[\s\S]*?<\/text>/)?.[0] ?? "";
const titleTspans = titleBlock.match(/<tspan /g) ?? [];
expect(titleTspans.length).toBe(2);
expect(svg).toContain("Excalidraw");
expect(svg).toContain("Flowchart");
});
it("clips and wraps long descriptions", () => {
const longWord = "a".repeat(200);
const svg = buildSkillOgSvg({
markDataUrl: "data:image/png;base64,AAA=",
title: "Gurkerlcli",
description: `Prefix ${longWord} suffix`,
ownerLabel: "@pasogott",
versionLabel: "v0.1.0",
});
expect(svg).toContain('<svg width="1200" height="630"');
expect(svg).toContain('fill="url(#bgBase)"');
expect(svg).not.toContain(longWord);
expect(svg).toContain("…");
const descBlock = svg.match(/<text[^>]*font-size="28"[\s\S]*?<\/text>/)?.[0] ?? "";
const descTspans = descBlock.match(/<tspan /g) ?? [];
expect(descTspans.length).toBeLessThanOrEqual(2);
});
it("wraps CJK text as full-width glyphs without inserting continuation ellipses", () => {
const description = "西瓜视频数据查询助手。覆盖视频详情、用户数据、搜索、评论等全功能。";
const lines = wrapText(description, 760, 28, 2);
expect(lines.length).toBeGreaterThan(1);
expect(lines).toHaveLength(2);
expect(lines.join("")).toBe(description);
expect(lines[0]).not.toContain("…");
});
it("clips overlong CJK text on the last visible line", () => {
const lines = wrapText("视频详情用户数据搜索评论".repeat(10), 760, 28, 2);
expect(lines).toHaveLength(2);
expect(lines[0]).not.toContain("…");
expect(lines[1]).toContain("…");
});
});