



























@@ -0,0 +1,87 @@
1+/* @vitest-environment jsdom */
2+3+import { render } from "lit";
4+import { describe, expect, it, vi } from "vitest";
5+import { renderChatAvatar } from "./chat-avatar.ts";
6+7+vi.mock("../views/agents-utils.ts", () => ({
8+isRenderableControlUiAvatarUrl: (value: string) =>
9+/^data:image\//i.test(value) || (value.startsWith("/") && !value.startsWith("//")),
10+assistantAvatarFallbackUrl: () => "apple-touch-icon.png",
11+resolveAssistantTextAvatar: (value: string | null | undefined) => {
12+if (!value) {
13+return null;
14+}
15+return value.length <= 3 ? value : null;
16+},
17+resolveChatAvatarRenderUrl: (
18+candidate: string | null | undefined,
19+agent: { identity?: { avatar?: string; avatarUrl?: string } },
20+) => {
21+const isRenderableControlUiAvatarUrl = (value: string) =>
22+/^data:image\//i.test(value) || (value.startsWith("/") && !value.startsWith("//"));
23+if (typeof candidate === "string" && candidate.startsWith("blob:")) {
24+return candidate;
25+}
26+for (const value of [candidate, agent.identity?.avatarUrl, agent.identity?.avatar]) {
27+if (typeof value === "string" && isRenderableControlUiAvatarUrl(value)) {
28+return value;
29+}
30+}
31+return null;
32+},
33+}));
34+35+function renderAvatar(params: Parameters<typeof renderChatAvatar>) {
36+const container = document.createElement("div");
37+render(renderChatAvatar(...params), container);
38+return container.querySelector<HTMLElement>(".chat-avatar");
39+}
40+41+describe("renderChatAvatar", () => {
42+it("uses the assistant fallback when no assistant avatar is configured", () => {
43+const avatar = renderAvatar(["assistant"]);
44+45+expect(avatar).not.toBeNull();
46+expect(avatar?.getAttribute("src")).toBe("apple-touch-icon.png");
47+});
48+49+it("renders assistant fallback, blob image, and text avatars", () => {
50+const remoteAvatar = renderAvatar([
51+"assistant",
52+{ avatar: "https://example.com/avatar.png", name: "Val" },
53+]);
54+expect(remoteAvatar?.getAttribute("src")).toBe("apple-touch-icon.png");
55+56+const blobAvatar = renderAvatar(["assistant", { avatar: "blob:managed-image", name: "Val" }]);
57+expect(blobAvatar?.tagName).toBe("IMG");
58+expect(blobAvatar?.getAttribute("src")).toBe("blob:managed-image");
59+60+const textAvatar = renderAvatar(["assistant", { avatar: "VC", name: "Val" }]);
61+expect(textAvatar?.tagName).toBe("DIV");
62+expect(textAvatar?.textContent).toContain("VC");
63+expect(textAvatar?.getAttribute("aria-label")).toBe("Val");
64+});
65+66+it("uses the assistant fallback while authenticated avatar routes are loading", () => {
67+const avatar = renderAvatar([
68+"assistant",
69+{ avatar: "/avatar/main", name: "OpenClaw" },
70+undefined,
71+"",
72+"session-token",
73+]);
74+75+expect(avatar?.getAttribute("src")).toBe("apple-touch-icon.png");
76+});
77+78+it("renders local user image and text avatars", () => {
79+const imageAvatar = renderAvatar(["user", undefined, { name: "Buns", avatar: "/avatar/user" }]);
80+expect(imageAvatar?.getAttribute("src")).toBe("/avatar/user");
81+expect(imageAvatar?.getAttribute("alt")).toBe("Buns");
82+83+const textAvatar = renderAvatar(["user", undefined, { name: "Buns", avatar: "AB" }]);
84+expect(textAvatar?.tagName).toBe("DIV");
85+expect(textAvatar?.textContent).toContain("AB");
86+});
87+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。