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();
});
});
});