import { defineComponent, onUnmounted } from "vue"; import { screen, fireEvent, waitFor, cleanup } from "@testing-library/vue"; import { DEFAULT_AGENT_ID } from "@copilotkit/shared"; import { describe, it, expect, afterEach } from "vitest"; import { renderWithCopilotKit } from "../../__tests__/utils/test-helpers"; import { useSuggestions } from "../use-suggestions"; import { useCopilotKit } from "../../providers/useCopilotKit"; import { SuggestionsProviderAgent } from "../../__tests__/utils/agents"; const TestHarness = defineComponent({ setup() { const { suggestions, isLoading, reloadSuggestions, clearSuggestions } = useSuggestions(); const { copilotkit } = useCopilotKit(); const configId = copilotkit.value.addSuggestionsConfig({ instructions: "Return deterministic suggestions", providerAgentId: DEFAULT_AGENT_ID, consumerAgentId: DEFAULT_AGENT_ID, available: "always", }); onUnmounted(() => { copilotkit.value.removeSuggestionsConfig(configId); }); const handleReload = () => { reloadSuggestions(); }; const handleClear = () => { clearSuggestions(); }; return { suggestions, isLoading, handleReload, handleClear, }; }, template: `
{{ suggestions.length }}
{{ JSON.stringify(suggestions) }}
{{ isLoading ? "loading" : "idle" }}
`, }); afterEach(() => { cleanup(); }); describe("useSuggestions E2E", () => { describe("Basic functionality", () => { it("tracks suggestions stream and loading state", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option A", message: "Take path A", isLoading: false }, { title: "Option B", message: "Take path B", isLoading: false }, ]); const ui = renderWithCopilotKit({ agent, children: TestHarness, }); expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "loading", ); }); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("2"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); expect(screen.getByTestId("suggestions-json").textContent).toContain( "Option A", ); expect(screen.getByTestId("suggestions-json").textContent).toContain( "Option B", ); await fireEvent.click(screen.getByTestId("clear-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); }); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); ui.unmount(); }); it("starts with no suggestions and idle state", () => { const agent = new SuggestionsProviderAgent([]); renderWithCopilotKit({ agent, children: TestHarness, }); expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); expect(screen.getByTestId("suggestions-json").textContent).toBe("[]"); }); it("handles single suggestion", async () => { const agent = new SuggestionsProviderAgent([ { title: "Only Option", message: "The only way", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness, }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("1"); }); const json = screen.getByTestId("suggestions-json").textContent; expect(json).toContain("Only Option"); expect(json).toContain("The only way"); }); it("handles many suggestions", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option 1", message: "First choice", isLoading: false }, { title: "Option 2", message: "Second choice", isLoading: false }, { title: "Option 3", message: "Third choice", isLoading: false }, { title: "Option 4", message: "Fourth choice", isLoading: false }, { title: "Option 5", message: "Fifth choice", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness, }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("5"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); }); describe("Loading state transitions", () => { it("transitions from idle -> loading -> idle correctly", async () => { const agent = new SuggestionsProviderAgent([ { title: "Test", message: "Message", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "loading", ); }); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); it("stays in loading state during multiple reloads", async () => { const agent = new SuggestionsProviderAgent([ { title: "Test", message: "Message", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "loading", ); }); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); }); describe("Clear functionality", () => { it("clears suggestions immediately without loading state", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option A", message: "Message A", isLoading: false }, { title: "Option B", message: "Message B", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("2"); }); await fireEvent.click(screen.getByTestId("clear-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); }); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); it("can clear suggestions while loading", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option A", message: "Message A", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "loading", ); }); await fireEvent.click(screen.getByTestId("clear-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); it("clearing empty suggestions does not cause errors", async () => { const agent = new SuggestionsProviderAgent([]); renderWithCopilotKit({ agent, children: TestHarness }); expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); await fireEvent.click(screen.getByTestId("clear-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); }); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); describe("Reload functionality", () => { it("can reload to get fresh suggestions", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option A", message: "Message A", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("1"); }); await fireEvent.click(screen.getByTestId("clear-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("1"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); it("reload when already has suggestions replaces them", async () => { const agent = new SuggestionsProviderAgent([ { title: "Option A", message: "Message A", isLoading: false }, { title: "Option B", message: "Message B", isLoading: false }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("2"); }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "loading", ); }); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("2"); expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); }); }); describe("Edge cases", () => { it("handles empty suggestions from agent", async () => { const agent = new SuggestionsProviderAgent([]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-loading").textContent).toBe( "idle", ); }); expect(screen.getByTestId("suggestions-count").textContent).toBe("0"); }); it("handles suggestions with special characters", async () => { const agent = new SuggestionsProviderAgent([ { title: 'Option with "quotes"', message: "Message with 'quotes'", isLoading: false, }, { title: "Option with\nnewlines", message: "Message\nwith\nnewlines", isLoading: false, }, ]); renderWithCopilotKit({ agent, children: TestHarness }); await fireEvent.click(screen.getByTestId("reload-suggestions")); await waitFor(() => { expect(screen.getByTestId("suggestions-count").textContent).toBe("2"); }); const json = screen.getByTestId("suggestions-json").textContent; expect(json).toContain("quotes"); expect(json).toContain("newlines"); }); }); });