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.
283 lines
9.9 KiB
TypeScript
283 lines
9.9 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import Contact from "./Contact";
|
|
|
|
/**
|
|
* Testes de caminhos-base de Contact (complemento de Contact.test.tsx): validate() campo a campo,
|
|
* foco no primeiro campo inválido, limpeza independente dos erros e montagem da mensagem.
|
|
*/
|
|
|
|
beforeEach(() => {
|
|
jest.replaceProperty(process.env, "NODE_ENV", "production");
|
|
localStorage.setItem("evandro.analytics-consent.v1", "granted");
|
|
delete window["ga-disable-G-QTT9QT0T3S"];
|
|
window.gtag = jest.fn();
|
|
// 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(() => {
|
|
jest.restoreAllMocks();
|
|
localStorage.clear();
|
|
delete window.gtag;
|
|
});
|
|
|
|
const NAME_ERROR = "Informe seu nome para eu saber com quem falo.";
|
|
const EMAIL_ERROR =
|
|
"Confira o e-mail: ele precisa ter o formato [email protected].";
|
|
const MESSAGE_ERROR = "Conte em poucas palavras o que você precisa resolver.";
|
|
|
|
const nameField = () => screen.getByRole("textbox", { name: /nome/i });
|
|
const emailField = () => screen.getByRole("textbox", { name: /e-mail/i });
|
|
const messageField = () => screen.getByRole("textbox", { name: /contexto/i });
|
|
const submitButton = () => screen.getByRole("button", { name: /whatsapp/i });
|
|
|
|
async function fill(
|
|
user: ReturnType<typeof userEvent.setup>,
|
|
values: { name?: string; email?: string; message?: string },
|
|
) {
|
|
if (values.name !== undefined) await user.type(nameField(), values.name);
|
|
if (values.email !== undefined) await user.type(emailField(), values.email);
|
|
if (values.message !== undefined)
|
|
await user.type(messageField(), values.message);
|
|
}
|
|
|
|
function sentMessage() {
|
|
const url = new URL(String(jest.mocked(window.open).mock.calls[0][0]));
|
|
return url.searchParams.get("text") ?? "";
|
|
}
|
|
|
|
describe("Contact: validate()", () => {
|
|
it("path: only the name is missing -> only the name is flagged and gets the focus", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, {
|
|
email: "[email protected]",
|
|
message: "Organizar pedidos",
|
|
});
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(nameField()).toHaveAttribute("aria-invalid", "true");
|
|
expect(emailField()).not.toHaveAttribute("aria-invalid");
|
|
expect(messageField()).not.toHaveAttribute("aria-invalid");
|
|
expect(screen.getByText(NAME_ERROR)).toBeVisible();
|
|
expect(screen.queryByText(EMAIL_ERROR)).toBeNull();
|
|
expect(screen.queryByText(MESSAGE_ERROR)).toBeNull();
|
|
expect(nameField()).toHaveFocus();
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
expect(window.gtag).not.toHaveBeenCalled();
|
|
});
|
|
|
|
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({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, {
|
|
name: "Ana",
|
|
email: "ana@semponto",
|
|
message: "Organizar pedidos",
|
|
});
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(emailField()).toHaveAttribute("aria-invalid", "true");
|
|
expect(emailField()).toHaveAccessibleDescription(EMAIL_ERROR);
|
|
expect(nameField()).not.toHaveAttribute("aria-invalid");
|
|
expect(messageField()).not.toHaveAttribute("aria-invalid");
|
|
expect(emailField()).toHaveFocus();
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("path: only the message is missing -> only the message is flagged and gets the focus", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, { name: "Ana" });
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(messageField()).toHaveAttribute("aria-invalid", "true");
|
|
expect(messageField()).toHaveAccessibleDescription(
|
|
new RegExp(MESSAGE_ERROR),
|
|
);
|
|
expect(nameField()).not.toHaveAttribute("aria-invalid");
|
|
expect(emailField()).not.toHaveAttribute("aria-invalid");
|
|
expect(messageField()).toHaveFocus();
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("path: name and message made only of spaces -> both rejected", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, { name: " ", message: " " });
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(screen.getByText(NAME_ERROR)).toBeVisible();
|
|
expect(screen.getByText(MESSAGE_ERROR)).toBeVisible();
|
|
expect(nameField()).toHaveFocus();
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("path: e-mail made only of spaces -> counts as omitted: no error and no e-mail line", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, {
|
|
name: "Ana",
|
|
email: " ",
|
|
message: "Organizar pedidos",
|
|
});
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(screen.queryByText(EMAIL_ERROR)).toBeNull();
|
|
expect(window.open).toHaveBeenCalledTimes(1);
|
|
expect(sentMessage()).not.toContain("E-mail:");
|
|
});
|
|
|
|
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({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, { name: "Ana", email, message: "Organizar pedidos" });
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(screen.getByText(EMAIL_ERROR)).toBeVisible();
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it.each(["[email protected]", "[email protected]"])(
|
|
"path: e-mail %j -> valid format is accepted and sent",
|
|
async (email) => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, { name: "Ana", email, message: "Organizar pedidos" });
|
|
|
|
await user.click(submitButton());
|
|
|
|
expect(screen.queryByText(EMAIL_ERROR)).toBeNull();
|
|
expect(window.open).toHaveBeenCalledTimes(1);
|
|
expect(sentMessage()).toContain(`E-mail: ${email}\n`);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("Contact: updateField() and the error state", () => {
|
|
it("path: typing in one field clears only that field's error", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await user.click(submitButton());
|
|
expect(screen.getByText(NAME_ERROR)).toBeVisible();
|
|
expect(screen.getByText(MESSAGE_ERROR)).toBeVisible();
|
|
|
|
await user.type(messageField(), "Organizar pedidos");
|
|
|
|
expect(screen.queryByText(MESSAGE_ERROR)).toBeNull();
|
|
expect(messageField()).not.toHaveAttribute("aria-invalid");
|
|
expect(screen.getByText(NAME_ERROR)).toBeVisible();
|
|
expect(nameField()).toHaveAttribute("aria-invalid", "true");
|
|
});
|
|
|
|
it("path: changing the project type keeps the other fields' errors untouched", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await user.click(submitButton());
|
|
|
|
await user.selectOptions(
|
|
screen.getByRole("combobox", { name: /tipo de projeto/i }),
|
|
"Aplicativos",
|
|
);
|
|
|
|
expect(screen.getByText(NAME_ERROR)).toBeVisible();
|
|
expect(screen.getByText(MESSAGE_ERROR)).toBeVisible();
|
|
expect(
|
|
screen.getByRole("combobox", { name: /tipo de projeto/i }),
|
|
).toHaveValue("Aplicativos");
|
|
});
|
|
|
|
it("path: fixing every error and submitting again -> errors gone, WhatsApp opens once", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await user.click(submitButton());
|
|
expect(window.open).not.toHaveBeenCalled();
|
|
|
|
await fill(user, { name: "Ana", message: "Organizar pedidos" });
|
|
await user.click(submitButton());
|
|
|
|
expect(screen.queryByText(NAME_ERROR)).toBeNull();
|
|
expect(screen.queryByText(MESSAGE_ERROR)).toBeNull();
|
|
expect(window.open).toHaveBeenCalledTimes(1);
|
|
expect(window.gtag).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("Contact: mensagem montada", () => {
|
|
it("path: surrounding spaces are trimmed in name, e-mail and message", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
await fill(user, {
|
|
name: " Ana ",
|
|
email: " [email protected] ",
|
|
message: " Organizar pedidos ",
|
|
});
|
|
|
|
await user.click(submitButton());
|
|
|
|
const message = sentMessage();
|
|
expect(message).toContain("Nome: Ana\n");
|
|
expect(message).toContain("E-mail: [email protected]\n");
|
|
expect(message).toContain(
|
|
"Contexto, objetivo e prazo:\nOrganizar pedidos\n\n",
|
|
);
|
|
});
|
|
|
|
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({ delay: null });
|
|
render(<Contact />);
|
|
await user.selectOptions(
|
|
screen.getByRole("combobox", { name: /tipo de projeto/i }),
|
|
"Aplicativos",
|
|
);
|
|
await fill(user, { name: "Ana", message: "Organizar pedidos" });
|
|
|
|
const mail = screen.getByRole("link", {
|
|
name: "Prefere e-mail? Enviar por e-mail",
|
|
});
|
|
const body = new URL(mail.getAttribute("href") ?? "").searchParams.get(
|
|
"body",
|
|
);
|
|
expect(body).toContain("Tipo de projeto: Aplicativos");
|
|
expect(body).not.toContain("E-mail:");
|
|
|
|
await user.type(emailField(), "[email protected]");
|
|
const withEmail = new URL(
|
|
screen
|
|
.getByRole("link", { name: "Prefere e-mail? Enviar por e-mail" })
|
|
.getAttribute("href") ?? "",
|
|
).searchParams.get("body");
|
|
expect(withEmail).toContain("E-mail: [email protected]\n");
|
|
});
|
|
|
|
it("path: the direct e-mail row reports its own placement", async () => {
|
|
const user = userEvent.setup({ delay: null });
|
|
render(<Contact />);
|
|
|
|
const direct = screen.getByRole("link", {
|
|
name: /evandrofonsecajunior@gmail\.com/,
|
|
});
|
|
// o jsdom não navega para mailto:; o clique só precisa chegar ao rastreio
|
|
direct.addEventListener("click", (event) => event.preventDefault());
|
|
await user.click(direct);
|
|
|
|
expect(window.gtag).toHaveBeenCalledWith(
|
|
"event",
|
|
"email_click",
|
|
expect.objectContaining({ placement: "contact_direct" }),
|
|
);
|
|
});
|
|
});
|