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; the two offers without a case have neither", () => { expect(getService("tauri-rust").diagram).toBe("desktop"); expect(getService("integracao-hardware-software-iot").diagram).toBe("iot"); expect(getService("arquitetura-de-software").diagram).toBe("arquitetura"); // nenhum esquema existente mostra um atendimento no WhatsApp ou a emissão de nota: o topo fica numa coluna só const withoutSide = [ "atendimento-whatsapp-ia", "nota-fiscal-reforma-tributaria", ]; for (const { slug, heroCase, diagram } of servicePages) { if (withoutSide.includes(slug)) { expect(heroCase ?? diagram).toBeUndefined(); continue; } 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 the form, 'Chamar no WhatsApp' for WhatsApp and 'Ver case' for a case", () => { expect(buttonLabels.contact).toBe("Falar sobre meu projeto"); expect(buttonLabels.whatsapp).toBe("Chamar no WhatsApp"); expect(buttonLabels.case).toBe("Ver case"); expect(Object.values(buttonLabels).join("|")).not.toMatch( /Ver o case|Ler case|Conversar|oportunidade/, ); }); it("gives a block loop to the two offer pages that have no cover nor diagram at the top", () => { const loops = Object.fromEntries( servicePages .filter((service) => service.heroLoop) .map((service) => [service.slug, service.heroLoop]), ); expect(loops).toEqual({ "atendimento-whatsapp-ia": "whatsapp", "nota-fiscal-reforma-tributaria": "fiscal", }); }); 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 five offers 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", "Atendimento no WhatsApp com IA sob controle", "Revisão de sistema que já existe", "Nota fiscal pronta para a reforma tributária", ]); expect(serviceOffers.map(({ whatsappSubject }) => whatsappSubject)).toEqual([ "um site que traz pedidos pelo WhatsApp", "um sistema para tirar a operação da planilha", "um atendimento no WhatsApp com IA sob controle", "a revisão de um sistema que já existe", "a adaptação da nota fiscal à reforma tributária", ]); expect(serviceOffers.map(({ href }) => href)).toEqual([ "/servicos/criacao-de-sites", "/servicos/engenharia-de-software", "/servicos/atendimento-whatsapp-ia", "/ia", "/servicos/nota-fiscal-reforma-tributaria", ]); 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|["“”]/); } const offer = (id: string) => serviceOffers.find((item) => item.id === id); expect(offer("site")?.proof.caseIds).toEqual([ "lavacao-047", "sev-modaintima", ]); expect(offer("sistema")?.proof.caseIds).toEqual(["sev-modaintima"]); expect(offer("sistema")?.proof.note).toMatch(/Apolo Energia/); expect(offer("sistema")?.proof.note).toMatch(/uso interno/); expect(offer("revisao")?.proof.caseIds).toEqual([]); expect(offer("revisao")?.proof.note).toMatch(/cinco episódios públicos/); // as duas ofertas novas não têm case: a nota diz isso em vez de mostrar prova que não existe for (const id of ["whatsapp", "fiscal"]) { expect(offer(id)?.proof.caseIds).toEqual([]); expect(offer(id)?.proof.note).toMatch(/^Ainda não há case público/); } }); it("backs the WhatsApp and the invoice pages with the official sources, and says nothing the sources do not", () => { const whatsapp = getService("atendimento-whatsapp-ia"); expect(whatsapp.official?.links.map(({ href }) => href)).toEqual([ "https://whatsappbusiness.com/policy/", ]); const fiscal = getService("nota-fiscal-reforma-tributaria"); expect(fiscal.official?.links.map(({ href }) => href)).toEqual([ "https://www.cgibs.gov.br/upload/arquivos/202607/31091735-20260730-16h30-ato-conjunto-rfb-cgibs-na-c2-ba-4-260731-090909.pdf", "https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2026/julho/receita-federal-e-comite-gestor-do-ibs-publicam-o-cronograma-de-implementacao-dos-documentos-fiscais-eletronicos-da-reforma-tributaria-do-consumo", ]); // as quatro datas do art. 1º do Ato Conjunto RFB/CGIBS nº 4/2026 expect(fiscal.official?.items.join(" ")).toMatch( /3 de agosto de 2026.*1º de outubro de 2026.*1º de dezembro de 2026.*1º de janeiro de 2027/, ); // sem fonte oficial lida para rejeição de nota, multa ou tolerância: nada disso entra expect(JSON.stringify(fiscal)).not.toMatch(/rejei|multa|tolerânc/i); // o contador continua dono da classificação fiscal e das alíquotas expect(JSON.stringify(fiscal)).toMatch(/não substitui o contador/); }); 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", "Atendimento no WhatsApp com IA sob controle", "Nota fiscal pronta para a reforma tributária", "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:/); } });