import { render, screen, fireEvent } from "@testing-library/vue"; import { defineComponent } from "vue"; import { describe, it, expect, vi } from "vitest"; import type { UserMessage } from "@ag-ui/core"; import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue"; import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue"; import CopilotChatUserMessage from "../CopilotChatUserMessage.vue"; const TestWrapper = defineComponent({ components: { CopilotKitProvider, CopilotChatConfigurationProvider, }, template: ` `, }); function createUserMessage(content: string): UserMessage { return { id: "msg-1", role: "user", content, } as UserMessage; } function renderInWrapper(component: ReturnType) { const Wrapped = defineComponent({ components: { TestWrapper, UnderTest: component }, template: ` `, }); return render(Wrapped); } describe("CopilotChatUserMessage Slot System E2E Tests", () => { describe("1. Tailwind Class Slot Override", () => { describe("messageRenderer slot", () => { it("should apply tailwind class string to messageRenderer", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` {{ content }} `, }); renderInWrapper(Host); const renderer = screen.getByTestId("message-renderer"); expect(renderer.classList.contains("bg-blue-500")).toBe(true); expect(renderer.classList.contains("text-white")).toBe(true); expect(renderer.classList.contains("rounded-xl")).toBe(true); }); }); describe("toolbar slot", () => { it("should apply tailwind class string to toolbar", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` Toolbar `, }); renderInWrapper(Host); const toolbar = screen.getByTestId("toolbar"); expect(toolbar.classList.contains("bg-gray-50")).toBe(true); expect(toolbar.classList.contains("border")).toBe(true); }); }); describe("copyButton slot", () => { it("should apply tailwind class string to copyButton", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` Copy `, }); renderInWrapper(Host); expect( screen.getByTestId("copy-btn").classList.contains("text-indigo-500"), ).toBe(true); }); }); describe("editButton slot", () => { it("should apply tailwind class string to editButton", () => { const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage }; }, template: ` Edit `, }); renderInWrapper(Host); expect( screen.getByTestId("edit-btn").classList.contains("text-yellow-500"), ).toBe(true); }); }); describe("branchNavigation slot", () => { it("should apply tailwind class string to branchNavigation", () => { const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onSwitchToBranch }; }, template: ` Branch `, }); renderInWrapper(Host); const branchNav = screen.getByTestId("branch-nav"); expect(branchNav.classList.contains("bg-slate-100")).toBe(true); expect(branchNav.classList.contains("px-2")).toBe(true); }); }); }); describe("2. Property Passing (onClick, disabled, etc.)", () => { describe("messageRenderer slot", () => { it("should pass custom props to messageRenderer", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` {{ content }} `, }); renderInWrapper(Host); expect(screen.queryByTestId("custom-message-renderer")).toBeDefined(); }); }); describe("toolbar slot", () => { it("should pass custom onClick to toolbar", async () => { const onClick = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onClick }; }, template: ` Toolbar `, }); renderInWrapper(Host); await fireEvent.click(screen.getByTestId("custom-toolbar")); expect(onClick).toHaveBeenCalled(); }); }); describe("copyButton slot", () => { it("should pass custom onClick that wraps default behavior", async () => { const customOnClick = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), customOnClick }; }, template: ` Copy `, }); renderInWrapper(Host); await fireEvent.click(screen.getByTestId("custom-copy-button")); expect(customOnClick).toHaveBeenCalled(); }); it("should support disabled state on copyButton", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` Copy `, }); renderInWrapper(Host); expect( screen.getByTestId("disabled-copy-button").hasAttribute("disabled"), ).toBe(true); }); }); describe("editButton slot", () => { it("should call custom onClick on editButton", async () => { const customOnClick = vi.fn(); const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), customOnClick, onEditMessage, }; }, template: ` Edit `, }); renderInWrapper(Host); await fireEvent.click(screen.getByTestId("custom-edit-button")); expect(customOnClick).toHaveBeenCalled(); }); it("should support disabled state on editButton", () => { const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage }; }, template: ` Edit `, }); renderInWrapper(Host); expect( screen.getByTestId("disabled-edit-button").hasAttribute("disabled"), ).toBe(true); }); }); describe("branchNavigation slot", () => { it("should pass custom props to branchNavigation", () => { const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onSwitchToBranch }; }, template: ` Branch `, }); renderInWrapper(Host); expect(screen.queryByTestId("custom-branch-nav")).toBeDefined(); }); }); }); describe("3. Custom Component Receiving Sub-components", () => { it("should allow custom component for messageRenderer", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` [{{ content }}] `, }); renderInWrapper(Host); const custom = screen.queryByTestId("custom-renderer"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("[Hello]"); }); it("should allow custom component for toolbar", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` Actions: `, }); renderInWrapper(Host); const custom = screen.queryByTestId("custom-toolbar-component"); expect(custom).toBeDefined(); expect(custom?.textContent).toContain("Actions"); }); it("should allow custom component for copyButton", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` Copy It `, }); renderInWrapper(Host); const custom = screen.queryByTestId("custom-copy-btn"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("Copy It"); }); it("should allow custom component for editButton", () => { const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage }; }, template: ` Modify `, }); renderInWrapper(Host); expect(screen.queryByTestId("custom-edit-btn")).toBeDefined(); }); it("should allow custom component for branchNavigation", () => { const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onSwitchToBranch }; }, template: ` Branch {{ branchIndex + 1 }} of {{ numberOfBranches }} `, }); renderInWrapper(Host); const custom = screen.queryByTestId("custom-branch-nav"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("Branch 2 of 3"); }); }); describe("4. Children Render Function for Drill-down", () => { it("should provide all bound sub-components via children render function", () => { const onEditMessage = vi.fn(); const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage, onSwitchToBranch, }; }, template: ` renderer toolbar copy edit branch `, }); renderInWrapper(Host); expect(screen.queryByTestId("received-renderer")).toBeDefined(); expect(screen.queryByTestId("received-toolbar")).toBeDefined(); expect(screen.queryByTestId("received-copy")).toBeDefined(); expect(screen.queryByTestId("received-edit")).toBeDefined(); expect(screen.queryByTestId("received-branch")).toBeDefined(); }); it("should pass message and branch info through children render function", () => { const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Test message"), onSwitchToBranch, }; }, template: ` {{ branchIndex }}-{{ numberOfBranches }} `, }); renderInWrapper(Host); expect(screen.getByTestId("branch-info").textContent).toBe("1-3"); }); it("should allow reorganizing sub-components in children render", () => { const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage }; }, template: ` {{ content }} Edit Copy Toolbar `, }); const { container } = renderInWrapper(Host); expect(screen.queryByTestId("custom-layout")).toBeDefined(); expect(container.querySelector(".message-area")).toBeDefined(); expect(container.querySelector(".actions-row")).toBeDefined(); expect(container.querySelector(".toolbar-area")).toBeDefined(); }); }); describe("5. className Override with Tailwind Strings", () => { it("should override root className while preserving default layout classes", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` `, }); const { container } = renderInWrapper(Host); const root = container.querySelector(".custom-root-class"); expect(root).toBeDefined(); expect(root?.classList.contains("bg-purple-50")).toBe(true); }); it("should allow tailwind utilities to override default styles", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello") }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".pt-0")).toBeDefined(); }); it("should merge multiple slot classNames correctly", () => { const onEditMessage = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello"), onEditMessage }; }, template: ` Message Toolbar Copy Edit `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".root-custom")).toBeDefined(); expect(container.querySelector(".renderer-custom")).toBeDefined(); expect(container.querySelector(".toolbar-custom")).toBeDefined(); expect(container.querySelector(".copy-custom")).toBeDefined(); expect(container.querySelector(".edit-custom")).toBeDefined(); }); }); describe("6. Integration and Recursive Slot Application", () => { it("should correctly render all slots with mixed customization", () => { const onEditMessage = vi.fn(); const onSwitchToBranch = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello world"), onEditMessage, onSwitchToBranch, }; }, template: ` Message Toolbar Copy Edit Branch `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".renderer-style")).toBeDefined(); expect(container.querySelector(".toolbar-style")).toBeDefined(); expect(container.querySelector(".copy-style")).toBeDefined(); expect(container.querySelector(".edit-style")).toBeDefined(); expect(container.querySelector(".branch-style")).toBeDefined(); }); it("should work with property objects and class strings mixed", async () => { const onClick = vi.fn(); const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("Hello world"), onClick }; }, template: ` Message Toolbar `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".text-lg")).toBeDefined(); const toolbar = container.querySelector(".flex.gap-4"); if (toolbar) { await fireEvent.click(toolbar); expect(onClick).toHaveBeenCalled(); } }); it("should correctly display user message content", () => { const Host = defineComponent({ components: { CopilotChatUserMessage }, setup() { return { message: createUserMessage("This is my message content") }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByText("This is my message content")).toBeDefined(); }); }); });