import { render, screen, within } from "@testing-library/react";
import AiPage, { metadata } from "./page";
it("uses a short search title: with the layout suffix it fits in 44 characters", () => {
// o template de app/layout.tsx acrescenta " | Evandro.dev.br"
const suffix = " | Evandro.dev.br";
expect(metadata.title).toBe("Revisão de app feito com IA");
expect(`${metadata.title}${suffix}`.length).toBeLessThanOrEqual(44);
expect(metadata).toMatchObject({
description: expect.any(String),
alternates: { canonical: "https://evandro.dev.br/ia" },
});
// as prévias de compartilhamento não passam pelo template: o título delas já leva o sufixo
expect(metadata.openGraph?.title).toBe(
`Revisão de app feito com IA${suffix}`,
);
});
it("keeps the full AI content, with the cited incidents summarized only here", () => {
render();
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
"Revisão de app feito com IA (Lovable, Bolt, Replit)",
);
for (const heading of [
"Se a IA cria um sistema, por que contratar um desenvolvedor?",
"O que casos documentados ensinam",
"E a LGPD?",
]) {
expect(screen.getByRole("heading", { name: heading })).toBeInTheDocument();
}
for (const incident of [/Lovable:/, /Replit:/, /Base44:/]) {
expect(screen.getByRole("heading", { name: incident })).toBeInTheDocument();
}
expect(
screen.getByRole("link", { name: "Reportagem da Semafor (em inglês)" }),
).toHaveAttribute("rel", "noopener noreferrer");
});
it("offers WhatsApp conversations about reviewing an AI-built system", () => {
render();
const links = screen
.getAllByRole("link")
.filter((link) => link.getAttribute("href")?.startsWith("https://wa.me/"));
expect(links.map((link) => link.textContent)).toEqual([
"Pedir a revisão do meu app",
"Avaliar meu projeto",
]);
for (const link of links) {
const text = new URL(link.getAttribute("href") ?? "").searchParams.get(
"text",
);
// a mensagem tem de formar uma frase: "Quero conversar sobre a revisão de um aplicativo feito com IA."
expect(text).toContain(
"Quero conversar sobre a revisão de um aplicativo feito com IA.",
);
}
});
it("has breadcrumb navigation back to the home", () => {
render();
const breadcrumb = screen.getByRole("navigation", {
name: "Trilha de navegação",
});
expect(
within(breadcrumb).getByRole("link", { name: "Início" }),
).toHaveAttribute("href", "/");
expect(
within(breadcrumb).getByText("Revisão de app feito com IA"),
).toHaveAttribute("aria-current", "page");
});
it("presents the review as an offer: what the person receives, with no price or deadline", () => {
const { container } = render();
const offer = screen.getByRole("heading", {
name: "O que você recebe na revisão",
});
const section = offer.closest("section") as HTMLElement;
const items = within(section)
.getAllByRole("listitem")
.map((item) => item.querySelector("h3")?.textContent);
expect(items).toEqual([
"As falhas de acesso a dados",
"O que dá para aproveitar",
"O que é melhor refazer",
"Um orçamento para pôr em produção",
]);
expect(container.textContent).not.toMatch(/R\$|\d+ dias|em até \d+/);
});
it("keeps the incident news collapsed in a , closed by default", () => {
const { container } = render();
const heading = screen.getByRole("heading", {
name: "O que casos documentados ensinam",
});
const details = heading.closest("details") as HTMLDetailsElement;
expect(details).not.toBeNull();
expect(details.open).toBe(false);
expect(heading.closest("summary")).not.toBeNull();
for (const incident of [/Lovable:/, /Replit:/, /Base44:/]) {
expect(within(details).getByText(incident)).toBeInTheDocument();
}
// só os incidentes ficam recolhidos: a oferta e a LGPD ficam à vista
expect(container.querySelectorAll("details")).toHaveLength(1);
expect(
screen
.getByRole("heading", { name: "O que você recebe na revisão" })
.closest("details"),
).toBeNull();
});