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.
97 lines
3.4 KiB
TypeScript
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("/");
|
|
}
|
|
});
|
|
});
|