- `
- : nothing}
+ ${!isUser && showBotAvatar ? this._renderBotAvatar(avatarCfg?.bot) : nothing}
${this.messageData?.text
? html`
${this.messageData.text as string}
`
diff --git a/packages/ui/src/chat-ui/__tests__/ButtonsMessage.test.ts b/packages/ui/src/chat-ui/__tests__/ButtonsMessage.test.ts
index add2200..e79aecc 100644
--- a/packages/ui/src/chat-ui/__tests__/ButtonsMessage.test.ts
+++ b/packages/ui/src/chat-ui/__tests__/ButtonsMessage.test.ts
@@ -1,9 +1,19 @@
import { describe, it, expect, vi } from "vitest";
import { marked } from "marked";
+const { getStateMock, storeState } = vi.hoisted(() => {
+ const storeState: { theme: { avatar?: { bot?: string; showBot?: boolean } } } = {
+ theme: {},
+ };
+ return {
+ getStateMock: vi.fn(() => storeState),
+ storeState,
+ };
+});
+
vi.mock("@chativa/core", () => ({
MessageTypeRegistry: { register: vi.fn() },
- chatStore: { getState: () => ({ theme: {} }) },
+ chatStore: { getState: getStateMock },
}));
vi.mock("lit", () => ({
LitElement: class {},
@@ -20,6 +30,8 @@ vi.mock("lit/directives/unsafe-html.js", () => ({
unsafeHTML: (html: string) => html,
}));
+import { ButtonsMessage } from "../ButtonsMessage";
+
/** Mirrors the renderInlineMarkdown helper in ButtonsMessage.ts */
function renderInlineMarkdown(text: string): string {
return (marked.parse(text, { async: false }) as string).replace(/^
(.*)<\/p>\n?$/s, "$1");
@@ -43,3 +55,54 @@ describe("ButtonsMessage — label markdown rendering", () => {
expect(result.startsWith("
")).toBe(false);
});
});
+
+function collectTemplateValues(node: unknown, out: string[] = []): string[] {
+ if (typeof node === "string") {
+ out.push(node);
+ return out;
+ }
+ if (!node || typeof node !== "object") return out;
+ if (Array.isArray(node)) {
+ node.forEach((n) => collectTemplateValues(n, out));
+ return out;
+ }
+
+ const maybeTemplate = node as { vals?: unknown[] };
+ if (Array.isArray(maybeTemplate.vals)) {
+ collectTemplateValues(maybeTemplate.vals, out);
+ }
+
+ Object.values(node).forEach((value) => collectTemplateValues(value, out));
+ return out;
+}
+
+describe("ButtonsMessage — avatar theming", () => {
+ it("uses configured bot avatar URL", () => {
+ storeState.theme.avatar = { bot: "https://cdn.example.com/bot.png" };
+
+ const message = new ButtonsMessage();
+ message.sender = "bot";
+ message.messageData = { text: "Hi", buttons: [{ label: "A" }] };
+
+ const rendered = message.render();
+ const values = collectTemplateValues(rendered);
+
+ expect(values).toContain("https://cdn.example.com/bot.png");
+ });
+
+ it("hides bot avatar when showBot is false", () => {
+ storeState.theme.avatar = {
+ bot: "https://cdn.example.com/bot.png",
+ showBot: false,
+ };
+
+ const message = new ButtonsMessage();
+ message.sender = "bot";
+ message.messageData = { text: "Hi", buttons: [{ label: "A" }] };
+
+ const rendered = message.render();
+ const values = collectTemplateValues(rendered);
+
+ expect(values).not.toContain("https://cdn.example.com/bot.png");
+ });
+});
diff --git a/packages/ui/src/chat-ui/__tests__/QuickReplyMessage.test.ts b/packages/ui/src/chat-ui/__tests__/QuickReplyMessage.test.ts
new file mode 100644
index 0000000..5c621dd
--- /dev/null
+++ b/packages/ui/src/chat-ui/__tests__/QuickReplyMessage.test.ts
@@ -0,0 +1,86 @@
+import { describe, it, expect, vi } from "vitest";
+
+const { getStateMock, storeState } = vi.hoisted(() => {
+ const storeState: { theme: { avatar?: { bot?: string; showBot?: boolean } } } = {
+ theme: {},
+ };
+ return {
+ getStateMock: vi.fn(() => storeState),
+ storeState,
+ };
+});
+
+vi.mock("@chativa/core", () => ({
+ MessageTypeRegistry: { register: vi.fn() },
+ chatStore: { getState: getStateMock },
+}));
+vi.mock("lit", () => ({
+ LitElement: class {},
+ html: (strings: TemplateStringsArray, ...vals: unknown[]) => ({ strings, vals }),
+ css: (strings: TemplateStringsArray, ...vals: unknown[]) => ({ strings, vals }),
+ nothing: null,
+}));
+vi.mock("lit/decorators.js", () => ({
+ customElement: () => () => {},
+ property: () => () => {},
+ state: () => () => {},
+}));
+
+import { QuickReplyMessage } from "../QuickReplyMessage";
+
+function collectTemplateValues(node: unknown, out: string[] = []): string[] {
+ if (typeof node === "string") {
+ out.push(node);
+ return out;
+ }
+ if (!node || typeof node !== "object") return out;
+ if (Array.isArray(node)) {
+ node.forEach((n) => collectTemplateValues(n, out));
+ return out;
+ }
+
+ const maybeTemplate = node as { vals?: unknown[] };
+ if (Array.isArray(maybeTemplate.vals)) {
+ collectTemplateValues(maybeTemplate.vals, out);
+ }
+
+ Object.values(node).forEach((value) => collectTemplateValues(value, out));
+ return out;
+}
+
+describe("QuickReplyMessage — avatar theming", () => {
+ it("uses configured bot avatar URL", () => {
+ storeState.theme.avatar = { bot: "https://cdn.example.com/bot-quick.png" };
+
+ const message = new QuickReplyMessage();
+ message.sender = "bot";
+ message.messageData = {
+ text: "Choose",
+ actions: [{ label: "Yes", value: "yes" }],
+ };
+
+ const rendered = message.render();
+ const values = collectTemplateValues(rendered);
+
+ expect(values).toContain("https://cdn.example.com/bot-quick.png");
+ });
+
+ it("hides bot avatar when showBot is false", () => {
+ storeState.theme.avatar = {
+ bot: "https://cdn.example.com/bot-quick.png",
+ showBot: false,
+ };
+
+ const message = new QuickReplyMessage();
+ message.sender = "bot";
+ message.messageData = {
+ text: "Choose",
+ actions: [{ label: "Yes", value: "yes" }],
+ };
+
+ const rendered = message.render();
+ const values = collectTemplateValues(rendered);
+
+ expect(values).not.toContain("https://cdn.example.com/bot-quick.png");
+ });
+});