fix(ui): harden avatar fallback regressions
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
import {
|
import {
|
||||||
agentLogoUrl,
|
agentLogoUrl,
|
||||||
resolveConfiguredCronModelSuggestions,
|
resolveConfiguredCronModelSuggestions,
|
||||||
|
resolveAgentAvatarUrl,
|
||||||
resolveEffectiveModelFallbacks,
|
resolveEffectiveModelFallbacks,
|
||||||
sortLocaleStrings,
|
sortLocaleStrings,
|
||||||
} from "./agents-utils.ts";
|
} from "./agents-utils.ts";
|
||||||
@@ -110,3 +111,18 @@ describe("agentLogoUrl", () => {
|
|||||||
expect(agentLogoUrl("")).toBe("favicon.svg");
|
expect(agentLogoUrl("")).toBe("favicon.svg");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resolveAgentAvatarUrl", () => {
|
||||||
|
it("prefers a runtime avatar URL over non-URL identity avatars", () => {
|
||||||
|
expect(
|
||||||
|
resolveAgentAvatarUrl({ identity: { avatar: "A", avatarUrl: "/avatar/main" } }, {
|
||||||
|
avatar: "A",
|
||||||
|
} as { avatar: string }),
|
||||||
|
).toBe("/avatar/main");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for initials or emoji avatar values without a URL", () => {
|
||||||
|
expect(resolveAgentAvatarUrl({ identity: { avatar: "A" } })).toBeNull();
|
||||||
|
expect(resolveAgentAvatarUrl({ identity: { avatar: "🦞" } })).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -200,15 +200,18 @@ export function resolveAgentAvatarUrl(
|
|||||||
agent: { identity?: { avatar?: string; avatarUrl?: string } },
|
agent: { identity?: { avatar?: string; avatarUrl?: string } },
|
||||||
agentIdentity?: AgentIdentityResult | null,
|
agentIdentity?: AgentIdentityResult | null,
|
||||||
): string | null {
|
): string | null {
|
||||||
const url =
|
const candidates = [
|
||||||
agentIdentity?.avatar?.trim() ??
|
agentIdentity?.avatar?.trim(),
|
||||||
agent.identity?.avatarUrl?.trim() ??
|
agent.identity?.avatarUrl?.trim(),
|
||||||
agent.identity?.avatar?.trim();
|
agent.identity?.avatar?.trim(),
|
||||||
if (!url) {
|
];
|
||||||
return null;
|
for (const candidate of candidates) {
|
||||||
}
|
if (!candidate) {
|
||||||
if (AVATAR_URL_RE.test(url)) {
|
continue;
|
||||||
return url;
|
}
|
||||||
|
if (AVATAR_URL_RE.test(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,55 @@ describe("chat view", () => {
|
|||||||
expect(logoImage?.getAttribute("src")).toBe("favicon.svg");
|
expect(logoImage?.getAttribute("src")).toBe("favicon.svg");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps the welcome logo fallback under the mounted base path", () => {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
render(
|
||||||
|
renderChat(
|
||||||
|
createProps({
|
||||||
|
assistantName: "Assistant",
|
||||||
|
assistantAvatar: "A",
|
||||||
|
assistantAvatarUrl: null,
|
||||||
|
basePath: "/openclaw/",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
|
||||||
|
const logoImage = container.querySelector<HTMLImageElement>(
|
||||||
|
".agent-chat__welcome .agent-chat__avatar--logo img",
|
||||||
|
);
|
||||||
|
expect(logoImage).not.toBeNull();
|
||||||
|
expect(logoImage?.getAttribute("src")).toBe("/openclaw/favicon.svg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps grouped assistant avatar fallbacks under the mounted base path", () => {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
render(
|
||||||
|
renderChat(
|
||||||
|
createProps({
|
||||||
|
assistantName: "Assistant",
|
||||||
|
assistantAvatar: "A",
|
||||||
|
assistantAvatarUrl: null,
|
||||||
|
basePath: "/openclaw/",
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: "hello",
|
||||||
|
timestamp: 1000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
container,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupedLogo = container.querySelector<HTMLImageElement>(
|
||||||
|
".chat-group.assistant .chat-avatar--logo",
|
||||||
|
);
|
||||||
|
expect(groupedLogo).not.toBeNull();
|
||||||
|
expect(groupedLogo?.getAttribute("src")).toBe("/openclaw/favicon.svg");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders compacting indicator as a badge", () => {
|
it("renders compacting indicator as a badge", () => {
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export default defineConfig({
|
|||||||
"src/**/*.test.ts",
|
"src/**/*.test.ts",
|
||||||
"extensions/**/*.test.ts",
|
"extensions/**/*.test.ts",
|
||||||
"test/**/*.test.ts",
|
"test/**/*.test.ts",
|
||||||
|
"ui/src/ui/app-chat.test.ts",
|
||||||
"ui/src/ui/views/agents-utils.test.ts",
|
"ui/src/ui/views/agents-utils.test.ts",
|
||||||
"ui/src/ui/views/chat.test.ts",
|
"ui/src/ui/views/chat.test.ts",
|
||||||
"ui/src/ui/views/usage-render-details.test.ts",
|
"ui/src/ui/views/usage-render-details.test.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user