diff --git a/sonar-project.properties b/sonar-project.properties index e28c48c..d3a42db 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,6 +3,7 @@ sonar.projectKey=amrabed.github.io sonar.projectName=amrabed.github.io sonar.sources=src sonar.tests=src +sonar.exclusions=**/*.test.ts,**/*.test.tsx sonar.test.inclusions=**/*.test.ts,**/*.test.tsx sonar.javascript.lcov.reportPaths=coverage/lcov.info sonar.typescript.lcov.reportPaths=coverage/lcov.info diff --git a/src/components/publication.test.tsx b/src/components/publication.test.tsx index b7a1d25..9294114 100644 --- a/src/components/publication.test.tsx +++ b/src/components/publication.test.tsx @@ -1,12 +1,24 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render } from "@testing-library/react"; +import { render, fireEvent, act } from "@testing-library/react"; import publications from "@/data/publications"; import PublicationCard from "./publication"; describe("PublicationCard", () => { + beforeEach(() => { + // Mock navigator.clipboard.writeText + const mockClipboard = { + writeText: vi.fn().mockResolvedValue(undefined), + }; + Object.defineProperty(globalThis, "navigator", { + value: { clipboard: mockClipboard }, + writable: true, + }); + vi.useFakeTimers(); + }); + it("renders correctly", () => { if (publications.length > 0) { const { getByText } = render( @@ -15,4 +27,51 @@ describe("PublicationCard", () => { expect(getByText(publications[0].title)).toBeInTheDocument(); } }); + + it("renders a publication with a DOI link correctly", () => { + const pubWithDoi = publications.find(p => p.links && p.links.doi); + if (pubWithDoi) { + const { getByLabelText } = render( + , + ); + expect(getByLabelText("DOI")).toBeInTheDocument(); + } + }); + + it("triggers and copies BibTeX successfully with visual feedback", async () => { + if (publications.length > 0) { + const { getByLabelText, getByText, queryByText } = render( + , + ); + + // Open Popover + const citeBtn = getByLabelText("Cite"); + fireEvent.click(citeBtn); + + // Verify Popover content displays and the copy button is present + const copyBtnBefore = getByLabelText("Copy BibTeX to clipboard"); + expect(copyBtnBefore).toBeInTheDocument(); + + // Click the Copy BibTeX button + fireEvent.click(copyBtnBefore); + + // Should show the copied checkmark, "Copied!" tooltip/state, and "Copied" text + expect(navigator.clipboard.writeText).toHaveBeenCalled(); + + const copyBtnAfter = getByLabelText("BibTeX copied"); + expect(copyBtnAfter).toBeInTheDocument(); + + const copiedText = getByText("Copied"); + expect(copiedText).toBeInTheDocument(); + + // Fast-forward timers by 2 seconds + act(() => { + vi.advanceTimersByTime(2000); + }); + + // The button state should reset back to "Copy BibTeX to clipboard" + expect(getByLabelText("Copy BibTeX to clipboard")).toBeInTheDocument(); + expect(queryByText("Copied")).not.toBeInTheDocument(); + } + }); }); diff --git a/src/components/publication.tsx b/src/components/publication.tsx index 1c12dd6..177790b 100644 --- a/src/components/publication.tsx +++ b/src/components/publication.tsx @@ -49,7 +49,7 @@ const CiteButton = ({ publication }: { publication: Publication }) => { >
{copied && ( - + Copied )} @@ -63,16 +63,20 @@ const CiteButton = ({ publication }: { publication: Publication }) => { setCopied(true); setTimeout(() => setCopied(false), 2000); }} - aria-label="Copy BibTeX to clipboard" + aria-label={copied ? "BibTeX copied" : "Copy BibTeX to clipboard"} isIconOnly className="text-muted-foreground hover:text-primary transition-colors" > - + {copied ? ( + + ) : ( + + )} - Copy to clipboard + {copied ? "Copied!" : "Copy to clipboard"}
diff --git a/vitest.config.ts b/vitest.config.ts index 94b179a..73ddd54 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -29,6 +29,7 @@ export default defineConfig({ "src/components/featured-section-container.tsx", "src/components/header.tsx", "src/components/chat/client.tsx", + "src/components/publication.tsx", ], }, },