Files
portifolio-evandrodevbr/app/components/contact-subject.test.ts
T
evandro c7d09d63d4
CI / audit (push) Successful in 10m38s
CI / verify (push) Successful in 13m10s
CI / deploy (push) Successful in 1m50s
CI / e2e (push) Successful in 18m48s
feat(site): blocos 3D da marca, novas ofertas e hero mais abrangente
Hero e movimento
- cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py):
  marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao
  cursor e, parada no topo, passa sozinha pelos estados com legenda
- três níveis de aparelho (full, lite, static); sem WebGL fica o pôster
- título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha."
- faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções
- rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa
  compartilhada entre a lista de cases e o case

Ofertas
- atendimento no WhatsApp com IA e nota fiscal para a reforma tributária
  (páginas de serviço, cartões da home, formulário e aviso com data de validade)
- página /ia atualizada, com fontes
- peças 3D em loop nos cartões e topos de página (3d/pecas.py)

Design
- hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta
- um destino por rótulo: "Falar sobre meu projeto" leva ao formulário,
  "Chamar no WhatsApp" abre o WhatsApp
- logo montado em blocos, 404 redesenhado, grades sem célula vazia
- ícones do PWA e manifesto novos

Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
2026-11-10 03:48:25 -03:00

97 lines
3.4 KiB
TypeScript

import { serviceOffers } from "@/lib/site-content";
import { OFFER_SUBJECTS } from "@/lib/whatsapp";
import {
CONTACT_FORM_HASH,
CONTACT_FORM_ID,
CONTACT_KIND_PARAM,
CONTACT_KINDS,
CONTACT_SUBJECT_OPTIONS,
contactKindForSubject,
contactKindHref,
contactSubjectForKind,
DEFAULT_CONTACT_SUBJECT,
} from "./contact-subject";
describe("contact-subject", () => {
it("starts the options with the default, 'Ainda preciso definir', so nobody is pushed into a type", () => {
expect(CONTACT_SUBJECT_OPTIONS[0]).toBe(DEFAULT_CONTACT_SUBJECT);
expect(DEFAULT_CONTACT_SUBJECT).toBe("Ainda preciso definir");
});
it("maps every type an offer asks for to an option of the form", () => {
for (const subject of Object.values(CONTACT_KINDS)) {
expect(CONTACT_SUBJECT_OPTIONS).toContain(subject);
}
expect(CONTACT_KINDS).toEqual({
site: "Site que traz pedidos pelo WhatsApp",
sistema: "Sistema para tirar a operação da planilha",
whatsapp: "Atendimento no WhatsApp com IA sob controle",
revisao: "Revisão de sistema que já existe",
fiscal: "Nota fiscal pronta para a reforma tributária",
});
});
it("uses the name of each offer, the same one of serviceOffers (lib/site-content.ts), and the same WhatsApp phrase", () => {
expect(serviceOffers.map(({ id }) => id)).toEqual(
Object.keys(CONTACT_KINDS),
);
for (const offer of serviceOffers) {
expect(CONTACT_KINDS[offer.id]).toBe(offer.name);
expect(OFFER_SUBJECTS[offer.id]).toBe(offer.whatsappSubject);
// a frase entra em "Quero conversar sobre {assunto}.": começa em minúscula e não termina em ponto
expect(OFFER_SUBJECTS[offer.id]).toMatch(/^[a-zà-ú].*[^.]$/);
}
});
it("finds the offer of an option of the form, and none for the other options", () => {
expect(contactKindForSubject(CONTACT_KINDS.site)).toBe("site");
expect(contactKindForSubject(CONTACT_KINDS.revisao)).toBe("revisao");
for (const other of [
DEFAULT_CONTACT_SUBJECT,
"Aplicativos",
"",
"constructor",
null,
undefined,
]) {
expect(contactKindForSubject(other)).toBeUndefined();
}
});
it("looks a type up by its own keys only", () => {
expect(contactSubjectForKind("site")).toBe(
"Site que traz pedidos pelo WhatsApp",
);
expect(contactSubjectForKind("revisao")).toBe(
"Revisão de sistema que já existe",
);
for (const unknown of [
"",
"Site",
"constructor",
"toString",
"__proto__",
]) {
expect(contactSubjectForKind(unknown)).toBeUndefined();
}
expect(contactSubjectForKind(null)).toBeUndefined();
expect(contactSubjectForKind(undefined)).toBeUndefined();
});
it("builds the address with the type in the search, before the fragment, so #contact-form still finds the form card", () => {
expect(CONTACT_FORM_ID).toBe("contact-form");
expect(contactKindHref("site")).toBe(
`/?${CONTACT_KIND_PARAM}=site${CONTACT_FORM_HASH}`,
);
expect(contactKindHref("site")).toBe("/?tipo=site#contact-form");
for (const kind of Object.keys(CONTACT_KINDS) as Array<
keyof typeof CONTACT_KINDS
>) {
const url = new URL(contactKindHref(kind), "https://example.com");
expect(url.hash).toBe("#contact-form");
expect(url.searchParams.get("tipo")).toBe(kind);
expect(url.pathname).toBe("/");
}
});
});