Files
portifolio-evandrodevbr/app/components/About.paths.test.tsx
T
Evandro Fonseca Junior 8d07b8c6bb feat(home): prova no topo, contato sem atrito e conversão medida
Home mais curta (hero, vitrine de clientes, sobre, serviços, IA resumida, depoimentos e contato), faixa de clientes e telas reais no hero, sem números do GitHub. Barra de cookies fina, WhatsApp fixo no celular, formulário com e-mail opcional e alternativa por e-mail, página /ia, eventos de e-mail, redes e currículo e conversão do Ads opcional por variável. Rodapé em 3 colunas. Remove componentes órfãos (LighthouseMetrics, Skills, Experience, contagem do GitHub).
2026-10-14 03:32:23 -03:00

159 lines
4.3 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
import About from "./About";
/**
* Testes de caminhos-base de About: todas as decisões dependem de `shouldReduceMotion`
* (variantes dos cards, transição, hover e toque), então há dois caminhos: movimento normal e reduzido.
* O `motion/react` é trocado por elementos simples que registram as props de animação.
*/
const mockSeen = jest.fn();
jest.mock("motion/react", () => {
const React = require("react");
const cache = new Map<string, unknown>();
const motion = new Proxy(
{},
{
get: (_target, tag: string) => {
if (!cache.has(tag)) {
cache.set(
tag,
({
children,
variants,
whileHover,
whileTap,
transition,
initial: _initial,
animate: _animate,
whileInView: _whileInView,
viewport: _viewport,
...props
}: Record<string, unknown> & { children?: unknown }) => {
mockSeen(tag, { variants, whileHover, whileTap, transition });
return React.createElement(tag, props, children);
},
);
}
return cache.get(tag);
},
},
);
return { motion };
});
jest.mock("@/app/hooks/useReducedMotion", () => ({
useReducedMotion: jest.fn(),
}));
jest.mock("@/public/edev.jpg", () => ({
__esModule: true,
default: { src: "/edev.jpg", width: 10, height: 10 },
}));
jest.mock("./Section", () => ({
Section: ({
children,
id,
title,
}: {
children?: React.ReactNode;
id?: string;
title?: string;
}) => (
<section id={id}>
<h2>{title}</h2>
{children}
</section>
),
}));
jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null }));
jest.mock("./animations/LazyRevealImage", () => ({
LazyRevealImage: ({ alt }: { alt: string }) => (
<span role="img" aria-label={alt} />
),
}));
type Seen = {
variants?: {
hidden: Record<string, unknown>;
visible: { transition: Record<string, unknown> };
};
whileHover?: unknown;
whileTap?: unknown;
};
function renderedCards(): Seen[] {
return mockSeen.mock.calls
.filter(([tag]) => tag === "article")
.map(([, seen]) => seen as Seen);
}
beforeEach(() => {
mockSeen.mockClear();
});
describe("About", () => {
it("path: normal motion -> cards rise and clip in, with hover lift and press feedback", () => {
jest.mocked(useReducedMotion).mockReturnValue(false);
render(<About />);
const cards = renderedCards();
expect(cards).toHaveLength(4); // 3 etapas da história + o card do perfil
for (const card of cards) {
expect(card.variants?.hidden).toEqual({
opacity: 0,
y: 20,
clipPath: "inset(100% 0 0 0)",
});
expect(card.variants?.visible.transition).toEqual({
duration: 0.8,
ease: [0.16, 1, 0.3, 1],
});
expect(card.whileHover).toEqual({
y: -4,
boxShadow: "0 18px 40px rgba(10,10,10,0.16)",
});
expect(card.whileTap).toEqual({ scale: 0.99 });
}
});
it("path: reduced motion -> cards only fade, with no hover lift and no press feedback", () => {
jest.mocked(useReducedMotion).mockReturnValue(true);
render(<About />);
const cards = renderedCards();
expect(cards).toHaveLength(4);
for (const card of cards) {
expect(card.variants?.hidden).toEqual({ opacity: 0 });
expect(card.variants?.visible.transition).toEqual({ duration: 0.25 });
expect(card.whileHover).toBeUndefined();
expect(card.whileTap).toBeUndefined();
}
});
it("path: content does not depend on motion -> same story, profile points and résumé link", () => {
for (const reduced of [false, true]) {
jest.mocked(useReducedMotion).mockReturnValue(reduced);
const { unmount } = render(<About />);
expect(
screen.getByRole("heading", {
level: 3,
name: "Do sinal físico ao produto digital",
}),
).toBeInTheDocument();
expect(screen.getByText("Arquitetura pragmática")).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Ver trajetória completa" }),
).toHaveAttribute("href", "/curriculo");
unmount();
}
});
});