feat(home): posicionamento com prova, menu sem recarregar e WhatsApp em todo o site

H1 'Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha.'; HeroWork maior com a SEV; menu Cases, Serviços, Como trabalho, Sobre e IA com next/link e pré-carregamento no hover; menu do celular em tela cheia; WhatsApp flutuante e no rodapé com a mensagem da rota; rodapé em todas as páginas; FAQ da home; /ia como oferta de revisão de app feito com IA; formulário com noscript, validação do mailto e plano B do pop-up; cartões sem foco indevido; sem a fonte Monument.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 14:15:25 -03:00
1 parent e80f73d075
commit 10f170abf4
56 files changed
+3099 -906

No files matched your search

+17 -14
View File
@@ -12,7 +12,10 @@ beforeEach(() => {
localStorage.setItem("evandro.analytics-consent.v1", "granted");
delete window["ga-disable-G-QTT9QT0T3S"];
window.gtag = jest.fn();
jest.spyOn(window, "open").mockImplementation(() => null);
// uma aba aberta de verdade: se devolver null, o Contact cai para location.href
jest
.spyOn(window, "open")
.mockImplementation(() => ({ opener: {} }) as unknown as Window);
});
afterEach(() => {
@@ -48,7 +51,7 @@ function sentMessage() {
describe("Contact: validate()", () => {
it("path: only the name is missing -> only the name is flagged and gets the focus", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, {
email: "[email protected]",
@@ -69,7 +72,7 @@ describe("Contact: validate()", () => {
});
it("path: only the e-mail is malformed -> only the e-mail is flagged and gets the focus (not the name)", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, {
name: "Ana",
@@ -88,7 +91,7 @@ describe("Contact: validate()", () => {
});
it("path: only the message is missing -> only the message is flagged and gets the focus", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, { name: "Ana" });
@@ -105,7 +108,7 @@ describe("Contact: validate()", () => {
});
it("path: name and message made only of spaces -> both rejected", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, { name: " ", message: " " });
@@ -118,7 +121,7 @@ describe("Contact: validate()", () => {
});
it("path: e-mail made only of spaces -> counts as omitted: no error and no e-mail line", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, {
name: "Ana",
@@ -136,7 +139,7 @@ describe("Contact: validate()", () => {
it.each(["a@b", "a@c.", "@c.com", "a@@c.com", "a [email protected]", "sem-arroba.com"])(
"path: e-mail %j -> invalid format is blocked",
async (email) => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, { name: "Ana", email, message: "Organizar pedidos" });
@@ -150,7 +153,7 @@ describe("Contact: validate()", () => {
it.each(["[email protected]", "[email protected]"])(
"path: e-mail %j -> valid format is accepted and sent",
async (email) => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, { name: "Ana", email, message: "Organizar pedidos" });
@@ -165,7 +168,7 @@ describe("Contact: validate()", () => {
describe("Contact: updateField() and the error state", () => {
it("path: typing in one field clears only that field's error", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await user.click(submitButton());
expect(screen.getByText(NAME_ERROR)).toBeVisible();
@@ -180,7 +183,7 @@ describe("Contact: updateField() and the error state", () => {
});
it("path: changing the project type keeps the other fields' errors untouched", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await user.click(submitButton());
@@ -197,7 +200,7 @@ describe("Contact: updateField() and the error state", () => {
});
it("path: fixing every error and submitting again -> errors gone, WhatsApp opens once", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await user.click(submitButton());
expect(window.open).not.toHaveBeenCalled();
@@ -214,7 +217,7 @@ describe("Contact: updateField() and the error state", () => {
describe("Contact: mensagem montada", () => {
it("path: surrounding spaces are trimmed in name, e-mail and message", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await fill(user, {
name: " Ana ",
@@ -233,7 +236,7 @@ describe("Contact: mensagem montada", () => {
});
it("path: the chosen project type and the e-mail line reach the mailto draft, and the e-mail line is omitted when blank", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
await user.selectOptions(
screen.getByRole("combobox", { name: /tipo de projeto/i }),
@@ -260,7 +263,7 @@ describe("Contact: mensagem montada", () => {
});
it("path: the direct e-mail row reports its own placement", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
render(<Contact />);
const direct = screen.getByRole("link", {