From 8541acb491d43ab88f65c3e1f848147953a6b8eb Mon Sep 17 00:00:00 2001 From: rishab11250 Date: Tue, 16 Jun 2026 10:18:50 +0530 Subject: [PATCH] test: add copy/share button tests, clipboard polyfill, and CI test step Closes #612 - Add navigator.clipboard polyfill to Vitest setup for copy/share tests - Add copy button test to MessageBubble: verifies clipboard.writeText called with message content and 'Copied' label appears - Add share button test to MessageBubble: mocks API POST, verifies api.post called with correct chat share path and clipboard receives the full share URL (origin + share_url) - Update CI frontend-check job to run 'npm test' (Vitest) before the Next.js production build check --- .github/workflows/ci.yml | 4 +++ .../components/chat/MessageBubble.test.tsx | 36 ++++++++++++++++++- frontend/src/test/setup.ts | 8 +++++ 3 files changed, 47 insertions(+), 1 deletion(-) 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) => ({