feat(servicos): topo, entregáveis e cases por serviço, assunto próprio no WhatsApp e títulos curtos
This commit is contained in:
1 parent
cf360041bd
commit
2fd8b063d9
33 files changed
+1578
-300
No files matched your search
@@ -6,8 +6,10 @@ import {
|
||||
caseStudies,
|
||||
clientCases,
|
||||
clientCasesForListing,
|
||||
generalFirstStep,
|
||||
getRelatedCases,
|
||||
getRelatedServices,
|
||||
getServiceOffer,
|
||||
isClientCase,
|
||||
labCases,
|
||||
pageUpdatedAt,
|
||||
@@ -93,12 +95,18 @@ 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, with the SEV only where it is the proof: the systems service", () => {
|
||||
it("maps each service to its client cases, with the SEV as proof of both offers: the site and the system", () => {
|
||||
// a Lavação abre a página, a SEV é o segundo trabalho e os dois sites institucionais vão para o bloco menor
|
||||
expect(getService("criacao-de-sites").relatedCaseIds).toEqual([
|
||||
"lavacao-047",
|
||||
"sev-modaintima",
|
||||
"sc-plus",
|
||||
"as-servicos",
|
||||
]);
|
||||
expect(getService("criacao-de-sites").alsoCases).toEqual({
|
||||
title: "Também faço sites institucionais",
|
||||
caseIds: ["sc-plus", "as-servicos"],
|
||||
});
|
||||
expect(getService("engenharia-de-software").relatedCaseIds).toEqual([
|
||||
"sev-modaintima",
|
||||
"lavacao-047",
|
||||
@@ -111,7 +119,12 @@ it("maps each service to its client cases, with the SEV only where it is the pro
|
||||
servicePages
|
||||
.filter(({ relatedCaseIds }) => relatedCaseIds.includes("sev-modaintima"))
|
||||
.map(({ slug }) => slug),
|
||||
).toEqual(["engenharia-de-software"]);
|
||||
).toEqual(["criacao-de-sites", "engenharia-de-software"]);
|
||||
// o bloco menor só cita cases que o serviço já relaciona
|
||||
for (const { relatedCaseIds, alsoCases } of servicePages) {
|
||||
for (const id of alsoCases?.caseIds ?? [])
|
||||
expect(relatedCaseIds).toContain(id);
|
||||
}
|
||||
// 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({});
|
||||
@@ -195,12 +208,12 @@ it("gives each service without a client cover a flow diagram instead, and never
|
||||
}
|
||||
});
|
||||
|
||||
it("lists the client cases for the mosaic and /cases with the SEV last, never first", () => {
|
||||
it("lists the client cases for the mosaic and /cases in the order of the home showcase, the SEV first", () => {
|
||||
expect(clientCasesForListing.map(({ id }) => id)).toEqual([
|
||||
"sev-modaintima",
|
||||
"lavacao-047",
|
||||
"sc-plus",
|
||||
"as-servicos",
|
||||
"sev-modaintima",
|
||||
]);
|
||||
expect(clientCasesForListing).toHaveLength(clientCases.length);
|
||||
});
|
||||
@@ -222,7 +235,12 @@ it("explains the three offers of the home with the same names, 3 or 4 deliverabl
|
||||
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",
|
||||
"Revisão de sistema que já existe",
|
||||
]);
|
||||
expect(serviceOffers.map(({ whatsappSubject }) => whatsappSubject)).toEqual([
|
||||
"um site que traz pedidos pelo WhatsApp",
|
||||
"um sistema para tirar a operação da planilha",
|
||||
"a revisão de um sistema que já existe",
|
||||
]);
|
||||
expect(serviceOffers.map(({ href }) => href)).toEqual([
|
||||
"/servicos/criacao-de-sites",
|
||||
@@ -232,6 +250,13 @@ it("explains the three offers of the home with the same names, 3 or 4 deliverabl
|
||||
for (const offer of serviceOffers) {
|
||||
expect(offer.deliverables.length).toBeGreaterThanOrEqual(3);
|
||||
expect(offer.deliverables.length).toBeLessThanOrEqual(4);
|
||||
expect(offer.firstStep).toMatch(
|
||||
/^Primeira conversa .* proposta por escrito\.$/,
|
||||
);
|
||||
// a frase geral da home não contradiz a da oferta: as duas terminam igual
|
||||
expect(offer.firstStep.endsWith(generalFirstStep.split(". ")[1])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
offer.proof.caseIds.every((id) => caseStudies.some((c) => c.id === id)),
|
||||
).toBe(true);
|
||||
@@ -240,6 +265,7 @@ it("explains the three offers of the home with the same names, 3 or 4 deliverabl
|
||||
offer.name,
|
||||
offer.summary,
|
||||
...offer.deliverables,
|
||||
offer.firstStep,
|
||||
offer.proof.note ?? "",
|
||||
].join(" ");
|
||||
expect(text).not.toMatch(/R\$|\d+\s?%|\d+\s?dias|["“”]/);
|
||||
@@ -254,6 +280,48 @@ it("explains the three offers of the home with the same names, 3 or 4 deliverabl
|
||||
expect(serviceOffers[2].proof.caseIds).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses one name per offer: the service page, the breadcrumb label and the WhatsApp subject match the offer", () => {
|
||||
for (const offer of serviceOffers) {
|
||||
const service = servicePages.find(({ slug }) => offer.href.endsWith(slug));
|
||||
// a revisão abre /ia, que não é uma página de serviço
|
||||
if (!service) {
|
||||
expect(offer.id).toBe("revisao");
|
||||
continue;
|
||||
}
|
||||
expect(service.navLabel).toBe(offer.name);
|
||||
expect(service.headline).toBe(offer.name);
|
||||
expect(service.whatsappSubject).toBe(offer.whatsappSubject);
|
||||
expect(getServiceOffer(service.slug)).toBe(offer);
|
||||
}
|
||||
expect(getServiceOffer("consultoria-nextjs")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("gives every service a short name and a lowercase WhatsApp subject with its article, and no two names alike", () => {
|
||||
expect(servicePages.map(({ navLabel }) => navLabel)).toEqual([
|
||||
"Site que traz pedidos pelo WhatsApp",
|
||||
"Sistema para tirar a operação da planilha",
|
||||
"Consultoria em Next.js",
|
||||
"Aplicativos desktop",
|
||||
"Integração de equipamentos",
|
||||
"Diagnóstico de arquitetura",
|
||||
]);
|
||||
for (const service of servicePages) {
|
||||
expect(service.whatsappSubject).toMatch(/^(um|uma|a) [a-zà-ú]/);
|
||||
expect(service.whatsappSubject).not.toMatch(/[.!?]$/);
|
||||
}
|
||||
expect(getService("consultoria-nextjs").whatsappSubject).toBe(
|
||||
"uma consultoria em Next.js",
|
||||
);
|
||||
const names = servicePages.map(({ navLabel }) => navLabel);
|
||||
expect(new Set(names).size).toBe(names.length);
|
||||
});
|
||||
|
||||
it("says the systems service in terms of the pain: spreadsheets and rework", () => {
|
||||
expect(getService("engenharia-de-software").answer).toMatch(
|
||||
/planilhas.*retrabalho/,
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -323,7 +391,9 @@ it("derives the exact related service set from the existing relationship", () =>
|
||||
expect(getRelatedServices("twins-lab").map(({ slug }) => slug)).toEqual([
|
||||
"integracao-hardware-software-iot",
|
||||
]);
|
||||
// a SEV liga à oferta de site e à de sistema, nesta ordem
|
||||
expect(getRelatedServices("sev-modaintima").map(({ slug }) => slug)).toEqual([
|
||||
"criacao-de-sites",
|
||||
"engenharia-de-software",
|
||||
]);
|
||||
expect(
|
||||
@@ -373,7 +443,7 @@ it("writes one sentence on why each related case matters to its service", () =>
|
||||
}
|
||||
});
|
||||
|
||||
it("cites the Apolo Energia experience, from 2022, only on the IoT and architecture services", () => {
|
||||
it("cites the Apolo Energia experience, from 2022, only on the systems, IoT and architecture services", () => {
|
||||
const apolo = portfolioData.experiences.find(
|
||||
({ id }) => id === "apolo-energia",
|
||||
);
|
||||
@@ -383,6 +453,7 @@ it("cites the Apolo Energia experience, from 2022, only on the IoT and architect
|
||||
.filter(({ experienceNote }) => experienceNote)
|
||||
.map(({ slug }) => slug);
|
||||
expect(withNote).toEqual([
|
||||
"engenharia-de-software",
|
||||
"integracao-hardware-software-iot",
|
||||
"arquitetura-de-software",
|
||||
]);
|
||||
@@ -390,12 +461,24 @@ it("cites the Apolo Energia experience, from 2022, only on the IoT and architect
|
||||
const note = getService(slug).experienceNote ?? "";
|
||||
expect(note).toMatch(/Apolo Energia/);
|
||||
expect(note).toMatch(/desde 2022/i);
|
||||
expect(note).toMatch(/sistemas internos/i);
|
||||
expect(note).toMatch(/aplicativos/i);
|
||||
expect(note).toMatch(/APIs/);
|
||||
// sem link e sem detalhe de operação do cliente
|
||||
expect(note).not.toMatch(/https?:|www\.|\.com/i);
|
||||
}
|
||||
for (const slug of [
|
||||
"integracao-hardware-software-iot",
|
||||
"arquitetura-de-software",
|
||||
]) {
|
||||
const note = getService(slug).experienceNote ?? "";
|
||||
expect(note).toMatch(/sistemas internos/i);
|
||||
expect(note).toMatch(/aplicativos/i);
|
||||
}
|
||||
// o sistema diz o que o currículo diz da Apolo: aplicações web e mobile para rotinas internas, APIs e painéis de operação
|
||||
const systemsNote = getService("engenharia-de-software").experienceNote ?? "";
|
||||
expect(systemsNote).toMatch(/aplicações web e mobile para rotinas internas/);
|
||||
expect(systemsNote).toMatch(/painéis de operação/);
|
||||
expect(apolo?.description).toMatch(/aplicações Web e Mobile/);
|
||||
expect(apolo?.description).toMatch(/rotinas internas/);
|
||||
});
|
||||
|
||||
it("keeps the problem of every client case concrete, with no invented figure or quote", () => {
|
||||
@@ -449,6 +532,7 @@ it("keeps AS Serviços without a public site and states the author's participati
|
||||
it("keeps site-content and the modules it imports free of Node built-ins, so client components can import it", () => {
|
||||
for (const file of [
|
||||
"site-content",
|
||||
"offer-labels",
|
||||
"portfolio-data",
|
||||
"proof",
|
||||
"project-health",
|
||||
|
||||
Reference in new issue
Block a user