feat(cases): mídia no topo, títulos de busca curtos e CreativeWork

Case com mídia ao lado do título e play acessível; problema de cada cliente reescrito com base nos dados; serviços relacionados em 2 cards; FAQ antes do próximo passo e bloco 'Onde já apliquei isso'; Apolo citada em IoT e arquitetura. metaTitle até 43 caracteres e descrições até 155 com teste; JSON-LD CreativeWork; imagens de compartilhamento abaixo de 100 KB; 404 e robots corrigidos; server-only nos módulos com node:.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 14:15:25 -03:00
1 parent c9a0b80656
commit e80f73d075
52 files changed
+2593 -439

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
import { render, screen } from "@testing-library/react";
import { Breadcrumb } from "./Breadcrumb";
/**
* Testes de caminhos-base de Breadcrumb: o primeiro item não tem separador,
* os seguintes têm, e só o último é a página atual (inclusive quando é o único).
*/
describe("Breadcrumb", () => {
it("path: several items -> a separator before every item but the first", () => {
const { container } = render(
<Breadcrumb
items={[
{ label: "Início", href: "/" },
{ label: "Cases", href: "/cases" },
{ label: "SEV Moda Íntima", href: "/cases/sev-modaintima" },
]}
/>,
);
const entries = Array.from(container.querySelectorAll("li"));
expect(entries).toHaveLength(3);
expect(entries[0].querySelector("span[aria-hidden='true']")).toBeNull();
expect(
entries[1].querySelector("span[aria-hidden='true']"),
).toHaveTextContent("/");
expect(
entries[2].querySelector("span[aria-hidden='true']"),
).toHaveTextContent("/");
expect(entries[0].className).toBe("");
expect(entries[1].className).toContain("flex");
});
it("path: a single item -> no separator, and it is the current page", () => {
const { container } = render(
<Breadcrumb items={[{ label: "Início", href: "/" }]} />,
);
expect(container.querySelector("span[aria-hidden='true']")).toBeNull();
expect(screen.getByRole("link", { name: "Início" })).toHaveAttribute(
"aria-current",
"page",
);
});
it("path: no items -> an empty list inside the navigation", () => {
render(<Breadcrumb items={[]} />);
const nav = screen.getByRole("navigation");
expect(nav.querySelectorAll("li")).toHaveLength(0);
expect(screen.queryByRole("link")).toBeNull();
});
});
+31
View File
@@ -0,0 +1,31 @@
import { render, screen, within } from "@testing-library/react";
import { Breadcrumb } from "./Breadcrumb";
describe("Breadcrumb", () => {
const items = [
{ label: "Início", href: "/" },
{ label: "Cases", href: "/cases" },
{ label: "SEV Moda Íntima", href: "/cases/sev-modaintima" },
];
it("marks only the last item as the current page", () => {
render(<Breadcrumb items={items} />);
const links = within(
screen.getByRole("navigation", { name: "Trilha de navegação" }),
).getAllByRole("link");
expect(links.map((link) => link.getAttribute("aria-current"))).toEqual([
null,
null,
"page",
]);
});
it("keeps the text at 15 px or more, as the body text of the inner pages", () => {
const { container } = render(<Breadcrumb items={items} />);
const list = container.querySelector("ol");
expect(list).toHaveClass("text-[15px]");
expect(list?.className).not.toMatch(/text-(xs|sm)\b|text-\[1[0-4]px\]/);
});
});
+2 -2
View File
@@ -7,8 +7,8 @@ export function Breadcrumb({
items: readonly { label: string; href: string }[];
}) {
return (
<nav aria-label="Breadcrumb" className="mb-sp-6">
<ol className="flex flex-wrap items-center gap-sp-2 text-sm">
<nav aria-label="Trilha de navegação" className="mb-sp-6">
<ol className="flex flex-wrap items-center gap-sp-2 text-[15px]">
{items.map(({ label, href }, index) => (
<li
key={href}