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 CopilotChatView from "../CopilotChatView.vue"; import CopilotChatMessageView from "../CopilotChatMessageView.vue"; import CopilotChatInput from "../CopilotChatInput.vue"; import CopilotChatSuggestionView from "../CopilotChatSuggestionView.vue"; import CopilotChatAssistantMessage from "../CopilotChatAssistantMessage.vue"; import CopilotChatUserMessage from "../CopilotChatUserMessage.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 }, { title: "Another", message: "Another message", isLoading: false }, ]; function renderInWrapper(component: ReturnType) { const Host = defineComponent({ components: { CopilotKitProvider, CopilotChatConfigurationProvider, UnderTest: component, }, template: `
`, }); return render(Host); } describe("CopilotChatView Slot System E2E Tests", () => { describe("1. Tailwind Class Slot Override", () => { describe("messageView slot", () => { it("should apply tailwind class string to messageView", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const messageView = screen.getByTestId("message-view"); expect(messageView.classList.contains("bg-red-500")).toBe(true); expect(messageView.classList.contains("text-white")).toBe(true); expect(messageView.classList.contains("p-4")).toBe(true); }); it("should override default className with tailwind string", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".custom-override-class")).toBeDefined(); }); }); describe("scrollView slot", () => { it("should apply tailwind class string to scrollView", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const scrollView = screen.getByTestId("scroll-view"); expect(scrollView.classList.contains("overflow-y-auto")).toBe(true); expect(scrollView.classList.contains("bg-gray-100")).toBe(true); }); }); describe("scrollToBottomButton slot (nested under scrollView)", () => { it("should apply tailwind class string to scrollToBottomButton via scrollView", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const button = screen.queryByTestId("scroll-bottom-btn"); if (button) { expect(button.classList.contains("rounded-full")).toBe(true); } }); }); describe("input slot", () => { it("should apply tailwind class string to input", () => { const Host = defineComponent({ components: { CopilotChatView, CopilotChatInput }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("input-slot") .classList.contains("border-purple-500"), ).toBe(true); }); }); describe("feather slot (via scrollView)", () => { it("should apply tailwind class string to feather via scrollView", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const feather = screen.getByTestId("feather-slot"); expect(feather.classList.contains("text-green-500")).toBe(true); expect(feather.classList.contains("font-bold")).toBe(true); }); }); describe("suggestionView slot", () => { it("should apply tailwind class string to suggestionView", () => { const Host = defineComponent({ components: { CopilotChatView, CopilotChatSuggestionView }, setup() { return { sampleMessages, sampleSuggestions }; }, template: ` `, }); renderInWrapper(Host); expect( screen .getByTestId("suggestion-view") .classList.contains("bg-indigo-50"), ).toBe(true); }); }); describe("className vs tailwind string precedence", () => { it("tailwind string should completely replace className (not merge)", () => { const Host = defineComponent({ components: { CopilotChatView, CopilotChatInput }, setup() { return { sampleMessages }; }, template: ` `, }); const { container } = renderInWrapper(Host); expect(container.querySelector(".only-this-class")).toBeDefined(); }); }); describe("non-tailwind inline styles should still work", () => { it("should accept style prop alongside className override", () => { const Host = defineComponent({ components: { CopilotChatView, CopilotChatInput }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const customInput = screen.getByTestId("custom-input"); expect(customInput.style.backgroundColor).toBe("rgb(255, 0, 0)"); }); }); }); describe("2. Properties Slot Override", () => { describe("scrollToBottomButton props (nested under scrollView)", () => { it("should pass onClick handler to scrollToBottomButton via scrollView", async () => { const handleClick = vi.fn(); const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages, handleClick }; }, template: ` `, }); renderInWrapper(Host); const btn = screen.queryByTestId("scroll-button"); if (btn) { await fireEvent.click(btn); expect(handleClick).toHaveBeenCalled(); } }); it("should pass disabled prop to scrollToBottomButton via scrollView", () => { const Host = defineComponent({ components: { CopilotChatView }, setup() { return { sampleMessages }; }, template: ` `, }); renderInWrapper(Host); const btn = screen.queryByTestId("scroll-button-disabled"); if (btn) { expect(btn.hasAttribute("disabled")).toBe(true); } }); }); describe("input props", () => { it("should pass onFocus handler to input", async () => { const handleFocus = vi.fn(); const Host = defineComponent({ components: { CopilotChatView, CopilotChatInput }, setup() { return { sampleMessages, handleFocus }; }, template: `