diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74838149..bd4a95ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,6 +233,10 @@ jobs: working-directory: frontend run: npm run lint + - name: Frontend unit tests (Vitest) + working-directory: frontend + run: npm test + - name: Next.js build (production bundle check) working-directory: frontend run: npm run build diff --git a/frontend/src/components/chat/MessageBubble.test.tsx b/frontend/src/components/chat/MessageBubble.test.tsx index f48acd9e..fd671a9f 100644 --- a/frontend/src/components/chat/MessageBubble.test.tsx +++ b/frontend/src/components/chat/MessageBubble.test.tsx @@ -1,7 +1,8 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import MessageBubble from "./MessageBubble"; import type { ChatMsg } from "@/store/chat-store"; +import { api } from "@/lib/api"; vi.mock("@/lib/api", () => ({ api: { @@ -52,4 +53,37 @@ describe("MessageBubble", () => { expect(screen.getByLabelText("Copy response")).toBeInTheDocument(); expect(screen.getByLabelText("Share response")).toBeInTheDocument(); }); + + it("copies assistant message content to clipboard when copy button is clicked", () => { + const content = "This is some assistant response text"; + render(); + + fireEvent.click(screen.getByLabelText("Copy response")); + + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(content); + expect(screen.getByLabelText("Copied")).toBeInTheDocument(); + }); + + it("shares assistant message via API and copies share link to clipboard", async () => { + vi.mocked(api.post).mockResolvedValueOnce({ + message_id: "msg-1", + share_url: "/shared/abc-123", + }); + + render( + , + ); + + fireEvent.click(screen.getByLabelText("Share response")); + + await waitFor(() => { + expect(api.post).toHaveBeenCalledWith("/api/v1/chat/share/msg-1"); + }); + + expect(navigator.clipboard.writeText).toHaveBeenCalledWith( + "http://localhost:3000/shared/abc-123", + ); + }); }); diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts index 51e7ef37..4309d5f5 100644 --- a/frontend/src/test/setup.ts +++ b/frontend/src/test/setup.ts @@ -7,6 +7,14 @@ afterEach(() => { vi.clearAllMocks(); }); +Object.defineProperty(navigator, "clipboard", { + writable: true, + value: { + writeText: vi.fn(), + readText: vi.fn(), + }, +}); + Object.defineProperty(window, "matchMedia", { writable: true, value: vi.fn().mockImplementation((query: string) => ({