fix(home): oferta leva ao formulário, ofertas logo depois da vitrine
O botão das ofertas rola até o contato com o tipo marcado e não quebra mais as âncoras; ofertas com o que você recebe e primeiro passo, logo depois da vitrine; bloco de IA fora da home; cartão largo da SEV no hero do celular; separador da faixa de clientes só entre itens; favicon de 13 para 1,8 KB; pré-carregamento só no hover e nunca de /#... fora da home; X do menu e cabeçalho opaco; WhatsApp com "o case"; /ia com 3 passos e modelo de relatório ilustrativo; Sobre alinhado ao currículo; CSP Report-Only com os domínios do Ads.
This commit is contained in:
1 parent
fc44a69fb3
commit
9e2d65a926
51 files changed
+1730
-420
No files matched your search
@@ -0,0 +1,189 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import ContactOfferLink from "./ContactOfferLink";
|
||||
import { focusFragment, onFragmentClick } from "./cinematic/fragment-nav";
|
||||
import { CONTACT_KIND_EVENT } from "./contact-subject";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de ContactOfferLink (complemento de ContactOfferLink.test.tsx).
|
||||
*
|
||||
* onClick (CC 6): fora da home | sem #contact | clique já tratado pelo gerenciador de âncoras | tecla modificadora ou
|
||||
* botão que não é o principal | rolagem suave | rolagem em salto (movimento reduzido). Os seis caminhos já estão
|
||||
* em ContactOfferLink.test.tsx; aqui entram o que faltava:
|
||||
* - o gerenciador de âncoras REAL (fragment-nav.onFragmentClick) tratando o clique, em vez de um ouvinte avulso;
|
||||
* - Alt como tecla modificadora;
|
||||
* - showContactInAddress (CC 2): o endereço já é /#contact, então não há replaceState; e o `history.state` do roteador
|
||||
* do Next sobrevive ao replaceState.
|
||||
*/
|
||||
|
||||
jest.mock("next/link", () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
prefetch,
|
||||
...props
|
||||
}: { children?: React.ReactNode; prefetch?: boolean | null } & Record<
|
||||
string,
|
||||
unknown
|
||||
>) => (
|
||||
<a data-prefetch={String(prefetch)} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
const scrollIntoView = jest.fn();
|
||||
const scrollTo = jest.fn();
|
||||
let form: HTMLElement;
|
||||
let replaceState: jest.SpyInstance;
|
||||
const originalMatchMedia = window.matchMedia;
|
||||
const originalScrollTo = window.scrollTo;
|
||||
|
||||
beforeEach(() => {
|
||||
scrollIntoView.mockReset();
|
||||
scrollTo.mockReset();
|
||||
HTMLElement.prototype.scrollIntoView = scrollIntoView;
|
||||
window.scrollTo = scrollTo as unknown as typeof window.scrollTo;
|
||||
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}));
|
||||
window.history.replaceState(null, "", "/");
|
||||
replaceState = jest.spyOn(window.history, "replaceState");
|
||||
form = document.createElement("section");
|
||||
form.id = "contact";
|
||||
document.body.append(form);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
replaceState.mockRestore();
|
||||
form.remove();
|
||||
window.matchMedia = originalMatchMedia;
|
||||
window.scrollTo = originalScrollTo;
|
||||
window.history.replaceState(null, "", "/");
|
||||
});
|
||||
|
||||
/** O que useCinematicScroll faz com um clique numa âncora: cancela, empurra o hash, rola e dá o foco. */
|
||||
function installAnchorManager() {
|
||||
return onFragmentClick((hit, event) => {
|
||||
event.preventDefault();
|
||||
if (window.location.hash !== hit.hash)
|
||||
window.history.pushState(null, "", hit.hash);
|
||||
window.scrollTo({ top: 1234, behavior: "smooth" });
|
||||
focusFragment(hit.target);
|
||||
});
|
||||
}
|
||||
|
||||
describe("ContactOfferLink: gerenciador de âncoras real", () => {
|
||||
it("path: the manager takes the click -> its scroll is the only one, the offer cleans the old ?tipo= and still tells the form", () => {
|
||||
window.history.replaceState(null, "", "/?tipo=site");
|
||||
replaceState.mockClear();
|
||||
const heard: unknown[] = [];
|
||||
const listener = (event: Event) =>
|
||||
heard.push((event as CustomEvent).detail);
|
||||
window.addEventListener(CONTACT_KIND_EVENT, listener);
|
||||
const stop = installAnchorManager();
|
||||
try {
|
||||
render(<ContactOfferLink kind="sistema">Começar</ContactOfferLink>);
|
||||
const entries = window.history.length;
|
||||
|
||||
expect(
|
||||
fireEvent.click(screen.getByRole("link", { name: "Começar" })),
|
||||
).toBe(false);
|
||||
|
||||
expect(scrollTo).toHaveBeenCalledTimes(1);
|
||||
expect(scrollIntoView).not.toHaveBeenCalled();
|
||||
expect(form).toHaveFocus();
|
||||
expect(heard).toEqual(["sistema"]);
|
||||
// o gerenciador abriu uma entrada de histórico (/?tipo=site#contact); a oferta só a reescreve
|
||||
expect(window.history.length).toBe(entries + 1);
|
||||
expect(window.location.search).toBe("");
|
||||
expect(window.location.hash).toBe("#contact");
|
||||
expect(replaceState).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
stop();
|
||||
window.removeEventListener(CONTACT_KIND_EVENT, listener);
|
||||
}
|
||||
});
|
||||
|
||||
it("path: the manager takes the click and the address is already /#contact -> no replaceState at all", () => {
|
||||
window.history.replaceState(null, "", "/#contact");
|
||||
replaceState.mockClear();
|
||||
const stop = installAnchorManager();
|
||||
try {
|
||||
render(<ContactOfferLink kind="site">Começar</ContactOfferLink>);
|
||||
|
||||
fireEvent.click(screen.getByRole("link", { name: "Começar" }));
|
||||
|
||||
expect(scrollTo).toHaveBeenCalledTimes(1);
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(window.location.pathname + window.location.hash).toBe("/#contact");
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("path: no manager mounted -> the offer scrolls and focuses by itself, and a second click scrolls again", () => {
|
||||
render(<ContactOfferLink kind="site">Começar</ContactOfferLink>);
|
||||
const link = screen.getByRole("link", { name: "Começar" });
|
||||
|
||||
expect(fireEvent.click(link)).toBe(false);
|
||||
expect(scrollIntoView).toHaveBeenCalledTimes(1);
|
||||
expect(form).toHaveFocus();
|
||||
expect(window.location.hash).toBe("#contact");
|
||||
|
||||
// o endereço já é /#contact (o navegador não rolaria): a oferta rola de novo mesmo assim, sem tocar no endereço
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
replaceState.mockClear();
|
||||
expect(fireEvent.click(link)).toBe(false);
|
||||
expect(scrollIntoView).toHaveBeenCalledTimes(2);
|
||||
expect(form).toHaveFocus();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContactOfferLink: teclas e histórico", () => {
|
||||
it("path: Alt held down -> not taken over (the browser keeps its own behavior)", () => {
|
||||
render(<ContactOfferLink kind="site">Começar</ContactOfferLink>);
|
||||
|
||||
expect(
|
||||
fireEvent.click(screen.getByRole("link", { name: "Começar" }), {
|
||||
altKey: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(scrollIntoView).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(form).not.toHaveFocus();
|
||||
});
|
||||
|
||||
it("path: a modified click still tells the form which type was chosen (the form may be in the same tab)", () => {
|
||||
const heard: unknown[] = [];
|
||||
const listener = (event: Event) =>
|
||||
heard.push((event as CustomEvent).detail);
|
||||
window.addEventListener(CONTACT_KIND_EVENT, listener);
|
||||
try {
|
||||
render(<ContactOfferLink kind="revisao">Começar</ContactOfferLink>);
|
||||
|
||||
fireEvent.click(screen.getByRole("link", { name: "Começar" }), {
|
||||
ctrlKey: true,
|
||||
});
|
||||
|
||||
expect(heard).toEqual(["revisao"]);
|
||||
} finally {
|
||||
window.removeEventListener(CONTACT_KIND_EVENT, listener);
|
||||
}
|
||||
});
|
||||
|
||||
it("path: replaceState keeps the history.state of the router (the Next router stores its tree there)", () => {
|
||||
const routerState = { __NA: true, tree: ["", {}] };
|
||||
window.history.replaceState(routerState, "", "/?tipo=site#contact");
|
||||
replaceState.mockClear();
|
||||
render(<ContactOfferLink kind="sistema">Começar</ContactOfferLink>);
|
||||
|
||||
fireEvent.click(screen.getByRole("link", { name: "Começar" }));
|
||||
|
||||
expect(replaceState).toHaveBeenCalledWith(routerState, "", "/#contact");
|
||||
expect(window.history.state).toEqual(routerState);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user