import { fireEvent, render, screen } from "@testing-library/react"; import Hero from "./Hero"; import { HERO_CAPTIONS } from "./hero-captions"; // o Link do Next, trocado por um que mostra o `prefetch` recebido: HoverPrefetchLink começa em false jest.mock("next/link", () => ({ __esModule: true, default: ({ children, prefetch, ...props }: { children?: React.ReactNode; prefetch?: boolean | null } & Record< string, unknown >) => ( {children} ), })); const clients = [ { id: "sev-modaintima", name: "SEV Moda Íntima" }, { id: "lavacao-047", name: "Lavação 047" }, { id: "sc-plus", name: "SC Plus Aquicultura" }, { id: "as-servicos", name: "AS Serviços" }, ]; describe("Hero", () => { beforeEach(() => { jest.spyOn(window, "open").mockImplementation(() => null); }); afterEach(() => { jest.restoreAllMocks(); }); it("opens WhatsApp straight from the main button, with the subject of the home, while 'Ver projetos entregues' stays secondary", () => { render(); const contactLink = screen.getByRole("link", { name: /falar sobre meu projeto/i, }); expect(contactLink).toHaveAccessibleName("Falar sobre meu projeto"); expect(contactLink.getAttribute("href")).toMatch( /^https:\/\/wa\.me\/\d+\?/, ); expect(contactLink).toHaveAttribute("target", "_blank"); expect(contactLink).toHaveAttribute("rel", "noopener noreferrer"); const text = new URL( contactLink.getAttribute("href") ?? "", ).searchParams.get("text"); expect(text).toBe( "Olá, Evandro! Quero conversar sobre um site ou sistema para a minha empresa. Contato iniciado pelo site evandro.dev.br.", ); // o botão primário é o único com o fundo limão; o secundário é contorno expect(contactLink.className).toContain("bg-[var(--slash)]"); const projects = screen.getByRole("link", { name: /ver projetos/i }); expect(projects).toHaveAttribute("href", "/cases"); expect(projects.className).not.toContain("bg-[var(--slash)]"); expect(projects).not.toHaveAttribute("target"); }); it("keeps the headline as one readable sentence despite the per-word animation", () => { render(); expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent( "Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha.", ); }); it("highlights only the short stretch 'pedidos pelo WhatsApp' in lime", () => { render(); const highlighted = Array.from( screen.getByRole("heading", { level: 1 }).querySelectorAll(".hero-word"), ) .filter((word) => word.className.includes("text-[var(--slash)]")) .map((word) => word.textContent); expect(highlighted).toEqual(["pedidos", "pelo", "WhatsApp"]); }); it("keeps the single LinkedIn profile and the e-mail, with no GitHub link", () => { render(); expect(screen.getByRole("link", { name: "LinkedIn" })).toHaveAttribute( "href", "https://www.linkedin.com/in/evandrodevbr/", ); expect(screen.getByRole("link", { name: "Email" })).toHaveAttribute( "href", "mailto:evandrofonsecajunior@gmail.com", ); const linkedin = screen.getByRole("link", { name: "LinkedIn" }); expect(linkedin).toHaveAttribute("target", "_blank"); expect(linkedin).toHaveAttribute("rel", "noopener noreferrer"); expect(screen.queryByRole("link", { name: "GitHub" })).toBeNull(); for (const link of screen.getAllByRole("link")) { expect(link.getAttribute("href") ?? "").not.toMatch(/github\.com/); } }); it("states who the work is for and how the contact works", () => { render(); expect( screen.getByText( "Do catálogo com pedido no WhatsApp ao painel da sua equipe. Você fala com quem desenvolve.", ), ).toBeInTheDocument(); expect( screen.getByRole("link", { name: /falar sobre meu projeto/i }), ).toHaveAttribute("id", "hero-contact-cta"); }); it("shows only the years of experience, with no repository numbers", () => { render(); expect(screen.getByText("anos de experiência")).toBeInTheDocument(); expect(screen.getByText("5")).toBeInTheDocument(); expect(screen.queryByRole("term")).toBeNull(); expect(screen.queryByText(/projetos desenvolvidos/i)).toBeNull(); expect(screen.queryByText(/públicos|privados|mais de/i)).toBeNull(); }); it("lists the delivered clients, each linking to its case", () => { render(); expect(screen.getByText("Já entregue para:")).toBeInTheDocument(); for (const { id, name } of clients) { expect(screen.getByRole("link", { name })).toHaveAttribute( "href", `/cases/${id}`, ); } }); it("renders the work column with lazy posters that stay out of small screens", () => { render(); const work = screen.getByTestId("hero-work"); expect(work).toHaveClass("hidden", "lg:block"); const images = work.querySelectorAll("img"); expect(images).toHaveLength(2); for (const image of images) { expect(image).toHaveAttribute("loading", "lazy"); } // pelo otimizador do next/image: o endereço original vai no parâmetro `url` const original = (image: Element) => new URL( image.getAttribute("src") ?? "", "http://localhost", ).searchParams.get("url"); expect(original(images[0])).toBe("/videos/lavacao-047/desktop.webp"); expect(images[0]).toHaveAccessibleName( "Site da Lavação 047 aberto no computador", ); expect(original(images[1])).toBe("/videos/as-servicos/mobile.webp"); }); it("shows a wide card to the SEV case below the buttons on small screens", () => { render(); const small = screen.getByTestId("hero-work-mobile"); expect(small).toHaveClass("lg:hidden"); expect(small).toHaveAttribute("href", "/cases/sev-modaintima"); expect(small).toHaveAccessibleName("Site da SEV Moda Íntima Ver case"); const buttons = screen.getByRole("link", { name: /ver projetos/i }); expect( buttons.compareDocumentPosition(small) & Node.DOCUMENT_POSITION_FOLLOWING, ).toBeTruthy(); }); it("keeps the label above the headline in the display font, so the mono font is not downloaded before the LCP", () => { render(); const label = screen.getByText( /Evandro Fonseca Junior · Engenharia de software/, ); expect(label.className).toContain("font-display"); expect(label.className).not.toContain("font-mono"); }); it("uses no mono font anywhere in the first fold (label, years, scroll cue)", () => { const { container } = render( , ); expect(container.querySelector(".font-mono")).toBeNull(); }); it("pre-fetches 'Ver projetos entregues', the clients and the SEV card only when reached (hover, focus or touch), never by viewport", () => { render(); const links = [ screen.getByRole("link", { name: /ver projetos/i }), screen.getByRole("link", { name: "SEV Moda Íntima" }), screen.getByTestId("hero-work-mobile"), ]; for (const link of links) { expect(link).toHaveAttribute("data-prefetch", "false"); fireEvent.mouseEnter(link); expect(link).toHaveAttribute("data-prefetch", "null"); } }); it("writes the range of each dive caption (--from and --to, from hero-captions.ts) on its own item, in order", () => { const { container } = render(); const items = Array.from( container.querySelectorAll(".hero-captions li"), ); expect(items).toHaveLength(HERO_CAPTIONS.length); items.forEach((item, index) => { const { title, text, from, to } = HERO_CAPTIONS[index]; expect(item).toHaveTextContent(`${title}${text}`); expect(item.style.getPropertyValue("--from")).toBe(String(from)); expect(item.style.getPropertyValue("--to")).toBe(String(to)); }); }); });