Files
portifolio-evandrodevbr/app/components/About.paths.test.tsx

221 lines
8.1 KiB
TypeScript

import { fireEvent, render, screen, within } from "@testing-library/react";
import About from "./About";
// o Link do Next, trocado por um <a> 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
>) => (
<a data-prefetch={String(prefetch)} {...props}>
{children}
</a>
),
}));
/**
* 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;
}) => (
<section id={id}>
<h2>{title}</h2>
<p data-testid="about-description">{description}</p>
{aside}
{children}
</section>
),
}));
jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null }));
jest.mock("./animations/LazyRevealImage", () => ({
LazyRevealImage: ({ alt }: { alt: string }) => (
<span role="img" aria-label={alt} />
),
}));
describe("About", () => {
it("path: the four cards enter through Reveal -> same rise and duration, delays growing in steps", () => {
const { container } = render(<About />);
const reveals = [...container.querySelectorAll<HTMLElement>(".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(<About />);
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(<About />);
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(<About />);
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(<About />);
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(<About />);
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(<About />);
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(<About />);
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(<About />);
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(<About />);
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");
}
});
});