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).
This commit is contained in:
1 parent
6e1022b58c
commit
8d07b8c6bb
60 files changed
+4392
-2124
No files matched your search
@@ -1,47 +1,12 @@
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import {
|
||||
getFeaturedProjects,
|
||||
type PortfolioProjectView,
|
||||
} from "@/lib/portfolio-data";
|
||||
import { isClientProject } from "@/lib/proof";
|
||||
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<string, unknown>) =>
|
||||
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 }) => (
|
||||
<section>
|
||||
@@ -51,111 +16,69 @@ jest.mock("./Section", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
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",
|
||||
const providerProjects = jest.fn();
|
||||
|
||||
jest.mock("./showcase/ShowcaseProvider", () => ({
|
||||
ShowcaseProvider: ({
|
||||
children,
|
||||
projects,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
projects: PortfolioProjectView[];
|
||||
}) => {
|
||||
providerProjects(projects);
|
||||
return <div data-testid="showcase-provider">{children}</div>;
|
||||
},
|
||||
{
|
||||
...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,
|
||||
},
|
||||
];
|
||||
}));
|
||||
|
||||
jest.mock("./showcase/ProjectReel", () => ({
|
||||
ProjectReel: () => <div data-testid="project-reel" />,
|
||||
}));
|
||||
|
||||
// A página entrega só os projetos de cliente; aqui usamos os reais do portfólio, com uma demo indisponível de propósito
|
||||
const clientProjects: PortfolioProjectView[] = getFeaturedProjects(null)
|
||||
.filter(isClientProject)
|
||||
.map((project, index) => ({
|
||||
...project,
|
||||
demoAvailability: index === 0 ? ("unavailable" as const) : "available",
|
||||
}));
|
||||
|
||||
describe("Projects", () => {
|
||||
it("renders demo actions according to availability", () => {
|
||||
render(<Projects projects={projects} />);
|
||||
beforeEach(() => providerProjects.mockClear());
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Demo disponível" }),
|
||||
).toBeVisible();
|
||||
expect(screen.getByRole("link", { name: "Abrir projeto" })).toHaveAttribute(
|
||||
"href",
|
||||
"https://available.example",
|
||||
);
|
||||
it("presents the client projects once, through the reel only", () => {
|
||||
const { container } = render(<Projects projects={clientProjects} />);
|
||||
|
||||
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();
|
||||
expect(clientProjects.length).toBeGreaterThan(0);
|
||||
expect(screen.getByTestId("project-reel")).toBeInTheDocument();
|
||||
expect(providerProjects).toHaveBeenCalledWith(clientProjects);
|
||||
// sem grade de cards: nem artigos, nem capas provisórias
|
||||
expect(container.querySelector("article")).toBeNull();
|
||||
expect(container.querySelector(".ds-cover, .ds-cover-fallback")).toBeNull();
|
||||
expect(screen.queryByRole("heading", { level: 3 })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps project evidence visible when the demo is unavailable", () => {
|
||||
render(<Projects projects={projects} />);
|
||||
it("drops the availability badge, the code link and every GitHub link", () => {
|
||||
const { container } = render(<Projects projects={clientProjects} />);
|
||||
|
||||
const card = screen
|
||||
.getByRole("heading", { name: "Demo indisponível" })
|
||||
.closest("article");
|
||||
expect(card).not.toBeNull();
|
||||
const unavailableCard = within(card as HTMLElement);
|
||||
expect(
|
||||
screen.queryByText(/Demo indisponível|indisponível na última/i),
|
||||
).toBeNull();
|
||||
expect(screen.queryByText(/Ver codigo|Ver código/i)).toBeNull();
|
||||
expect(screen.queryByText(/Abrir projeto/i)).toBeNull();
|
||||
for (const link of container.querySelectorAll("a")) {
|
||||
expect(link.getAttribute("href") ?? "").not.toMatch(/github\.com/);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
it("ends with a secondary link to all the cases", () => {
|
||||
render(<Projects projects={clientProjects} />);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Ver todos os cases" });
|
||||
expect(link).toHaveAttribute("href", "/cases");
|
||||
// secundário: contorno, sem o fundo verde-limão do contato
|
||||
expect(link.className).toContain("border");
|
||||
expect(link.className).not.toContain("bg-[var(--slash)]");
|
||||
expect(screen.getAllByRole("link")).toEqual([link]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user