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:
1 parent
e63f3c2ebd
commit
fc44a69fb3
41 files changed
+2486
-550
No files matched your search
@@ -0,0 +1,162 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { getCaseMedia } from "@/app/cases/_components/case-media";
|
||||
import { type Device, getShowcaseAssets } from "@/lib/project-showcase";
|
||||
import { getServicePage } from "@/lib/site-content";
|
||||
import ServicePage from "./page";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base da escolha da capa do topo em ServicePage (complemento de page.test.tsx), com as capturas
|
||||
* gravadas controladas pelo teste (getCaseMedia trocado por um dublê que só "tem" o que o teste diz).
|
||||
*
|
||||
* A capa vale quando o serviço tem `heroCase` E existe captura gravada do aparelho pedido (heroCase.device); senão
|
||||
* entra o `diagram` do serviço, se tiver; sem os dois, o topo vira uma coluna só e mais baixa.
|
||||
* Caminhos: capa de computador | capa de celular | sem captura do computador | só captura do outro aparelho |
|
||||
* case da capa que não existe | serviço de esquema, com ou sem captura de qualquer case.
|
||||
* Quando a capa vale, a legenda dela cita o case e o case não ganha cartão em "Trabalhos parecidos".
|
||||
*/
|
||||
|
||||
jest.mock("next/navigation", () => ({
|
||||
notFound: jest.fn(() => {
|
||||
throw new Error("NEXT_NOT_FOUND");
|
||||
}),
|
||||
usePathname: () => "/servicos",
|
||||
}));
|
||||
jest.mock("@/app/cases/_components/case-media", () => ({
|
||||
...jest.requireActual("@/app/cases/_components/case-media"),
|
||||
getCaseMedia: jest.fn(),
|
||||
}));
|
||||
|
||||
type Capture = readonly [id: string, device: Device];
|
||||
|
||||
/** Só as capturas listadas existem no disco. */
|
||||
function capturesOnDisk(captures: readonly Capture[]) {
|
||||
jest
|
||||
.mocked(getCaseMedia)
|
||||
.mockImplementation((id, device) =>
|
||||
captures.some(
|
||||
([knownId, knownDevice]) => knownId === id && knownDevice === device,
|
||||
)
|
||||
? getShowcaseAssets(id, device)
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
async function renderService(slug: string) {
|
||||
const ui = await ServicePage({ params: Promise.resolve({ slug }) });
|
||||
const view = render(ui);
|
||||
const hero = view.container.querySelector("section") as HTMLElement;
|
||||
return {
|
||||
...view,
|
||||
hero,
|
||||
twoColumns: hero
|
||||
.querySelector(".ds-section-shell")
|
||||
?.classList.contains("grid"),
|
||||
compact: hero.className.includes("md:!pb-sp-12"),
|
||||
caption: hero.querySelector("figure figcaption"),
|
||||
cards: Array.from(
|
||||
view.container.querySelectorAll("#casos-relacionados article"),
|
||||
).map((card) => card.querySelector(".ds-kicker")?.textContent),
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
jest.mocked(getCaseMedia).mockReset();
|
||||
});
|
||||
|
||||
describe("ServicePage: escolha da capa do topo", () => {
|
||||
it("path: desktop cover with its capture on disk -> browser frame in the hero, the caption cites the case and the case gets no card", async () => {
|
||||
capturesOnDisk([["lavacao-047", "desktop"]]);
|
||||
|
||||
const view = await renderService("criacao-de-sites");
|
||||
|
||||
expect(view.hero.querySelector(".ds-bframe")).not.toBeNull();
|
||||
expect(view.twoColumns).toBe(true);
|
||||
expect(view.compact).toBe(false);
|
||||
expect(view.caption).toHaveTextContent("Lavação 047");
|
||||
expect(view.cards).not.toContain("Lavação 047");
|
||||
expect(view.cards).toEqual(["SC Plus Aquicultura", "AS Serviços"]);
|
||||
});
|
||||
|
||||
it("path: mobile cover with its capture on disk -> phone frame in the hero, no browser frame", async () => {
|
||||
capturesOnDisk([["sev-modaintima", "mobile"]]);
|
||||
|
||||
const view = await renderService("engenharia-de-software");
|
||||
|
||||
expect(view.hero.querySelector(".ds-phone-screen")).not.toBeNull();
|
||||
expect(view.hero.querySelector(".ds-bframe")).toBeNull();
|
||||
expect(view.twoColumns).toBe(true);
|
||||
expect(view.caption).toHaveTextContent("SEV Moda Íntima");
|
||||
expect(view.cards).toEqual(["Lavação 047"]);
|
||||
});
|
||||
|
||||
it("path: no capture on disk -> no cover, a lower one-column hero, and the case that would be the cover gets its card back", async () => {
|
||||
capturesOnDisk([]);
|
||||
|
||||
const view = await renderService("criacao-de-sites");
|
||||
|
||||
expect(view.hero.querySelector("figure")).toBeNull();
|
||||
expect(view.twoColumns).toBe(false);
|
||||
expect(view.compact).toBe(true);
|
||||
expect(screen.queryByRole("img")).toBeNull();
|
||||
expect(view.cards).toEqual([
|
||||
"Lavação 047",
|
||||
"SC Plus Aquicultura",
|
||||
"AS Serviços",
|
||||
]);
|
||||
});
|
||||
|
||||
it("path: only the OTHER device was captured (mobile cover, desktop capture) -> no cover, the check follows the device the service asks for", async () => {
|
||||
capturesOnDisk([["sev-modaintima", "desktop"]]);
|
||||
|
||||
const view = await renderService("engenharia-de-software");
|
||||
|
||||
expect(view.hero.querySelector("figure")).toBeNull();
|
||||
expect(view.compact).toBe(true);
|
||||
expect(view.cards).toEqual(["SEV Moda Íntima", "Lavação 047"]);
|
||||
expect(getCaseMedia).toHaveBeenCalledWith("sev-modaintima", "mobile");
|
||||
});
|
||||
|
||||
it("path: the cover case does not exist -> same as no capture, no crash", async () => {
|
||||
capturesOnDisk([["nao-existe", "desktop"]]);
|
||||
const service = getServicePage("consultoria-nextjs");
|
||||
if (!service?.heroCase) throw new Error("Missing hero case");
|
||||
const original = service.heroCase;
|
||||
service.heroCase = { id: "nao-existe", device: "desktop" };
|
||||
try {
|
||||
const view = await renderService("consultoria-nextjs");
|
||||
|
||||
expect(view.hero.querySelector("figure")).toBeNull();
|
||||
expect(view.compact).toBe(true);
|
||||
} finally {
|
||||
service.heroCase = original;
|
||||
}
|
||||
});
|
||||
|
||||
it("path: diagram service -> the diagram, never a cover, even with every capture on disk", async () => {
|
||||
capturesOnDisk([
|
||||
["twins-lab", "desktop"],
|
||||
["twins-lab", "mobile"],
|
||||
["lavacao-047", "desktop"],
|
||||
]);
|
||||
|
||||
const view = await renderService("integracao-hardware-software-iot");
|
||||
|
||||
expect(
|
||||
screen.getByRole("img", { name: /dispositivo envia as leituras/ }),
|
||||
).toBeVisible();
|
||||
expect(view.hero.querySelector(".ds-bframe")).toBeNull();
|
||||
expect(view.twoColumns).toBe(true);
|
||||
expect(view.compact).toBe(false);
|
||||
});
|
||||
|
||||
it("path: a service with a cover AND the capture missing never falls back to a diagram it does not have", async () => {
|
||||
capturesOnDisk([]);
|
||||
const service = getServicePage("consultoria-nextjs");
|
||||
|
||||
const view = await renderService("consultoria-nextjs");
|
||||
|
||||
expect(service?.diagram).toBeUndefined();
|
||||
expect(screen.queryByRole("img", { name: /Esquema|esquema/ })).toBeNull();
|
||||
expect(view.compact).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user