import React from "react"; import { Link } from "./index"; import { fireEvent, render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; // Mock next/link jest.mock("next/link", () => { const ReactModule = require("react"); const MockLink = ReactModule.forwardRef( ( { children, href, className, onClick, ...rest }: any, ref: React.Ref ) => ( {children} ) ); MockLink.displayName = "MockNextLink"; return MockLink; }); describe("Link", () => { describe("Rendering", () => { it("renders children content", () => { render(Click me); expect(screen.getByText("Click me")).toBeInTheDocument(); }); it("has correct displayName", () => { expect(Link.displayName).toBe("Link"); }); it("renders as a Next.js Link for internal links", () => { render(Internal); const el = screen.getByText("Internal"); expect(el.closest("a")).toHaveAttribute("href", "/internal"); }); it("renders as for external links", () => { render( External ); const el = screen.getByText("External"); expect(el.closest("a")).toHaveAttribute("href", "https://example.com"); }); it("renders as for http links even without external prop", () => { render(Auto External); const el = screen.getByText("Auto External"); expect(el.closest("a")).toHaveAttribute("href", "https://example.com"); }); }); describe("Variants", () => { it("applies unstyled variant with no extra classes", () => { render( Unstyled ); const el = screen.getByText("Unstyled").closest("a"); expect(el?.className).toContain("link--unstyled"); }); it("applies default variant classes", () => { render( Default ); const el = screen.getByText("Default").closest("a"); expect(el?.className).toContain("link--default"); expect(el?.className).toContain("underline"); }); }); describe("Disabled state", () => { it("adds disabled class when disabled", () => { render( Disabled ); const el = screen.getByText("Disabled").closest("a"); expect(el?.className).toContain("link--disabled"); }); it("sets aria-disabled when disabled", () => { render( Disabled ); const el = screen.getByText("Disabled").closest("a"); expect(el).toHaveAttribute("aria-disabled", "true"); }); it("sets tabIndex -1 when disabled", () => { render( Disabled ); const el = screen.getByText("Disabled").closest("a"); expect(el).toHaveAttribute("tabindex", "-1"); }); it("applies disabled accessibility attributes on internal links", () => { render( Disabled ); const el = screen.getByText("Disabled").closest("a"); // disabled links render as a plain , not via next/link expect(el).not.toHaveAttribute("data-mock", "next-link"); expect(el).toHaveAttribute("aria-disabled", "true"); expect(el).toHaveAttribute("tabindex", "-1"); expect(el).not.toHaveAttribute("href"); }); it("prevents click when disabled", () => { const onClick = jest.fn(); render( Disabled ); const el = screen.getByText("Disabled").closest("a")!; fireEvent.click(el); expect(onClick).not.toHaveBeenCalled(); }); }); describe("External links", () => { it("adds target=_blank for external links", () => { render( External ); const el = screen.getByText("External").closest("a"); expect(el).toHaveAttribute("target", "_blank"); }); it("adds rel=noopener for external links", () => { render( External ); const el = screen.getByText("External").closest("a"); expect(el).toHaveAttribute("rel", "noopener"); }); it("does not add target for disabled external links", () => { render( External ); const el = screen.getByText("External").closest("a"); expect(el).not.toHaveAttribute("target", "_blank"); }); }); describe("Event handling", () => { it("calls onClick handler when clicked", () => { const onClick = jest.fn(); render( Click ); const el = screen.getByText("Click").closest("a")!; fireEvent.click(el); expect(onClick).toHaveBeenCalledTimes(1); }); }); describe("Ref forwarding", () => { it("forwards ref to the anchor element", () => { const ref = React.createRef(); render( Ref ); expect(ref.current).toBeInstanceOf(HTMLAnchorElement); }); it("forwards ref to the anchor element for internal (next/link) links", () => { const ref = React.createRef(); render( Internal Ref ); expect(ref.current).toBeInstanceOf(HTMLAnchorElement); expect(ref.current).toHaveAttribute("data-mock", "next-link"); }); }); describe("Custom className and style", () => { it("applies custom className", () => { render( Custom ); const el = screen.getByText("Custom").closest("a"); expect(el?.className).toContain("custom-class"); }); it("applies inline style", () => { render( Styled ); const el = screen.getByText("Styled").closest("a")!; expect(el.style.color).toBe("red"); }); }); });