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(); 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 & { 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; }) => (

{title}

{children}
), })); jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null })); jest.mock("./animations/LazyRevealImage", () => ({ LazyRevealImage: ({ alt }: { alt: string }) => ( ), })); type Seen = { variants?: { hidden: Record; visible: { transition: Record }; }; 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(); 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(); 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(); 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(); } }); });