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
+173
-11
@@ -2,12 +2,16 @@ import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import portfolioData from "@/public/portfolio-data.json";
|
||||
import {
|
||||
buttonLabels,
|
||||
caseStudies,
|
||||
clientCases,
|
||||
clientCasesForListing,
|
||||
getRelatedCases,
|
||||
getRelatedServices,
|
||||
isClientCase,
|
||||
labCases,
|
||||
pageUpdatedAt,
|
||||
serviceOffers,
|
||||
servicePages,
|
||||
} from "./site-content";
|
||||
|
||||
@@ -89,17 +93,25 @@ it("mentions the region and a benefit in the title and description of every serv
|
||||
expect(new Set(descriptions).size).toBe(descriptions.length);
|
||||
});
|
||||
|
||||
it("maps each service to its client cases", () => {
|
||||
expect(getService("criacao-de-sites").relatedCaseIds).toEqual(clientIds);
|
||||
it("maps each service to its client cases, with the SEV only where it is the proof: the systems service", () => {
|
||||
expect(getService("criacao-de-sites").relatedCaseIds).toEqual([
|
||||
"lavacao-047",
|
||||
"sc-plus",
|
||||
"as-servicos",
|
||||
]);
|
||||
expect(getService("engenharia-de-software").relatedCaseIds).toEqual([
|
||||
"sev-modaintima",
|
||||
"lavacao-047",
|
||||
]);
|
||||
expect(getService("consultoria-nextjs").relatedCaseIds).toEqual([
|
||||
"sev-modaintima",
|
||||
"lavacao-047",
|
||||
"sc-plus",
|
||||
"lavacao-047",
|
||||
]);
|
||||
expect(
|
||||
servicePages
|
||||
.filter(({ relatedCaseIds }) => relatedCaseIds.includes("sev-modaintima"))
|
||||
.map(({ slug }) => slug),
|
||||
).toEqual(["engenharia-de-software"]);
|
||||
// nenhum case de cliente é um diagnóstico de sistema: o serviço fica só com a experiência na Apolo
|
||||
expect(getService("arquitetura-de-software").relatedCaseIds).toEqual([]);
|
||||
expect(getService("arquitetura-de-software").caseRelevance).toEqual({});
|
||||
@@ -112,16 +124,163 @@ it("maps each service to its client cases", () => {
|
||||
|
||||
it("says what the Next.js service cases really prove: sites built by me, not a diagnosis", () => {
|
||||
const { caseRelevance } = getService("consultoria-nextjs");
|
||||
for (const id of ["sev-modaintima", "lavacao-047"]) {
|
||||
expect(caseRelevance[id]).toMatch(/construí em Next\.js 16/);
|
||||
expect(caseRelevance[id]).not.toMatch(/diagn|conheço por dentro/i);
|
||||
expect(caseRelevance["lavacao-047"]).toMatch(/construí em Next\.js 16/);
|
||||
expect(caseRelevance["sc-plus"]).toMatch(/Next\.js 15/);
|
||||
for (const sentence of Object.values(caseRelevance)) {
|
||||
expect(sentence).not.toMatch(/diagn|conheço por dentro/i);
|
||||
}
|
||||
});
|
||||
|
||||
it("is honest that the desktop case is a Windows app made in Python, not in Tauri", () => {
|
||||
expect(getService("tauri-rust").caseRelevance["gcc-installer"]).toMatch(
|
||||
/Windows.*Python/,
|
||||
it("calls the desktop case a personal Windows app project, without claiming it is made in Tauri", () => {
|
||||
const sentence = getService("tauri-rust").caseRelevance["gcc-installer"];
|
||||
expect(sentence).toMatch(/projeto próprio de aplicativo para Windows/);
|
||||
expect(sentence).not.toMatch(/Tauri|Python/);
|
||||
});
|
||||
|
||||
it("opens the two offer services with the name of the offer as the h1, keeping the search title", () => {
|
||||
expect(getService("criacao-de-sites").headline).toBe(serviceOffers[0].name);
|
||||
expect(getService("criacao-de-sites").headline).toBe(
|
||||
"Site que traz pedidos pelo WhatsApp",
|
||||
);
|
||||
expect(getService("engenharia-de-software").headline).toBe(
|
||||
serviceOffers[1].name,
|
||||
);
|
||||
expect(getService("engenharia-de-software").headline).toBe(
|
||||
"Sistema para tirar a operação da planilha",
|
||||
);
|
||||
expect(getService("criacao-de-sites").metaTitle).toBe(
|
||||
"Criação de sites em Joinville e SC",
|
||||
);
|
||||
expect(getService("engenharia-de-software").metaTitle).toBe(
|
||||
"Sistemas web e aplicativos sob medida",
|
||||
);
|
||||
});
|
||||
|
||||
it("picks the cover by subject: sites -> Lavação 047, Next.js -> SC Plus, systems -> SEV on the phone, nothing for the rest", () => {
|
||||
expect(getService("criacao-de-sites").heroCase).toEqual({
|
||||
id: "lavacao-047",
|
||||
device: "desktop",
|
||||
});
|
||||
expect(getService("consultoria-nextjs").heroCase).toEqual({
|
||||
id: "sc-plus",
|
||||
device: "desktop",
|
||||
});
|
||||
expect(getService("engenharia-de-software").heroCase).toEqual({
|
||||
id: "sev-modaintima",
|
||||
device: "mobile",
|
||||
});
|
||||
for (const slug of [
|
||||
"tauri-rust",
|
||||
"integracao-hardware-software-iot",
|
||||
"arquitetura-de-software",
|
||||
]) {
|
||||
expect(getService(slug).heroCase).toBeUndefined();
|
||||
}
|
||||
// a capa é sempre um dos cases relacionados, e nenhuma capa se repete entre os serviços
|
||||
const heroIds = servicePages.flatMap(({ heroCase }) =>
|
||||
heroCase ? [heroCase.id] : [],
|
||||
);
|
||||
expect(new Set(heroIds).size).toBe(heroIds.length);
|
||||
for (const { heroCase, relatedCaseIds } of servicePages) {
|
||||
if (heroCase) expect(relatedCaseIds).toContain(heroCase.id);
|
||||
}
|
||||
});
|
||||
|
||||
it("gives each service without a client cover a flow diagram instead, and never both", () => {
|
||||
expect(getService("tauri-rust").diagram).toBe("desktop");
|
||||
expect(getService("integracao-hardware-software-iot").diagram).toBe("iot");
|
||||
expect(getService("arquitetura-de-software").diagram).toBe("arquitetura");
|
||||
for (const { heroCase, diagram } of servicePages) {
|
||||
expect(Boolean(heroCase) !== Boolean(diagram)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("lists the client cases for the mosaic and /cases with the SEV last, never first", () => {
|
||||
expect(clientCasesForListing.map(({ id }) => id)).toEqual([
|
||||
"lavacao-047",
|
||||
"sc-plus",
|
||||
"as-servicos",
|
||||
"sev-modaintima",
|
||||
]);
|
||||
expect(clientCasesForListing).toHaveLength(clientCases.length);
|
||||
});
|
||||
|
||||
it("keeps one list of button labels: 'Falar sobre meu projeto' for contact and 'Ver case' for a case", () => {
|
||||
expect(buttonLabels.contact).toBe("Falar sobre meu projeto");
|
||||
expect(buttonLabels.case).toBe("Ver case");
|
||||
expect(Object.values(buttonLabels).join("|")).not.toMatch(
|
||||
/Ver o case|Ler case|Conversar|oportunidade/,
|
||||
);
|
||||
});
|
||||
|
||||
it("reduces 'direto' and 'diretamente' in the case and service text", () => {
|
||||
const text = JSON.stringify({ caseStudies, servicePages });
|
||||
expect(text.match(/diret[oa]s?|diretamente/gi) ?? []).toEqual([]);
|
||||
});
|
||||
|
||||
it("explains the three offers of the home with the same names, 3 or 4 deliverables and a place to see them", () => {
|
||||
expect(serviceOffers.map(({ name }) => name)).toEqual([
|
||||
"Site que traz pedidos pelo WhatsApp",
|
||||
"Sistema para tirar a operação da planilha",
|
||||
"Revisão de sistema que já existe, inclusive feito com IA",
|
||||
]);
|
||||
expect(serviceOffers.map(({ href }) => href)).toEqual([
|
||||
"/servicos/criacao-de-sites",
|
||||
"/servicos/engenharia-de-software",
|
||||
"/ia",
|
||||
]);
|
||||
for (const offer of serviceOffers) {
|
||||
expect(offer.deliverables.length).toBeGreaterThanOrEqual(3);
|
||||
expect(offer.deliverables.length).toBeLessThanOrEqual(4);
|
||||
expect(
|
||||
offer.proof.caseIds.every((id) => caseStudies.some((c) => c.id === id)),
|
||||
).toBe(true);
|
||||
// sem número, preço, prazo ou depoimento inventados
|
||||
const text = [
|
||||
offer.name,
|
||||
offer.summary,
|
||||
...offer.deliverables,
|
||||
offer.proof.note ?? "",
|
||||
].join(" ");
|
||||
expect(text).not.toMatch(/R\$|\d+\s?%|\d+\s?dias|["“”]/);
|
||||
}
|
||||
expect(serviceOffers[0].proof.caseIds).toEqual([
|
||||
"lavacao-047",
|
||||
"sev-modaintima",
|
||||
]);
|
||||
expect(serviceOffers[1].proof.caseIds).toEqual(["sev-modaintima"]);
|
||||
expect(serviceOffers[1].proof.note).toMatch(/Apolo Energia/);
|
||||
expect(serviceOffers[1].proof.note).toMatch(/uso interno/);
|
||||
expect(serviceOffers[2].proof.caseIds).toEqual([]);
|
||||
});
|
||||
|
||||
it("writes 2 or 3 capabilities for every client case, without numbers, and none for the laboratory", () => {
|
||||
for (const caseStudy of clientCases) {
|
||||
expect(caseStudy.capabilities?.length).toBeGreaterThanOrEqual(2);
|
||||
expect(caseStudy.capabilities?.length).toBeLessThanOrEqual(3);
|
||||
expect(JSON.stringify(caseStudy.capabilities)).not.toMatch(
|
||||
/\d+\s?%|R\$|\d+\s?(clientes|pedidos|dias|vezes)/,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
caseStudies.find(({ id }) => id === "lavacao-047")?.capabilities?.[0],
|
||||
).toBe(
|
||||
"O dono muda preços, textos, fotos e o mapa sem pedir a ninguém, e a mudança aparece no site sem nova publicação.",
|
||||
);
|
||||
for (const caseStudy of labCases) {
|
||||
expect(caseStudy.capabilities).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("dates every service, case and fixed page with a plain calendar day for the sitemap", () => {
|
||||
const days = [
|
||||
...servicePages.map(({ updatedAt }) => updatedAt),
|
||||
...caseStudies.map(({ updatedAt }) => updatedAt),
|
||||
...Object.values(pageUpdatedAt),
|
||||
];
|
||||
for (const day of days) expect(day).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
||||
// a política de privacidade informa a própria vigência: a data do mapa é a dela
|
||||
expect(pageUpdatedAt.privacidade).toBe("2026-09-13");
|
||||
});
|
||||
|
||||
it("places the services in Joinville and SC instead of only 'em Garuva'", () => {
|
||||
@@ -164,8 +323,11 @@ it("derives the exact related service set from the existing relationship", () =>
|
||||
expect(getRelatedServices("twins-lab").map(({ slug }) => slug)).toEqual([
|
||||
"integracao-hardware-software-iot",
|
||||
]);
|
||||
expect(getRelatedServices("sev-modaintima").map(({ slug }) => slug)).toEqual([
|
||||
"engenharia-de-software",
|
||||
]);
|
||||
expect(
|
||||
getRelatedServices("sev-modaintima")
|
||||
getRelatedServices("lavacao-047")
|
||||
.map(({ slug }) => slug)
|
||||
.sort(),
|
||||
).toEqual(
|
||||
|
||||
Reference in new issue
Block a user