feat(servicos): ofertas no topo de /servicos, capa por assunto e diagramas

/servicos abre com as 3 ofertas e depois outros serviços; H1 de criação de sites e de engenharia com o nome da oferta; capa de cada serviço pelo assunto (SEV fora do próprio case cai de ~11 para 2 imagens); diagrama de fluxo em SVG nos serviços sem case; cases com o que o cliente passou a poder fazer e contato logo depois de Como ficou; FAQ num componente só; lastmod por rota; ProfessionalService com areaServed.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 19:14:46 -03:00
1 parent e63f3c2ebd
commit fc44a69fb3
41 files changed
+2486 -550

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
import { render, screen, within } from "@testing-library/react";
import { Faq } from "./Faq";
import HomeFaq from "./HomeFaq";
const items = [
{ question: "Primeira pergunta?", answer: "Primeira resposta." },
{
question: "Segunda pergunta?",
answer: "Segunda resposta.",
link: { href: "/ia", label: "Ver a revisão" },
},
{ question: "Sem resposta ainda?", answer: " " },
];
describe("Faq", () => {
it("path: answered questions -> one closed accordion item per question, under a level 2 heading", () => {
const { container } = render(<Faq items={items} />);
expect(
screen.getByRole("heading", { level: 2, name: "Perguntas frequentes" }),
).toBeInTheDocument();
const summaries = Array.from(container.querySelectorAll("summary")).map(
(summary) => summary.textContent?.replace("+", "").trim(),
);
expect(summaries).toEqual(["Primeira pergunta?", "Segunda pergunta?"]);
for (const details of container.querySelectorAll("details")) {
expect(details.open).toBe(false);
}
});
it("path: a question with no answer -> stays out of the page, like on the home", () => {
render(<Faq items={items} />);
expect(screen.queryByText("Sem resposta ainda?")).toBeNull();
});
it("path: a question with a link -> the link sits inside its answer", () => {
render(<Faq items={items} />);
const details = screen
.getByText("Segunda pergunta?")
.closest("details") as HTMLElement;
expect(within(details).getByText("Segunda resposta.")).toBeInTheDocument();
expect(
within(details).getByRole("link", { name: "Ver a revisão" }),
).toHaveAttribute("href", "/ia");
});
it("path: custom title and anchor -> the heading and the section follow them", () => {
const { container } = render(
<Faq items={items} id="antes" title="Antes de contratar" />,
);
const heading = screen.getByRole("heading", { name: "Antes de contratar" });
expect(heading).toHaveAttribute("id", "antes-title");
expect(container.querySelector("section")).toHaveAttribute("id", "antes");
expect(container.querySelector("section")).toHaveAttribute(
"aria-labelledby",
"antes-title",
);
});
it("path: no items, or none with an answer -> renders nothing, not even the heading", () => {
const empty = render(<Faq />);
expect(empty.container).toBeEmptyDOMElement();
empty.unmount();
const unanswered = render(
<Faq items={[{ question: "Pergunta?", answer: "" }]} />,
);
expect(unanswered.container).toBeEmptyDOMElement();
});
it("path: same pattern as the home FAQ -> same summary and details classes", () => {
const shared = render(<Faq items={items} />);
const home = render(
<HomeFaq
items={[{ question: "Primeira pergunta?", answer: "Resposta." }]}
/>,
);
const classOf = (container: HTMLElement, selector: string) =>
container.querySelector(selector)?.className;
for (const selector of ["details", "summary", "summary > span"]) {
expect(classOf(shared.container, selector)).toBe(
classOf(home.container, selector),
);
}
});
});