feat(home): prova no topo, contato sem atrito e conversão medida
Home mais curta (hero, vitrine de clientes, sobre, serviços, IA resumida, depoimentos e contato), faixa de clientes e telas reais no hero, sem números do GitHub. Barra de cookies fina, WhatsApp fixo no celular, formulário com e-mail opcional e alternativa por e-mail, página /ia, eventos de e-mail, redes e currículo e conversão do Ads opcional por variável. Rodapé em 3 colunas. Remove componentes órfãos (LighthouseMetrics, Skills, Experience, contagem do GitHub).
This commit is contained in:
1 parent
6e1022b58c
commit
8d07b8c6bb
60 files changed
+4392
-2124
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import FloatingWhatsApp from "./FloatingWhatsApp";
|
||||
|
||||
const CONSENT_KEY = "evandro.analytics-consent.v1";
|
||||
|
||||
type Entry = Pick<IntersectionObserverEntry, "target" | "isIntersecting">;
|
||||
|
||||
let notify: (entries: Entry[]) => void = () => {};
|
||||
const originalObserver = global.IntersectionObserver;
|
||||
|
||||
class FakeObserver {
|
||||
constructor(callback: IntersectionObserverCallback) {
|
||||
notify = (entries) =>
|
||||
callback(entries as IntersectionObserverEntry[], this as never);
|
||||
}
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
takeRecords = () => [];
|
||||
}
|
||||
|
||||
function Page({ withInvites = true }: { withInvites?: boolean }) {
|
||||
return (
|
||||
<>
|
||||
{withInvites && (
|
||||
<>
|
||||
<a id="hero-contact-cta" href="/#contact">
|
||||
Conversar sobre meu projeto
|
||||
</a>
|
||||
<section id="contact" aria-label="Contato" />
|
||||
</>
|
||||
)}
|
||||
<FloatingWhatsApp />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function setVisibility(hero: boolean, contact: boolean) {
|
||||
act(() =>
|
||||
notify([
|
||||
{
|
||||
target: document.getElementById("hero-contact-cta") as Element,
|
||||
isIntersecting: hero,
|
||||
},
|
||||
{
|
||||
target: document.getElementById("contact") as Element,
|
||||
isIntersecting: contact,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
const floatingLink = () =>
|
||||
screen.queryByRole("link", { name: "Conversar no WhatsApp" });
|
||||
|
||||
beforeEach(() => {
|
||||
global.IntersectionObserver =
|
||||
FakeObserver as unknown as typeof IntersectionObserver;
|
||||
jest.replaceProperty(process.env, "NODE_ENV", "production");
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
window.gtag = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.IntersectionObserver = originalObserver;
|
||||
jest.restoreAllMocks();
|
||||
localStorage.clear();
|
||||
delete window.gtag;
|
||||
});
|
||||
|
||||
it("stays hidden until the observer reports, then shows a 56px WhatsApp shortcut for small screens", () => {
|
||||
render(<Page />);
|
||||
expect(floatingLink()).toBeNull();
|
||||
|
||||
setVisibility(false, false);
|
||||
const link = floatingLink();
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
const url = new URL(link?.getAttribute("href") ?? "");
|
||||
expect(url.origin).toBe("https://wa.me");
|
||||
expect(url.searchParams.get("text")).toContain(
|
||||
"Quero conversar sobre um projeto.",
|
||||
);
|
||||
// alvo de 56 px, canto inferior direito, só abaixo de 768 px
|
||||
expect(link).toHaveClass("h-14", "w-14");
|
||||
expect(link?.parentElement).toHaveClass("fixed", "right-sp-4", "md:hidden");
|
||||
});
|
||||
|
||||
it("hides while the hero button is on screen", () => {
|
||||
render(<Page />);
|
||||
setVisibility(true, false);
|
||||
expect(floatingLink()).toBeNull();
|
||||
|
||||
setVisibility(false, false);
|
||||
expect(floatingLink()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides while the contact section is on screen", () => {
|
||||
render(<Page />);
|
||||
setVisibility(false, true);
|
||||
expect(floatingLink()).toBeNull();
|
||||
|
||||
setVisibility(false, false);
|
||||
expect(floatingLink()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows right away on a page that has neither the hero button nor the contact section", () => {
|
||||
render(<Page withInvites={false} />);
|
||||
expect(floatingLink()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("lifts itself by the height of the open cookie bar", () => {
|
||||
render(<Page />);
|
||||
setVisibility(false, false);
|
||||
|
||||
expect(floatingLink()?.parentElement?.className).toContain(
|
||||
"var(--cookie-bar-height,0px)",
|
||||
);
|
||||
});
|
||||
|
||||
it("reports the click as a floating WhatsApp handoff", () => {
|
||||
render(<Page />);
|
||||
setVisibility(false, false);
|
||||
|
||||
fireEvent.click(floatingLink() as HTMLElement, { button: 0 });
|
||||
|
||||
expect(window.gtag).toHaveBeenCalledTimes(1);
|
||||
expect(window.gtag).toHaveBeenCalledWith(
|
||||
"event",
|
||||
"whatsapp_open",
|
||||
expect.objectContaining({ placement: "floating" }),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user