import { render, screen, fireEvent } from "@testing-library/vue"; import { defineComponent } from "vue"; import { describe, it, expect, vi } from "vitest"; import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue"; import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue"; import CopilotPopupView from "../CopilotPopupView.vue"; import CopilotModalHeader from "../CopilotModalHeader.vue"; import CopilotChatToggleButton from "../CopilotChatToggleButton.vue"; import CopilotChatView from "../CopilotChatView.vue"; const sampleMessages = [ { id: "1", role: "user" as const, content: "Hello" }, { id: "2", role: "assistant" as const, content: "Hi there!" }, ]; const sampleSuggestions = [ { title: "Quick Reply", message: "Reply message", isLoading: false }, ]; function renderInWrapper(component: ReturnType) { const Host = defineComponent({ components: { CopilotKitProvider, CopilotChatConfigurationProvider, UnderTest: component, }, template: ` `, }); return render(Host); } describe("CopilotPopupView Slot System E2E Tests", () => { describe("1. Tailwind Class Slot Override - Header Slot", () => { describe("header slot", () => { it("should apply tailwind class string to header", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const header = screen.getByTestId("popup-header"); expect(header.classList.contains("bg-indigo-500")).toBe(true); }); it("should override default header styles", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("popup-header") .classList.contains("rounded-t-3xl"), ).toBe(true); }); }); }); describe("2. Property Passing - Header Slot", () => { describe("header slot", () => { it("should pass custom props to header", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("custom-popup-header")).toBeDefined(); }); it("should pass title prop through to header", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.queryByText("CopilotKit")).toBeDefined(); }); }); }); describe("3. Custom Component - Header Slot", () => { it("should allow custom component for header", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen.getByTestId("custom-popup-header-component").textContent, ).toContain("AI Assistant"); }); it("should allow passing header props for customization", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(screen.queryByText("Chat Popup")).toBeDefined(); expect(container.querySelector(".text-lg")).toBeDefined(); expect(container.querySelector(".italic")).toBeDefined(); }); }); describe("4. Inherited CopilotChatView Slots", () => { describe("messageView slot (inherited)", () => { it("should apply tailwind class string to inherited messageView", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("popup-message-view") .classList.contains("bg-slate-50"), ).toBe(true); }); }); describe("input slot (inherited)", () => { it("should apply tailwind class string to inherited input", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("popup-input")).toBeDefined(); }); }); describe("scrollView slot (inherited)", () => { it("should apply tailwind class string to inherited scrollView", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("popup-scroll")).toBeDefined(); }); }); describe("suggestionView slot (inherited)", () => { it("should apply tailwind class string to inherited suggestionView", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages, sampleSuggestions }; }, template: ` `, }); renderInWrapper(Host); const suggestionView = screen.getByTestId("popup-suggestions"); expect(suggestionView.classList.contains("flex-wrap")).toBe(true); expect(suggestionView.classList.contains("gap-2")).toBe(true); }); }); describe("input slot (inherited)", () => { it("should apply tailwind class string to inherited input", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("popup-input-2")).toBeDefined(); }); }); }); describe("5. Drill-down into Header Sub-slots", () => { it("should allow customizing header titleContent through props object", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); const titleContent = container.querySelector(".text-xl"); expect(titleContent).toBeDefined(); expect(titleContent?.classList.contains("text-indigo-600")).toBe(true); expect(titleContent?.classList.contains("tracking-wide")).toBe(true); }); it("should allow customizing header closeButton through props object", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".popup-close-btn")).toBeDefined(); }); it("should allow custom component for header via component slot", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const customClose = screen.queryByTestId("popup-custom-close"); expect(customClose).toBeDefined(); expect(customClose?.textContent).toBe("Dismiss"); }); it("should allow custom layout via custom header component", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect( container.querySelector(".custom-popup-header-layout"), ).toBeDefined(); expect(container.querySelector(".close-area")).toBeDefined(); expect(container.querySelector(".title-area")).toBeDefined(); }); }); describe("6. className Override and Mixed Customization", () => { it("should apply className to popup root", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".custom-popup-class")).toBeDefined(); }); it("should merge multiple slot classNames correctly", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".header-style")).toBeDefined(); expect(container.querySelector(".message-style")).toBeDefined(); expect(container.querySelector(".input-style")).toBeDefined(); }); it("should work with property objects and class strings mixed", async () => { const onClick = vi.fn(); const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages, onClick }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".styled-input")).toBeDefined(); const header = container.querySelector(".clickable-header"); if (header) { await fireEvent.click(header); expect(onClick).toHaveBeenCalledTimes(1); } }); }); describe("7. Popup-specific Props", () => { it("should support custom width prop", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); const popup = container.querySelector("[data-copilot-popup]"); expect(popup?.getAttribute("style") ?? "").toContain( "--copilot-popup-width", ); }); it("should support custom height prop", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); const popup = container.querySelector("[data-copilot-popup]"); expect(popup?.getAttribute("style") ?? "").toContain( "--copilot-popup-height", ); }); it("should support string dimensions", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-popup]")).toBeDefined(); }); it("should support clickOutsideToClose prop", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-popup]")).toBeDefined(); }); }); describe("8. Integration Tests", () => { it("should render popup with all default components when open", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-popup]")).toBeDefined(); expect( container.querySelector('[data-slot="copilot-modal-header"]'), ).toBeDefined(); }); it("should render messages in popup", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ``, }); renderInWrapper(Host); expect(screen.queryByText("Hello")).toBeDefined(); expect(screen.queryByText("Hi there!")).toBeDefined(); }); it("should handle empty messages array", () => { const Host = defineComponent({ components: { CopilotPopupView }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-popup]")).toBeDefined(); }); it("should combine header customization with inherited slot customization", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".custom-header-root")).toBeDefined(); expect(container.querySelector(".custom-title")).toBeDefined(); expect(container.querySelector(".custom-message")).toBeDefined(); expect(container.querySelector(".custom-input")).toBeDefined(); expect(container.querySelector(".custom-scroll")).toBeDefined(); }); it("should not render popup content when closed", () => { const Host = defineComponent({ components: { CopilotKitProvider, CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = render(Host); expect(container.querySelector("[data-copilot-popup]")).toBeNull(); }); }); describe("9. Toggle Button Slot", () => { describe("toggleButton slot - Tailwind class string", () => { it("should apply tailwind class string to toggle button", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const toggleButton = screen.getByTestId("popup-toggle"); expect(toggleButton.classList.contains("bg-purple-500")).toBe(true); }); }); describe("toggleButton slot - Props object", () => { it("should pass custom props to toggle button", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.queryByTestId("popup-custom-toggle")).toBeDefined(); }); it("should pass openIcon and closeIcon sub-slot props", () => { const Host = defineComponent({ components: { CopilotPopupView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("popup-open-icon")).toBeDefined(); expect(screen.getByTestId("popup-close-icon")).toBeDefined(); }); }); describe("toggleButton slot - Custom component", () => { it("should allow custom component for toggle button", () => { const Host = defineComponent({ components: { CopilotPopupView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const custom = screen.queryByTestId("popup-custom-toggle-component"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("Open Chat"); }); }); }); });