import { render, screen, within } from "@testing-library/react"; import type { ReactNode } from "react"; import { getFeaturedProjects, type PortfolioProjectView, } from "@/lib/portfolio-data"; import Projects from "./Projects"; jest.mock("motion/react", () => { const React = require("react"); const motion = new Proxy( {}, { get: (_, tag: string) => { const Component = ({ children, animate: _animate, initial: _initial, transition: _transition, variants: _variants, viewport: _viewport, whileHover: _whileHover, whileInView: _whileInView, whileTap: _whileTap, ...props }: { children?: ReactNode } & Record) => React.createElement(tag, props, children); Component.displayName = `motion.${tag}`; return Component; }, }, ); return { motion }; }); jest.mock("@/app/hooks/useReducedMotion", () => ({ useReducedMotion: () => true, })); jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null, })); jest.mock("./Section", () => ({ Section: ({ children, title }: { children: ReactNode; title: string }) => (

{title}

{children}
), })); const [baseProject] = getFeaturedProjects(null); const projects: PortfolioProjectView[] = [ { ...baseProject, id: "available-demo", title: "Demo disponível", description: "Descrição disponível", technologies: ["Stack disponível"], status: "Disponível", demoUrl: "https://available.example", githubUrl: "https://github.com/example/available", demoAvailability: "available", demoCheckedAt: "2026-09-04T12:00:00.000Z", }, { ...baseProject, id: "unavailable-demo", title: "Demo indisponível", description: "Descrição indisponível", technologies: ["Stack indisponível"], status: "Indisponível", demoUrl: "https://unavailable.example", githubUrl: "https://github.com/example/unavailable", demoAvailability: "unavailable", demoCheckedAt: "2026-09-04T12:00:00.000Z", }, { ...baseProject, id: "unknown-demo", title: "Demo desconhecida", description: "Descrição desconhecida", technologies: ["Stack desconhecida"], status: "Não verificada", demoUrl: "https://unknown.example", githubUrl: "https://github.com/example/unknown", demoAvailability: "unknown", demoCheckedAt: null, }, { ...baseProject, id: "no-demo", title: "Projeto sem demo", description: "Descrição sem demo", technologies: ["Stack sem demo"], status: "Sem demo", demoUrl: null, githubUrl: "https://github.com/example/no-demo", demoAvailability: "none", demoCheckedAt: null, }, ]; describe("Projects", () => { it("renders demo actions according to availability", () => { render(); expect( screen.getByRole("heading", { name: "Demo disponível" }), ).toBeVisible(); expect(screen.getByRole("link", { name: "Abrir projeto" })).toHaveAttribute( "href", "https://available.example", ); const unavailable = screen.getByText( "Demo indisponível na última verificação", ); expect(unavailable).toHaveAttribute("aria-disabled", "true"); expect(unavailable).not.toHaveAttribute("href"); expect(unavailable.closest("a")).toBeNull(); expect( screen.getByRole("link", { name: /Abrir projeto.*Disponibilidade não verificada/i, }), ).toHaveAttribute("href", "https://unknown.example"); const noDemoCard = screen .getByRole("heading", { name: "Projeto sem demo" }) .closest("article"); expect(noDemoCard).not.toBeNull(); expect( within(noDemoCard as HTMLElement).queryByRole("link", { name: /Abrir projeto/i, }), ).not.toBeInTheDocument(); }); it("keeps project evidence visible when the demo is unavailable", () => { render(); const card = screen .getByRole("heading", { name: "Demo indisponível" }) .closest("article"); expect(card).not.toBeNull(); const unavailableCard = within(card as HTMLElement); expect(unavailableCard.getByText("Descrição indisponível")).toBeVisible(); expect(unavailableCard.getByText("Stack indisponível")).toBeVisible(); expect(unavailableCard.getByText("Indisponível")).toBeVisible(); expect( unavailableCard.getByRole("link", { name: "Ver codigo" }), ).toHaveAttribute("href", "https://github.com/example/unavailable"); expect( unavailableCard.queryByRole("link", { name: /Abrir projeto/i }), ).not.toBeInTheDocument(); }); });