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",
],
},
},