import { fireEvent, render, screen, within } from "@testing-library/react"; import About from "./About"; // 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} ), })); /** * Testes de caminhos-base de About: a entrada dos quatro cartões (Reveal, CSS) com atrasos crescentes, o * feedback de toque e de mouse por classes (cv-press, cv-lift; cartões sem foco), o conteúdo e a coluna de * apoio. Nada de animação em JavaScript: o movimento reduzido é tratado pelo CSS. */ jest.mock("@/public/edev.jpg", () => ({ __esModule: true, default: { src: "/edev.jpg", width: 10, height: 10 }, })); jest.mock("./Section", () => ({ Section: ({ children, id, title, description, aside, }: { children?: React.ReactNode; id?: string; title?: string; description?: string; aside?: React.ReactNode; }) => (

{title}

{description}

{aside} {children}
), })); jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null })); jest.mock("./animations/LazyRevealImage", () => ({ LazyRevealImage: ({ alt }: { alt: string }) => ( ), })); describe("About", () => { it("path: the four cards enter through Reveal -> same rise and duration, delays growing in steps", () => { const { container } = render(); const reveals = [...container.querySelectorAll(".ds-reveal")]; // 3 etapas da história + o card do perfil (o cabeçalho da seção é um dublê, sem Reveal) expect(reveals).toHaveLength(4); expect( reveals.map((element) => element.style.getPropertyValue("--reveal-y")), ).toEqual(["20px", "20px", "20px", "20px"]); expect( reveals.map((element) => element.style.getPropertyValue("--reveal-duration"), ), ).toEqual(["0.8s", "0.8s", "0.8s", "0.8s"]); const delays = reveals.map((element) => Number.parseFloat(element.style.getPropertyValue("--reveal-delay")), ); expect(delays).toEqual([0.08, 0.16, 0.24, 0.32]); for (const element of reveals) { expect(element.style.opacity).toBe(""); expect(element.style.transform).toBe(""); } }); it("path: the four cards are not focus stops and use the CSS press feedback", () => { const { container } = render(); const cards = container.querySelectorAll("article"); expect(cards).toHaveLength(4); for (const card of cards) { expect(card).not.toHaveAttribute("tabindex"); expect(card).toHaveClass("cv-press"); } }); it("path: the three story cards lift on hover and press with CSS; the profile card presses only", () => { const { container } = render(); const cards = [...container.querySelectorAll("article")]; expect(cards).toHaveLength(4); for (const card of cards.slice(0, 3)) { expect(card).toHaveClass("cv-press", "cv-lift"); } expect(cards[3]).toHaveClass("cv-press"); }); it("path: the section has three points, each with the proof that backs it, and the résumé link", () => { const { container } = render(); const titles = [ "Quem conversa com você escreve o código", "Aproveito o que você já tem", "Do código ao sistema no ar", ]; for (const name of titles) { expect( screen.getByRole("heading", { level: 3, name }), ).toBeInTheDocument(); } // no máximo três pontos: os três cartões da história; o cartão do perfil não é um quarto ponto, só diz do que cuido const story = [...container.querySelectorAll("article")].slice(0, 3); expect(story).toHaveLength(3); for (const card of story) { expect(within(card).getAllByText("Na prática:")).toHaveLength(1); } // a prova de cada ponto é concreta: a Apolo desde 2022, a revisão e os cases entregues expect(story[0]).toHaveTextContent( /Na prática: Na Apolo Energia, desde 2022, desenvolvo aplicações web e mobile, APIs e integrações com dispositivos IoT/, ); expect(story[1]).toHaveTextContent( /Na prática: Na revisão, você recebe o que dá para aproveitar e o que é melhor refazer/, ); expect(story[2]).toHaveTextContent( /Na prática: Já entregue para SEV Moda Íntima, Lavação 047, SC Plus Aquicultura e AS Serviços\./, ); expect( within(story[1]).getByRole("link", { name: "Ver como funciona a revisão", }), ).toHaveAttribute("href", "/ia"); expect( screen.getByRole("link", { name: "Ver trajetória completa" }), ).toHaveAttribute("href", "/curriculo"); }); it("path: the idea of talking to whoever develops shows up once, not in five texts", () => { const { container } = render(); expect(container.textContent).not.toMatch( /Sem intermediários|Uma pessoa cuida|Quem responde pelo projeto|acompanha as decisões|fala com quem desenvolve|Solução do tamanho|Tudo funcionando junto/, ); expect(container.textContent?.match(/mesma pessoa/g)).toHaveLength(1); }); it("path: the résumé and the review links pre-fetch only when the visitor reaches them (HoverPrefetchLink), as in the rest of the home", () => { render(); for (const name of [ "Ver trajetória completa", "Ver como funciona a revisão", ]) { const link = screen.getByRole("link", { name }); expect(link).toHaveAttribute("data-prefetch", "false"); fireEvent.mouseEnter(link); expect(link).toHaveAttribute("data-prefetch", "null"); } }); it("path: speaks in client benefits, with no technical jargon, and cites the Apolo Energia work once, as the proof of the first point", () => { const { container } = render(); expect(container.textContent).not.toMatch( /protocolos de baixo nível|operabilidade|camada física|sinal físico/i, ); // a seção abre pela credencial, logo depois do nome expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent( "Evandro Fonseca Junior", ); expect(screen.getByTestId("about-description").textContent).toBe( "Engenheiro da computação e desenvolvedor de software, com projetos entregues para empresas.", ); // só o que o currículo sustenta: nada de "os sistemas internos e a IoT" da empresa inteira expect(container.textContent).not.toMatch( /os sistemas internos e a IoT da Apolo/, ); // a frase da Apolo aparece uma só vez (na prova do primeiro ponto) expect(container.textContent?.match(/Apolo Energia/g)).toHaveLength(1); }); it("path: the first story card does not reuse the name 'Como eu trabalho' of the process list in the offers section right before", () => { const { container } = render(); expect(container.textContent).not.toMatch(/Como eu trabalho/); expect(screen.getByText("Quem desenvolve")).toBeInTheDocument(); }); it("path: 'contato direto', 'fala direto' and 'conversa comigo' no longer repeat in the section", () => { const { container } = render(); expect(container.textContent).not.toMatch( /contato direto|fala direto|falar direto|conversa comigo/i, ); }); it("path: the location label stays on one line and the portrait card keeps its own height next to the text cards", () => { const { container } = render(); expect(screen.getByText(/Garuva, SC · Atendimento remoto/)).toHaveClass( "whitespace-nowrap", ); // items-start: o retrato tem a altura do próprio conteúdo e acompanha a rolagem; só os cartões de texto se esticam entre si expect(container.querySelector(".ds-grid-2")).toHaveClass("items-start"); const story = container.querySelectorAll("article"); for (const card of Array.from(story).slice(0, 3)) { expect(card).toHaveClass("flex-1"); } }); });