import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; import { describe, it, expect, vi } from "vitest"; import { CopilotModalHeader } from "../CopilotModalHeader"; import { CopilotKitProvider } from "../../../providers/CopilotKitProvider"; import { CopilotChatConfigurationProvider } from "../../../providers/CopilotChatConfigurationProvider"; // Wrapper to provide required context const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} ); 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 { container } = render( , ); const title = container.querySelector(".text-2xl"); expect(title).toBeDefined(); 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 { container } = render( , ); const title = container.querySelector(".custom-title-class"); expect(title).toBeDefined(); // Should still have default text-foreground expect(title?.classList.contains("cpk:text-foreground")).toBe(true); }); }); describe("closeButton slot", () => { it("should apply tailwind class string to closeButton", () => { const { container } = render( , ); const closeBtn = container.querySelector(".bg-red-100"); expect(closeBtn).toBeDefined(); expect(closeBtn?.classList.contains("text-red-600")).toBe(true); }); it("should override default rounded-full with custom border radius", () => { const { container } = render( , ); const closeBtn = container.querySelector(".rounded-lg"); expect(closeBtn).toBeDefined(); }); }); }); // ============================================================================ // 2. PROPERTY PASSING TESTS // ============================================================================ describe("2. Property Passing (onClick, disabled, etc.)", () => { describe("titleContent slot", () => { it("should pass custom props to titleContent", () => { render( , ); const title = screen.queryByTestId("custom-title"); expect(title).toBeDefined(); expect(title?.textContent).toBe("Test Title"); }); it("should pass custom onClick to titleContent", () => { const onClick = vi.fn(); render( , ); const title = screen.queryByTestId("clickable-title"); if (title) { fireEvent.click(title); expect(onClick).toHaveBeenCalled(); } }); }); describe("closeButton slot", () => { it("should pass custom onClick that overrides default close behavior", () => { const customOnClick = vi.fn(); const { container } = render( , ); const closeBtn = container.querySelector('button[aria-label="Close"]'); if (closeBtn) { fireEvent.click(closeBtn); expect(customOnClick).toHaveBeenCalled(); } }); it("should support disabled state on closeButton", () => { const { container } = render( , ); const closeBtn = container.querySelector('button[aria-label="Close"]'); if (closeBtn) { expect(closeBtn.hasAttribute("disabled")).toBe(true); } }); it("should pass custom aria-label to closeButton", () => { const { container } = render( , ); const closeBtn = container.querySelector( 'button[aria-label="Dismiss dialog"]', ); expect(closeBtn).toBeDefined(); }); }); }); // ============================================================================ // 3. CUSTOM COMPONENT TESTS // ============================================================================ describe("3. Custom Component Receiving Sub-components", () => { it("should allow custom component for titleContent", () => { const CustomTitle: React.FC = ({ children }) => (

{children}

); render( , ); const custom = screen.queryByTestId("custom-title-component"); expect(custom).toBeDefined(); expect(custom?.tagName).toBe("H1"); expect(custom?.textContent).toBe("Custom Header"); }); it("should allow custom component for closeButton", () => { const CustomCloseButton: React.FC< React.ButtonHTMLAttributes > = (props) => ( ); render( , ); const custom = screen.queryByTestId("custom-close-btn"); expect(custom).toBeDefined(); expect(custom?.textContent).toBe("X Close"); }); it("should call onClick when custom closeButton is clicked", () => { const handleClose = vi.fn(); const CustomCloseButton: React.FC< React.ButtonHTMLAttributes > = ({ onClick, ...props }) => ( ); render( , ); const closeBtn = screen.queryByTestId("custom-close-btn"); if (closeBtn) { fireEvent.click(closeBtn); expect(handleClose).toHaveBeenCalled(); } }); }); // ============================================================================ // 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", () => { const childrenFn = vi.fn((props) => (
{props.titleContent}
{props.closeButton}
)); render( {childrenFn} , ); expect(childrenFn).toHaveBeenCalled(); const callArgs = childrenFn.mock.calls[0][0]; expect(callArgs).toHaveProperty("titleContent"); expect(callArgs).toHaveProperty("closeButton"); expect(callArgs).toHaveProperty("title"); expect(screen.queryByTestId("children-render")).toBeDefined(); }); it("should pass resolved title through children render function", () => { const childrenFn = vi.fn(() =>
); render( {childrenFn} , ); const callArgs = childrenFn.mock.calls[0][0]; expect(callArgs.title).toBe("My Custom Title"); }); it("should allow custom layout via children render function", () => { const { container } = render( {({ titleContent, closeButton, title }) => (
{closeButton}
{titleContent}
Subtitle: {title}
)}
, ); 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", () => { render( {() => ( )} , ); 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 { container } = render( , ); 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 { container } = render( , ); 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 { container } = render( , ); 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 { container } = render( , ); 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", () => { const onClick = vi.fn(); const { container } = render( , ); expect(container.querySelector(".text-xl")).toBeDefined(); const closeBtn = container.querySelector(".bg-gray-200"); if (closeBtn) { fireEvent.click(closeBtn); expect(onClick).toHaveBeenCalled(); } }); it("should use default title from configuration when not provided", () => { render( , ); // Should render with default "CopilotKit" title from CopilotChatDefaultLabels const header = document.querySelector( '[data-slot="copilot-modal-header"]', ); expect(header).toBeDefined(); }); it("should render title content correctly", () => { render( , ); expect(screen.getByText("My Chat Header")).toBeDefined(); }); it("should render close button with X icon", () => { const { container } = render( , ); const closeBtn = container.querySelector('button[aria-label="Close"]'); expect(closeBtn).toBeDefined(); // Should contain an SVG icon expect(closeBtn?.querySelector("svg")).toBeDefined(); }); }); });