import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it, vi } from "vitest"; import { BrowserHintOverlay } from "../browser-hint-overlay"; import { ChatHeader } from "../chat-header"; import { ChatInsufficientCreditsBanner } from "../chat-insufficient-credits-banner"; import { ChatInputTextField } from "../chat-input-text-field"; import { ChatSendButton } from "../chat-send-button"; import { HistoryUnlockDialog } from "../history-unlock-dialog"; import { ImageBubble } from "../image-bubble"; import { InsufficientCreditsDialog } from "../insufficient-credits-dialog"; import { MessageAvatar } from "../message-avatar"; import { PrivateMessageCard } from "../private-message-card"; import { PwaInstallDialog } from "../pwa-install-dialog"; import { TextBubble } from "../text-bubble"; vi.mock("@/router/use-app-navigator", () => ({ useAppNavigator: () => ({ openSubscription: vi.fn(), }), })); describe("chat Tailwind components", () => { it("renders MessageAvatar AI and user branches with image utilities", () => { const aiHtml = renderToStaticMarkup(); const userHtml = renderToStaticMarkup( , ); expect(aiHtml).toContain('aria-label="AI avatar"'); expect(aiHtml).toContain("size-(--chat-avatar-size,43px)"); expect(aiHtml).toContain("size-full object-cover"); expect(aiHtml).toContain("%2Fimages%2Favatar%2Felio.png"); expect(userHtml).toContain('aria-label="User avatar"'); expect(userHtml).toContain("%2Fuser-avatar.png"); }); it("renders TextBubble AI and user visual variants", () => { const aiHtml = renderToStaticMarkup( , ); const userHtml = renderToStaticMarkup( , ); expect(aiHtml).toContain("whitespace-pre-wrap"); expect(aiHtml).toContain("rounded-tl-none"); expect(aiHtml).toContain("bg-white"); expect(aiHtml).toContain("hello\nthere"); expect(userHtml).toContain("rounded-tr-none"); expect(userHtml).toContain( "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]", ); expect(userHtml).toContain("from user"); }); it("renders PrivateMessageCard with fallback and loading states", () => { const fallbackHtml = renderToStaticMarkup(); const unlockingHtml = renderToStaticMarkup( undefined} />, ); expect(fallbackHtml).toContain('role="group"'); expect(fallbackHtml).toContain('aria-label="Locked private message"'); expect(fallbackHtml).toContain("rounded-tl-none"); expect(fallbackHtml).toContain("Elio has a private message for you."); expect(fallbackHtml).toContain("disabled"); expect(unlockingHtml).toContain("Premium secret"); expect(unlockingHtml).toContain("Unlocking..."); }); it("renders ChatSendButton visual states", () => { const activeHtml = renderToStaticMarkup( undefined} onPointerDownSend={() => undefined} />, ); const emptyHtml = renderToStaticMarkup( undefined} onPointerDownSend={() => undefined} />, ); const disabledHtml = renderToStaticMarkup( undefined} onPointerDownSend={() => undefined} />, ); expect(activeHtml).toContain('aria-label="Send message"'); expect(activeHtml).toContain('data-analytics-ignore="true"'); expect(activeHtml).not.toContain("data-analytics-key"); expect(activeHtml).toContain("size-(--chat-send-button-size,40px)"); expect(activeHtml).toContain( "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]", ); expect(emptyHtml).toContain("bg-[#f8a8ce]"); expect(emptyHtml).toContain("text-[rgba(255,255,255,0.88)]"); expect(disabledHtml).toContain("disabled"); }); it("renders ImageBubble openable and paywalled states", () => { const openableHtml = renderToStaticMarkup( undefined} />, ); const paywalledHtml = renderToStaticMarkup( , ); expect(openableHtml).toContain('role="button"'); expect(openableHtml).toContain('tabindex="0"'); expect(openableHtml).toContain('aria-label="Open image in fullscreen"'); expect(openableHtml).toContain("rounded-tl-none"); expect(openableHtml).toContain("block h-auto w-full object-cover"); expect(openableHtml).toContain("data:image/png;base64,/chat-image.png"); expect(paywalledHtml).toContain("scale-104 blur-sm"); expect(paywalledHtml).toContain( "bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]", ); expect(paywalledHtml).toContain("data:image/png;base64,/locked-image.png"); }); it("renders ChatInsufficientCreditsBanner with split title and CTA", () => { const html = renderToStaticMarkup( undefined} />, ); expect(html).toContain('role="status"'); expect(html).toContain('aria-live="polite"'); expect(html).toContain("Need credits"); expect(html).toContain("
"); expect(html).toContain("Top up now"); expect(html).toContain("Credits keep the conversation going."); expect(html).toContain('aria-label="Top up"'); expect(html).toContain("bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)]"); expect(html).toContain("active:scale-98"); }); it("renders ChatHeader guest and member branches", () => { const guestHtml = renderToStaticMarkup(); const memberHtml = renderToStaticMarkup( Offer} />, ); const memberWithHintHtml = renderToStaticMarkup( , ); const guestWithHintHtml = renderToStaticMarkup( , ); expect(guestHtml).toContain('aria-label="Sign up to unlock more features"'); expect(guestHtml).toContain("bg-accent"); expect(guestHtml).toContain("size-(--icon-size-sm,16px)"); expect(guestHtml).not.toContain('href="/splash"'); expect(guestHtml).not.toContain('aria-label="Back to home"'); expect(guestHtml).not.toContain('aria-label="Menu"'); expect(memberHtml).toContain("Offer"); expect(memberHtml).toContain('href="/splash"'); expect(memberHtml).toContain('aria-label="Back to home"'); expect(memberHtml).toContain('aria-label="Menu"'); expect(memberHtml).toContain('data-analytics-key="chat.open_menu"'); expect(memberHtml).toContain('data-analytics-key="chat.back_to_home"'); expect(memberHtml).toContain("bg-[rgba(13,11,20,0.7)]"); expect(memberHtml).toContain("size-(--responsive-icon-button-size,42px)"); expect(memberWithHintHtml).toContain( 'data-analytics-key="chat.external_browser_hint"', ); expect(memberWithHintHtml).toContain( "grid-cols-[auto_minmax(0,1fr)_auto]", ); expect(memberWithHintHtml.indexOf('aria-label="Back to home"')).toBeLessThan( memberWithHintHtml.indexOf( 'data-analytics-key="chat.external_browser_hint"', ), ); expect( memberWithHintHtml.indexOf( 'data-analytics-key="chat.external_browser_hint"', ), ).toBeLessThan(memberWithHintHtml.indexOf('aria-label="Menu"')); expect(guestWithHintHtml).not.toContain( 'data-analytics-key="chat.external_browser_hint"', ); }); it("renders ChatInputTextField with textarea utilities", () => { const html = renderToStaticMarkup( undefined} onSubmit={() => undefined} placeholder="Message Elio" />, ); expect(html).toContain(" { const html = renderToStaticMarkup( , ); expect(html).not.toContain("aria-expanded"); expect(html).toContain('aria-label="Open elsewhere. Better experience"'); expect(html).toContain("grid-cols-[auto_minmax(0,1fr)]"); expect(html).not.toContain("absolute"); expect(html).toContain("max-w-full"); expect(html).toContain("size-7.5"); expect(html).toContain("bg-[#17131f]"); expect(html).toContain("text-[#f3dfe8]"); expect(html).not.toContain("Show external browser hint"); expect(html).toContain("Open elsewhere"); expect(html).toContain("Better experience"); }); it("renders PwaInstallDialog with Tailwind dialog layout", () => { const html = renderToStaticMarkup( undefined} onInstall={() => undefined} />, ); expect(html).toContain('role="dialog"'); expect(html).toContain('aria-modal="true"'); expect(html).toContain('aria-labelledby="pwa-dialog-title"'); expect(html).toContain("fixed inset-0"); expect(html).toContain("max-w-(--pwa-install-dialog-max-width,360px)"); expect(html).toContain("size-(--icon-size-86,86px)"); expect(html).toContain("min-h-(--pwa-button-height,44px)"); expect(html).toContain("text-(--color-pwa-button-text,#ffffff)"); expect(html).toContain("%2Fimages%2Ficons%2Ficon-add-to-home.png"); expect(html).toContain("Add to Home Screen"); expect(html).toContain("Cancel"); expect(html).toContain("OK"); }); it("renders HistoryUnlockDialog open and loading states", () => { expect( renderToStaticMarkup( undefined} onConfirm={() => undefined} />, ), ).toBe(""); const html = renderToStaticMarkup( undefined} onConfirm={() => undefined} />, ); expect(html).toContain('aria-labelledby="history-unlock-title"'); expect(html).toContain("z-200"); expect(html).toContain("Unlock previous messages?"); expect(html).toContain("We found 3 locked messages"); expect(html).toContain("Could not unlock history."); expect(html).toContain("Unlocking..."); expect(html).toContain('data-analytics-key="chat.unlock_history"'); expect(html).toContain("text-(--color-pwa-button-text,#ffffff)"); expect(html).toContain("disabled"); }); it("renders InsufficientCreditsDialog with credit details", () => { expect( renderToStaticMarkup( undefined} onConfirm={() => undefined} />, ), ).toBe(""); const html = renderToStaticMarkup( undefined} onConfirm={() => undefined} />, ); expect(html).toContain('aria-labelledby="insufficient-credits-title"'); expect(html).toContain("z-220"); expect(html).toContain("Not enough credits"); expect(html).toContain("Balance"); expect(html).toContain("Required"); expect(html).toContain("Still needed"); expect(html).toContain("text-(--color-pwa-button-text,#ffffff)"); expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]"); expect(html).toContain("Top up now"); expect(html).toContain('data-analytics-key="chat.topup"'); }); });