import { render, screen, within } from "@testing-library/react"; import { primaryButton, secondaryButtonLight } from "./button-styles"; import { WorkModalities } from "./WorkModalities"; /** * Testes de caminhos-base de WorkModalities e TierCard: a entrada dos planos (Reveal, CSS), o toque e o passe do * mouse (classes cv-press e cv-lift, sem foco nos cartões), plano em destaque (botão e borda) x demais planos, * primeiro passo de cada plano, e serviços do hub (só os três principais). * Nada de animação em JavaScript: o movimento reduzido é tratado pelo CSS (globals.css e conversion.css). */ jest.mock("./Section", () => ({ Section: ({ children, id }: { children?: React.ReactNode; id?: string }) => (
{children}
), })); jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null })); /** O hub inteiro, fora da ordem da home: a home mostra só sites, sistemas web e integrações, nesta ordem. */ const serviceLinks = [ { href: "/servicos/tauri-rust", label: "Aplicativos de desktop", description: "Programas rápidos para o computador.", }, { href: "/servicos/integracao-hardware-software-iot", label: "Integração de equipamentos e sistemas", description: "Máquinas e sistemas trocando dados.", }, { href: "/servicos/consultoria-nextjs", label: "Melhorias em sites e sistemas", description: "Evolução do que já existe.", }, { href: "/servicos/criacao-de-sites", label: "Sites profissionais", description: "Presença digital para a empresa.", }, { href: "/servicos/engenharia-de-software", label: "Sistemas web e aplicativos mobile", description: "Sistemas sob medida.", }, { href: "/servicos/arquitetura-de-software", label: "Diagnóstico e arquitetura", description: "Avaliação técnica.", }, ]; describe("WorkModalities: movimento", () => { it("path: tiers enter through Reveal -> same rise, same duration and a growing delay, no JavaScript animation props", () => { const { container } = render(); const reveals = [...container.querySelectorAll(".ds-reveal")]; expect(reveals).toHaveLength(3); expect( reveals.map((element) => element.style.getPropertyValue("--reveal-y")), ).toEqual(["20px", "20px", "20px"]); expect( reveals.map((element) => element.style.getPropertyValue("--reveal-duration"), ), ).toEqual(["0.75s", "0.75s", "0.75s"]); expect( reveals.map((element) => Number.parseFloat(element.style.getPropertyValue("--reveal-delay")), ), ).toEqual([0.08, 0.16, 0.24]); // o estado escondido vive no CSS: sem opacidade nem transformação inline for (const element of reveals) { expect(element.style.opacity).toBe(""); expect(element.style.transform).toBe(""); } }); it("path: the cards are not focus stops and keep no outline-none (touch and hover feedback come from CSS)", () => { const { container } = render(); const cards = container.querySelectorAll("article"); expect(cards).toHaveLength(3); for (const card of cards) { expect(card).not.toHaveAttribute("tabindex"); expect(card.className).not.toContain("outline-none"); expect(card).toHaveClass("cv-press", "cv-lift"); } }); it("path: each tier sits alone in its own reveal wrapper, which can shrink in the grid", () => { const { container } = render(); for (const card of container.querySelectorAll("article")) { const wrapper = card.closest(".ds-reveal"); expect(wrapper).toHaveClass("min-w-0"); expect(wrapper?.querySelectorAll("article")).toHaveLength(1); } }); }); describe("WorkModalities: plano em destaque", () => { it("path: the featured tier has the filled button and the thick border; the others have outline buttons", () => { const { container } = render(); const featured = screen.getByRole("link", { name: "Conversar sobre meu sistema", }); expect(featured.className).toContain(primaryButton); expect(featured.closest("article")).toHaveStyle({ borderWidth: "2px" }); expect(featured.closest("article")).toHaveClass("border-[var(--slash)]"); for (const name of ["Avaliar meu projeto", "Planejar a próxima etapa"]) { const other = screen.getByRole("link", { name }); expect(other.className).toContain(secondaryButtonLight); expect(other.className).not.toContain(primaryButton); expect(other.closest("article")).toHaveStyle({ borderWidth: "1px" }); expect(other.closest("article")).not.toHaveClass("border-[var(--slash)]"); } expect(container.querySelectorAll("article")).toHaveLength(3); }); }); describe("WorkModalities: serviços do hub", () => { it("path: no serviceLinks prop -> no service cards, the 'all services' link stays", () => { render(); expect( screen.getByRole("link", { name: "Ver todos os serviços" }), ).toHaveAttribute("href", "/servicos"); expect( screen.queryByRole("link", { name: /Sites profissionais/ }), ).toBeNull(); }); it("path: serviceLinks given -> only the three main services (sites, sistemas web, integrações), in that order", () => { const { container } = render( , ); const grid = container.querySelector( "[data-reel-stagger='left']", ) as HTMLElement; const links = within(grid).getAllByRole("link"); expect(links.map((link) => link.getAttribute("href"))).toEqual([ "/servicos/criacao-de-sites", "/servicos/engenharia-de-software", "/servicos/integracao-hardware-software-iot", ]); expect( within(links[0]).getByText("Sites profissionais"), ).toBeInTheDocument(); expect( within(links[1]).getByText("Sistemas sob medida."), ).toBeInTheDocument(); expect( screen.getByRole("link", { name: "Ver todos os serviços" }), ).toHaveAttribute("href", "/servicos"); }); it("path: a main service missing from the hub -> it is skipped, the others stay", () => { const { container } = render( !href.endsWith("/engenharia-de-software"), )} />, ); const grid = container.querySelector( "[data-reel-stagger='left']", ) as HTMLElement; expect( within(grid) .getAllByRole("link") .map((link) => link.getAttribute("href")), ).toEqual([ "/servicos/criacao-de-sites", "/servicos/integracao-hardware-software-iot", ]); }); }); describe("WorkModalities: primeiro passo", () => { it("path: each tier states a concrete first step, with no price, deadline or contract promise", () => { const { container } = render(); const articles = Array.from(container.querySelectorAll("article")); expect(articles).toHaveLength(3); for (const article of articles) { expect(within(article).getByText("Primeiro passo")).toBeInTheDocument(); } expect( screen.getAllByText( /Primeira conversa para entender o problema\. Depois, escopo e proposta por escrito\./, ), ).toHaveLength(1); expect( screen.getByText(/Você me mostra o que já existe\./), ).toBeInTheDocument(); for (const gone of [ "Proposta por projeto", "Escopo de análise definido", "Etapas acordadas", ]) { expect(screen.queryByText(gone)).toBeNull(); } expect(container.textContent).not.toMatch(/R\$|em contrato|prazo de \d/); }); });