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.
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
import { HOME_SUBJECT } from "@/lib/whatsapp";
|
|
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: "Chamar 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");
|
|
// o dublê central de next/navigation abre na home ("/"): o botão fala do assunto da home
|
|
expect(url.searchParams.get("text")).toContain(
|
|
`Quero conversar sobre ${HOME_SUBJECT}.`,
|
|
);
|
|
// 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("hides by CSS while the cookie bar is open (the data-cookie-bar mark on <html>), instead of sitting on top of it", () => {
|
|
render(<Page />);
|
|
setVisibility(false, false);
|
|
|
|
const className = floatingLink()?.parentElement?.className ?? "";
|
|
expect(className).toContain("[[data-cookie-bar=open]_&]:hidden");
|
|
// não sobe mais pela altura da barra: some enquanto ela está aberta
|
|
expect(className).not.toContain("--cookie-bar-height");
|
|
expect(className).toContain(
|
|
"bottom-[calc(var(--sp-4)+env(safe-area-inset-bottom))]",
|
|
);
|
|
});
|
|
|
|
it("sits in a named complementary region (the 'region' landmark rule), which only exists while the shortcut shows", () => {
|
|
render(<Page />);
|
|
expect(screen.queryByRole("complementary")).toBeNull();
|
|
|
|
setVisibility(false, false);
|
|
const region = screen.getByRole("complementary", { name: "Contato rápido" });
|
|
expect(region.tagName).toBe("ASIDE");
|
|
expect(region).toContainElement(floatingLink());
|
|
});
|
|
|
|
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" }),
|
|
);
|
|
});
|