import { render, screen, fireEvent } from "@testing-library/vue"; import { computed, defineComponent } from "vue"; import { describe, it, expect, vi } from "vitest"; import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue"; import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue"; import { useCopilotChatConfiguration } from "../../../providers/useCopilotChatConfiguration"; import { CopilotModalHeader } from "../index"; import CopilotModalHeaderTitle from "../CopilotModalHeaderTitle"; import CopilotModalHeaderCloseButton from "../CopilotModalHeaderCloseButton"; const ModalStateProbe = defineComponent({ setup() { const config = useCopilotChatConfiguration(); const modalState = computed(() => String(config.value?.isModalOpen)); return { modalState }; }, template: `{{ modalState }}`, }); const TestWrapper = defineComponent({ components: { CopilotKitProvider, CopilotChatConfigurationProvider, }, template: ` `, }); function renderInWrapper(component: ReturnType) { return render(component, { global: { components: { TestWrapper, }, }, wrapper: TestWrapper, }); } describe("CopilotModalHeader Slot System E2E Tests", () => { // ============================================================================ // 1. TAILWIND CLASS TESTS // ============================================================================ describe("1. Tailwind Class Slot Override", () => { describe("titleContent slot", () => { it("should apply tailwind class string to titleContent", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const title = screen.getByTestId("custom-title"); expect(title.classList.contains("text-2xl")).toBe(true); expect(title.classList.contains("font-bold")).toBe(true); expect(title.classList.contains("text-blue-600")).toBe(true); }); it("should merge titleContent classes with defaults", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderTitle }, template: ` `, }); renderInWrapper(Host); const title = screen.getByTestId("custom-title"); expect(title.classList.contains("custom-title-class")).toBe(true); expect(title.classList.contains("cpk:text-foreground")).toBe(true); }); }); describe("closeButton slot", () => { it("should apply tailwind class string to closeButton", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderCloseButton }, template: ` `, }); renderInWrapper(Host); const closeBtn = screen.getByTestId("custom-close-btn"); expect(closeBtn.classList.contains("bg-red-100")).toBe(true); expect(closeBtn.classList.contains("text-red-600")).toBe(true); }); it("should override default rounded-full with custom border radius", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderCloseButton }, template: ` `, }); renderInWrapper(Host); const closeBtn = screen.getByTestId("custom-close-btn"); expect(closeBtn.classList.contains("rounded-lg")).toBe(true); }); }); }); // ============================================================================ // 2. PROPERTY PASSING TESTS // ============================================================================ describe("2. Property Passing (onClick, disabled, etc.)", () => { describe("titleContent slot", () => { it("should pass custom props to titleContent", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const title = screen.getByTestId("custom-title"); expect(title).toBeDefined(); expect(title.textContent).toBe("Test Title"); }); it("should pass custom onClick to titleContent", async () => { const onClick = vi.fn(); const Host = defineComponent({ components: { CopilotModalHeader }, setup() { return { onClick }; }, template: ` `, }); renderInWrapper(Host); const title = screen.getByTestId("clickable-title"); await fireEvent.click(title); expect(onClick).toHaveBeenCalledTimes(1); }); }); describe("closeButton slot", () => { it("should pass custom onClick that overrides default close behavior", async () => { const customOnClick = vi.fn(); const Host = defineComponent({ components: { CopilotModalHeader, ModalStateProbe }, setup() { return { customOnClick }; }, template: ` `, }); render(Host, { global: { components: { CopilotKitProvider, CopilotChatConfigurationProvider, }, }, wrapper: CopilotKitProvider, }); expect(screen.getByTestId("modal-state").textContent).toBe("true"); const closeBtn = screen.getByTestId("custom-close-btn"); await fireEvent.click(closeBtn); expect(customOnClick).toHaveBeenCalledTimes(1); expect(screen.getByTestId("modal-state").textContent).toBe("true"); }); it("should support disabled state on closeButton", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const closeBtn = screen.getByTestId("custom-close-btn"); expect(closeBtn.hasAttribute("disabled")).toBe(true); }); it("should pass custom aria-label to closeButton", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderCloseButton }, template: ` `, }); renderInWrapper(Host); const closeBtn = screen.getByLabelText("Dismiss dialog"); expect(closeBtn).toBeDefined(); }); }); }); // ============================================================================ // 3. CUSTOM COMPONENT TESTS // ============================================================================ describe("3. Custom Component Receiving Sub-components", () => { it("should allow custom component for titleContent", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const custom = screen.getByTestId("custom-title-component"); expect(custom.tagName).toBe("H1"); expect(custom.textContent).toBe("Custom Header"); }); it("should allow custom component for closeButton", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const custom = screen.getByTestId("custom-close-btn"); expect(custom).toBeDefined(); expect(custom.textContent).toBe("X Close"); }); it("should call onClick when custom closeButton is clicked", async () => { const handleClose = vi.fn(); const Host = defineComponent({ components: { CopilotModalHeader, ModalStateProbe }, setup() { return { handleClose, }; }, template: ` `, }); render(Host, { global: { components: { CopilotKitProvider, CopilotChatConfigurationProvider, }, }, wrapper: CopilotKitProvider, }); expect(screen.getByTestId("modal-state").textContent).toBe("true"); const closeBtn = screen.getByTestId("custom-close-btn"); await fireEvent.click(closeBtn); expect(handleClose).toHaveBeenCalledTimes(1); expect(screen.getByTestId("modal-state").textContent).toBe("false"); }); }); // ============================================================================ // 4. CHILDREN RENDER FUNCTION (DRILL-DOWN) TESTS // ============================================================================ describe("4. Children Render Function for Drill-down", () => { it("should provide bound titleContent and closeButton via children render function", async () => { const Host = defineComponent({ components: { CopilotModalHeader, ModalStateProbe }, template: ` `, }); render(Host, { global: { components: { CopilotKitProvider, CopilotChatConfigurationProvider, }, }, wrapper: CopilotKitProvider, }); expect(screen.queryByTestId("children-render")).toBeDefined(); expect(screen.queryByTestId("received-title")?.textContent).toBe( "Test Title", ); expect(screen.getByTestId("modal-state").textContent).toBe("true"); await fireEvent.click(screen.getByTestId("received-close")); expect(screen.getByTestId("modal-state").textContent).toBe("false"); }); it("should pass resolved title through children render function", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); expect(screen.getByTestId("resolved-title").textContent).toBe( "My Custom Title", ); }); it("should allow custom layout via children render function", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); const { container } = renderInWrapper(Host); const customLayout = screen.queryByTestId("custom-header-layout"); expect(customLayout).toBeDefined(); expect(container.querySelector(".left-side")).toBeDefined(); expect(container.querySelector(".center")).toBeDefined(); expect(container.querySelector(".right-side")).toBeDefined(); expect(customLayout?.textContent).toContain("Subtitle: Custom Layout"); }); it("should allow completely custom rendering without using provided components", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ` `, }); renderInWrapper(Host); const customNav = screen.queryByTestId("custom-nav"); expect(customNav).toBeDefined(); expect(customNav?.textContent).toContain("Custom Nav Header"); expect(screen.queryByText("Back")).toBeDefined(); expect(screen.queryByText("Menu")).toBeDefined(); }); }); // ============================================================================ // 5. CLASSNAME OVERRIDE TESTS // ============================================================================ describe("5. className Override with Tailwind Strings", () => { it("should apply className to header root element", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ``, }); const { container } = renderInWrapper(Host); const header = container.querySelector(".custom-header-class"); expect(header).toBeDefined(); expect(header?.tagName).toBe("HEADER"); expect(header?.classList.contains("bg-slate-100")).toBe(true); }); it("should override default border and padding", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ``, }); const { container } = renderInWrapper(Host); const header = container.querySelector(".border-0"); expect(header).toBeDefined(); expect(header?.classList.contains("p-2")).toBe(true); }); it("should merge multiple slot classNames correctly", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderTitle, CopilotModalHeaderCloseButton, }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".header-custom")).toBeDefined(); expect(container.querySelector(".title-custom")).toBeDefined(); expect(container.querySelector(".close-custom")).toBeDefined(); }); }); // ============================================================================ // 6. INTEGRATION TESTS // ============================================================================ describe("6. Integration Tests", () => { it("should correctly render all slots with mixed customization", () => { const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderTitle, CopilotModalHeaderCloseButton, }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".header-style")).toBeDefined(); expect(container.querySelector(".title-style")).toBeDefined(); expect(container.querySelector(".close-style")).toBeDefined(); }); it("should work with property objects and class strings mixed", async () => { const onClick = vi.fn(); const Host = defineComponent({ components: { CopilotModalHeader, CopilotModalHeaderCloseButton }, setup() { return { onClick }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".text-xl")).toBeDefined(); const closeBtn = container.querySelector(".bg-gray-200"); if (closeBtn) { await fireEvent.click(closeBtn); expect(onClick).toHaveBeenCalled(); } }); it("should use default title from configuration when not provided", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ``, }); renderInWrapper(Host); const header = document.querySelector( '[data-slot="copilot-modal-header"]', ); expect(header).toBeDefined(); }); it("should render title content correctly", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ``, }); renderInWrapper(Host); expect(screen.getByText("My Chat Header")).toBeDefined(); }); it("should render close button with X icon", () => { const Host = defineComponent({ components: { CopilotModalHeader }, template: ``, }); const { container } = renderInWrapper(Host); const closeBtn = container.querySelector('button[aria-label="Close"]'); expect(closeBtn).toBeDefined(); expect(closeBtn?.querySelector("svg")).toBeDefined(); }); }); });