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 CopilotSidebarView from "../CopilotSidebarView.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: "Test", message: "Test message", isLoading: false }, ]; function renderInWrapper(component: ReturnType) { const Host = defineComponent({ components: { CopilotKitProvider, CopilotChatConfigurationProvider, UnderTest: component, }, template: ` `, }); return render(Host); } describe("CopilotSidebarView 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: { CopilotSidebarView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const header = screen.getByTestId("sidebar-header"); expect(header.classList.contains("bg-gradient-to-r")).toBe(true); }); it("should override default header border with custom styles", () => { const Host = defineComponent({ components: { CopilotSidebarView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("sidebar-header") .classList.contains("border-none"), ).toBe(true); }); }); }); describe("2. Property Passing - Header Slot", () => { describe("header slot", () => { it("should pass custom props to header", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("custom-sidebar-header")).toBeDefined(); }); it("should pass title prop through to header", () => { const Host = defineComponent({ components: { CopilotSidebarView }, 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: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen.getByTestId("custom-header-component").textContent, ).toContain("Custom Sidebar Header"); }); it("should allow passing header props for customization", () => { const Host = defineComponent({ components: { CopilotSidebarView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(screen.queryByText("Chat Sidebar")).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: { CopilotSidebarView, CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("sidebar-message-view") .classList.contains("bg-gray-50"), ).toBe(true); }); }); describe("input slot (inherited)", () => { it("should apply tailwind class string to inherited input", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("sidebar-input")).toBeDefined(); }); }); describe("scrollView slot (inherited)", () => { it("should apply tailwind class string to inherited scrollView", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("sidebar-scroll")).toBeDefined(); }); }); describe("suggestionView slot (inherited)", () => { it("should apply tailwind class string to inherited suggestionView", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages, sampleSuggestions }; }, template: ` `, }); renderInWrapper(Host); const suggestionView = screen.getByTestId("sidebar-suggestion-view"); expect(suggestionView.classList.contains("gap-4")).toBe(true); }); }); }); describe("5. Drill-down into Header Sub-slots", () => { it("should allow customizing header titleContent through props object", () => { const Host = defineComponent({ components: { CopilotSidebarView, 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: { CopilotSidebarView, CopilotModalHeader }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".sidebar-close-btn")).toBeDefined(); }); it("should allow custom component for header via component slot", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const customClose = screen.queryByTestId("sidebar-custom-close"); expect(customClose).toBeDefined(); expect(customClose?.textContent).toBe("Dismiss"); }); }); describe("6. className Override and Mixed Customization", () => { it("should merge multiple slot classNames correctly", () => { const Host = defineComponent({ components: { CopilotSidebarView }, 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: { CopilotSidebarView, 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); } }); it("should support custom width prop", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); const sidebar = container.querySelector("[data-copilot-sidebar]"); expect(sidebar?.getAttribute("style") ?? "").toContain("--sidebar-width"); }); it("should support string width prop", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-sidebar]")).toBeDefined(); }); }); describe("7. Integration Tests", () => { it("should render sidebar with all default components", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-sidebar]")).toBeDefined(); expect( container.querySelector("[data-slot='copilot-modal-header']"), ).toBeDefined(); }); it("should render messages in sidebar", () => { const Host = defineComponent({ components: { CopilotSidebarView }, 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: { CopilotSidebarView }, template: ``, }); const { container } = renderInWrapper(Host); expect(container.querySelector("[data-copilot-sidebar]")).toBeDefined(); }); it("should combine header customization with inherited slot customization", () => { const Host = defineComponent({ components: { CopilotSidebarView, 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(); }); }); describe("8. Toggle Button Slot", () => { describe("toggleButton slot - Tailwind class string", () => { it("should apply tailwind class string to toggle button", () => { const Host = defineComponent({ components: { CopilotSidebarView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const toggleButton = screen.getByTestId("sidebar-toggle"); expect(toggleButton.classList.contains("bg-red-500")).toBe(true); }); }); describe("toggleButton slot - Props object", () => { it("should pass custom props to toggle button", () => { const Host = defineComponent({ components: { CopilotSidebarView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.queryByTestId("sidebar-custom-toggle")).toBeDefined(); }); it("should pass openIcon and closeIcon sub-slot props", () => { const Host = defineComponent({ components: { CopilotSidebarView, CopilotChatToggleButton }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("sidebar-open-icon")).toBeDefined(); expect(screen.getByTestId("sidebar-close-icon")).toBeDefined(); }); }); describe("toggleButton slot - Custom component", () => { it("should allow custom component for toggle button", () => { const Host = defineComponent({ components: { CopilotSidebarView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const custom = screen.queryByTestId("sidebar-custom-toggle-component"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("Open Chat"); }); }); }); });