551 lines
20 KiB
TypeScript
551 lines
20 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import portfolioData from "@/public/portfolio-data.json";
|
|
import {
|
|
buttonLabels,
|
|
caseStudies,
|
|
clientCases,
|
|
clientCasesForListing,
|
|
generalFirstStep,
|
|
getRelatedCases,
|
|
getRelatedServices,
|
|
getServiceOffer,
|
|
isClientCase,
|
|
labCases,
|
|
pageUpdatedAt,
|
|
serviceOffers,
|
|
servicePages,
|
|
} from "./site-content";
|
|
|
|
const clientIds = [
|
|
"sev-modaintima",
|
|
"lavacao-047",
|
|
"sc-plus",
|
|
"as-servicos",
|
|
"julia-beauty",
|
|
];
|
|
const labIds = ["nutriscan", "twins-lab", "reddit-explorer", "gcc-installer"];
|
|
|
|
function getService(slug: string) {
|
|
const service = servicePages.find((item) => item.slug === slug);
|
|
if (!service) throw new Error(`Missing service ${slug}`);
|
|
return service;
|
|
}
|
|
|
|
it("separates client work from the laboratory using the kind in the portfolio data", () => {
|
|
expect(clientCases.map(({ id }) => id)).toEqual(clientIds);
|
|
expect(labCases.map(({ id }) => id)).toEqual(labIds);
|
|
expect(caseStudies).toHaveLength(9);
|
|
expect(
|
|
caseStudies.every(
|
|
(item) => isClientCase(item) === clientIds.includes(item.id),
|
|
),
|
|
).toBe(true);
|
|
expect(isClientCase({ id: "projeto-que-nao-existe" })).toBe(false);
|
|
});
|
|
|
|
it("gives every case the sections of the business structure", () => {
|
|
for (const caseStudy of caseStudies) {
|
|
expect(caseStudy.headline.length).toBeGreaterThan(10);
|
|
expect(caseStudy.context.length).toBeGreaterThan(20);
|
|
expect(caseStudy.problem.length).toBeGreaterThan(20);
|
|
expect(caseStudy.deliveries.length).toBeGreaterThanOrEqual(3);
|
|
expect(caseStudy.stack.length).toBeGreaterThan(0);
|
|
expect(caseStudy.decisions.length).toBeGreaterThan(0);
|
|
// sem dado real e autorizado, não há seção de resultado
|
|
expect(caseStudy.result).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it("writes the NutriScan case in business language", () => {
|
|
expect(caseStudies.find(({ id }) => id === "nutriscan")?.headline).toBe(
|
|
"Consulta de alimentos por nome ou código de barras",
|
|
);
|
|
});
|
|
|
|
it("has no GitHub link, source list or disqualifying caveat in the public text", () => {
|
|
const text = JSON.stringify({ caseStudies, servicePages });
|
|
expect(text).not.toMatch(/github\.com/i);
|
|
expect(text).not.toMatch(/\/#contact/);
|
|
expect(text).not.toMatch(
|
|
/Limites desta evidência|Desafio verificável|Exemplos ilustrativos|não projetos já entregues|Sem promessas|nem CI|não há CI|escopo definido|não comprova|não representa/i,
|
|
);
|
|
for (const caseStudy of caseStudies) {
|
|
expect(caseStudy).not.toHaveProperty("githubUrl");
|
|
expect(caseStudy).not.toHaveProperty("detail");
|
|
}
|
|
});
|
|
|
|
it("never mentions an unavailable demo in the case text", () => {
|
|
expect(JSON.stringify(caseStudies)).not.toMatch(/indispon/i);
|
|
});
|
|
|
|
it("leaves the commercial offer of every service empty for the owner to fill in", () => {
|
|
for (const service of servicePages) {
|
|
expect(service.startingPrice).toBeUndefined();
|
|
expect(service.typicalTimeline).toBeUndefined();
|
|
expect(service.guarantee).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it("mentions the region and a benefit in the title and description of every service", () => {
|
|
for (const service of servicePages) {
|
|
expect(`${service.metaTitle} ${service.metaDescription}`).toMatch(
|
|
/Garuva|Joinville|Santa Catarina/,
|
|
);
|
|
expect(service.metaDescription).toMatch(/remoto|Brasil/);
|
|
}
|
|
const descriptions = servicePages.map(
|
|
({ metaDescription }) => metaDescription,
|
|
);
|
|
expect(new Set(descriptions).size).toBe(descriptions.length);
|
|
});
|
|
|
|
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",
|
|
]);
|
|
expect(getService("consultoria-nextjs").relatedCaseIds).toEqual([
|
|
"sc-plus",
|
|
"lavacao-047",
|
|
]);
|
|
expect(
|
|
servicePages
|
|
.filter(({ relatedCaseIds }) => relatedCaseIds.includes("sev-modaintima"))
|
|
.map(({ slug }) => slug),
|
|
).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({});
|
|
// o GCC Installer é um app para Windows (em Python): entra no serviço de desktop como projeto próprio
|
|
expect(getService("tauri-rust").relatedCaseIds).toEqual(["gcc-installer"]);
|
|
expect(getService("integracao-hardware-software-iot").relatedCaseIds).toEqual(
|
|
["twins-lab"],
|
|
);
|
|
});
|
|
|
|
it("says what the Next.js service cases really prove: sites built by me, not a diagnosis", () => {
|
|
const { caseRelevance } = getService("consultoria-nextjs");
|
|
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("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 in the order of the home showcase, the SEV first", () => {
|
|
expect(clientCasesForListing.map(({ id }) => id)).toEqual([
|
|
"sev-modaintima",
|
|
"lavacao-047",
|
|
"sc-plus",
|
|
"as-servicos",
|
|
"julia-beauty",
|
|
]);
|
|
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",
|
|
]);
|
|
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",
|
|
"/servicos/engenharia-de-software",
|
|
"/ia",
|
|
]);
|
|
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);
|
|
// sem número, preço, prazo ou depoimento inventados
|
|
const text = [
|
|
offer.name,
|
|
offer.summary,
|
|
...offer.deliverables,
|
|
offer.firstStep,
|
|
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("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);
|
|
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'", () => {
|
|
expect(getService("criacao-de-sites").metaTitle).toBe(
|
|
"Criação de sites em Joinville e SC",
|
|
);
|
|
for (const service of servicePages) {
|
|
expect(service.metaTitle).not.toMatch(/em Garuva/);
|
|
expect(service.metaDescription).toMatch(/Garuva e Joinville \(SC\)/);
|
|
}
|
|
});
|
|
|
|
it("opens the case lists with what sells: the catalog, the order by WhatsApp and the panel", () => {
|
|
const deliveries =
|
|
caseStudies.find(({ id }) => id === "sev-modaintima")?.deliveries ?? [];
|
|
const [catalog, whatsapp, panel] = deliveries;
|
|
expect(catalog).toMatch(/^Catálogo por tamanho/);
|
|
expect(whatsapp).toMatch(/^Pedido pelo WhatsApp/);
|
|
expect(panel).toMatch(/^Painel para a loja cadastrar as peças/);
|
|
// a vitrine com animações, que era a primeira, vai para o fim
|
|
expect(deliveries[deliveries.length - 1]).toMatch(
|
|
/^Uma vitrine com animações/,
|
|
);
|
|
// a animação da Lavação também não abre a lista
|
|
expect(
|
|
caseStudies.find(({ id }) => id === "lavacao-047")?.deliveries[0],
|
|
).toMatch(/^Tabela de preços/);
|
|
});
|
|
|
|
it("puts client cases before laboratory cases when listing related work", () => {
|
|
expect(
|
|
getRelatedCases(["twins-lab", "lavacao-047", "sev-modaintima"]).map(
|
|
({ id }) => id,
|
|
),
|
|
).toEqual(["lavacao-047", "sev-modaintima", "twins-lab"]);
|
|
expect(getRelatedCases(["nao-existe"])).toEqual([]);
|
|
});
|
|
|
|
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(
|
|
getRelatedServices("lavacao-047")
|
|
.map(({ slug }) => slug)
|
|
.sort(),
|
|
).toEqual(
|
|
["criacao-de-sites", "engenharia-de-software", "consultoria-nextjs"].sort(),
|
|
);
|
|
expect(getRelatedServices("gcc-installer").map(({ slug }) => slug)).toEqual([
|
|
"tauri-rust",
|
|
]);
|
|
});
|
|
|
|
it("resolves every declared related case ID", () => {
|
|
const caseIds = new Set(caseStudies.map(({ id }) => id));
|
|
const unresolved = servicePages.flatMap(({ slug, relatedCaseIds }) =>
|
|
relatedCaseIds
|
|
.filter((caseId) => !caseIds.has(caseId))
|
|
.map((caseId) => ({ slug, caseId })),
|
|
);
|
|
|
|
expect(unresolved).toEqual([]);
|
|
});
|
|
|
|
it("only cites project ids that exist in public/portfolio-data.json", () => {
|
|
const projectIds = new Set(portfolioData.projects.map(({ id }) => id));
|
|
const cited = [
|
|
...caseStudies.map(({ id }) => id),
|
|
...servicePages.flatMap(({ relatedCaseIds }) => relatedCaseIds),
|
|
...servicePages.flatMap(({ caseRelevance }) => Object.keys(caseRelevance)),
|
|
];
|
|
|
|
expect(cited.length).toBeGreaterThan(0);
|
|
expect(cited.filter((id) => !projectIds.has(id))).toEqual([]);
|
|
});
|
|
|
|
it("writes one sentence on why each related case matters to its service", () => {
|
|
for (const service of servicePages) {
|
|
expect(Object.keys(service.caseRelevance).sort()).toEqual(
|
|
[...service.relatedCaseIds].sort(),
|
|
);
|
|
for (const sentence of Object.values(service.caseRelevance)) {
|
|
expect(sentence.length).toBeGreaterThan(30);
|
|
expect(sentence).toMatch(/[.]$/);
|
|
}
|
|
}
|
|
});
|
|
|
|
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",
|
|
);
|
|
expect(apolo?.period).toMatch(/2022/);
|
|
|
|
const withNote = servicePages
|
|
.filter(({ experienceNote }) => experienceNote)
|
|
.map(({ slug }) => slug);
|
|
expect(withNote).toEqual([
|
|
"engenharia-de-software",
|
|
"integracao-hardware-software-iot",
|
|
"arquitetura-de-software",
|
|
]);
|
|
for (const slug of withNote) {
|
|
const note = getService(slug).experienceNote ?? "";
|
|
expect(note).toMatch(/Apolo Energia/);
|
|
expect(note).toMatch(/desde 2022/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", () => {
|
|
for (const caseStudy of clientCases) {
|
|
expect(caseStudy.problem.length).toBeGreaterThan(200);
|
|
expect(caseStudy.problem).not.toMatch(/["“”]/);
|
|
expect(caseStudy.problem).not.toMatch(/\d+\s?%|R\$\s?\d/);
|
|
}
|
|
expect(
|
|
caseStudies.find(({ id }) => id === "sev-modaintima")?.problem,
|
|
).toMatch(/tamanho/);
|
|
expect(
|
|
caseStudies.find(({ id }) => id === "sev-modaintima")?.problem,
|
|
).toMatch(/WhatsApp/);
|
|
});
|
|
|
|
it("leaves the optional facts of every case empty until there is real data", () => {
|
|
for (const caseStudy of caseStudies) {
|
|
expect(caseStudy.facts ?? []).toEqual([]);
|
|
}
|
|
});
|
|
|
|
it("writes project subtitles without technical jargon", () => {
|
|
const jargon =
|
|
/Next\.js|React|MongoDB|MinGW|\/GCC|tkinter|\bSSE\b|Three\.js|em código|r\/artificial/i;
|
|
for (const caseStudy of caseStudies) {
|
|
expect(caseStudy.subtitle).not.toMatch(jargon);
|
|
}
|
|
for (const project of portfolioData.projects) {
|
|
expect(project.subtitle).not.toMatch(jargon);
|
|
}
|
|
expect(
|
|
portfolioData.projects.find(({ id }) => id === "sc-plus")?.subtitle,
|
|
).toBe("Site institucional com áreas, projetos e contato");
|
|
});
|
|
|
|
it("does not publish the unavailable Twins Lab demo", () => {
|
|
expect(
|
|
caseStudies.find((item) => item.id === "twins-lab")?.demoUrl,
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("keeps AS Serviços without a public site and states the author's participation", () => {
|
|
const asServicos = caseStudies.find(({ id }) => id === "as-servicos");
|
|
expect(asServicos?.demoUrl).toBeNull();
|
|
expect(asServicos?.role).toMatch(/primeira landing page/i);
|
|
expect(asServicos?.role).toMatch(/animações 3D/i);
|
|
expect(asServicos?.role).toMatch(/painel para controle de projetos/i);
|
|
});
|
|
|
|
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",
|
|
]) {
|
|
const source = readFileSync(join(__dirname, `${file}.ts`), "utf8");
|
|
expect(source).not.toMatch(/from "node:/);
|
|
}
|
|
});
|