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,418 @@
|
||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import type { ComponentProps } from "react";
|
||||
import { openCookiePreferences } from "@/lib/analytics";
|
||||
import GoogleAnalytics from "./GoogleAnalytics";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de GoogleAnalytics (complemento de GoogleAnalytics.test.tsx):
|
||||
* modo de desenvolvimento, teclas da barra, reaceite, nova concessão depois de revogar,
|
||||
* cookies de analytics x outros cookies, reaproveitamento de gtag/dataLayer e recuperação de falha de gravação.
|
||||
*/
|
||||
|
||||
jest.mock("next/script", () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
strategy: _strategy,
|
||||
...props
|
||||
}: ComponentProps<"script"> & { strategy?: string }) => <script {...props} />,
|
||||
}));
|
||||
|
||||
const CONSENT_KEY = "evandro.analytics-consent.v1";
|
||||
const GA_DISABLE_KEY = "ga-disable-G-QTT9QT0T3S";
|
||||
|
||||
type AnalyticsWindow = Window & {
|
||||
dataLayer?: IArguments[];
|
||||
gtag?: (...args: unknown[]) => void;
|
||||
[GA_DISABLE_KEY]?: boolean;
|
||||
};
|
||||
|
||||
const analyticsWindow = window as AnalyticsWindow;
|
||||
|
||||
function setCookie(value: string) {
|
||||
// biome-ignore lint/suspicious/noDocumentCookie: JSDOM cookie setup is the behavior boundary exercised by revocation.
|
||||
document.cookie = value;
|
||||
}
|
||||
|
||||
function expireCookie(name: string) {
|
||||
setCookie(`${name}=; Max-Age=0; Path=/`);
|
||||
}
|
||||
|
||||
const COOKIES = ["_ga", "_ga_ABC123", "_gid", "_gat", "portfolio_session"];
|
||||
|
||||
function commands() {
|
||||
return (analyticsWindow.dataLayer ?? []).map((entry) => Array.from(entry));
|
||||
}
|
||||
|
||||
function commandsNamed(...names: string[]) {
|
||||
return commands().filter((entry) =>
|
||||
names.every((name, index) => entry[index] === name),
|
||||
);
|
||||
}
|
||||
|
||||
function OpenPreferencesButton() {
|
||||
return (
|
||||
<button type="button" onClick={openCookiePreferences}>
|
||||
Preferências de cookies
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.replaceProperty(process.env, "NODE_ENV", "production");
|
||||
document.getElementById("google-analytics-script")?.remove();
|
||||
localStorage.clear();
|
||||
for (const name of COOKIES) expireCookie(name);
|
||||
analyticsWindow.dataLayer = undefined;
|
||||
analyticsWindow.gtag = undefined;
|
||||
analyticsWindow[GA_DISABLE_KEY] = undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.getElementById("google-analytics-script")?.remove();
|
||||
for (const name of COOKIES) expireCookie(name);
|
||||
});
|
||||
|
||||
describe("GoogleAnalytics: modo e teclas", () => {
|
||||
it("path: outside production -> renders nothing and never touches analytics", async () => {
|
||||
jest.replaceProperty(process.env, "NODE_ENV", "development");
|
||||
|
||||
const { container } = render(<GoogleAnalytics />);
|
||||
await act(async () => {});
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
expect(analyticsWindow.dataLayer).toBeUndefined();
|
||||
});
|
||||
|
||||
it("path: Escape on the first-visit bar -> bar stays and no choice is stored", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<GoogleAnalytics />);
|
||||
const accept = await screen.findByRole("button", { name: "Aceitar" });
|
||||
accept.focus();
|
||||
|
||||
await user.keyboard("{Escape}");
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeVisible();
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBeNull();
|
||||
expect(screen.queryByText(/Escolha atual/)).toBeNull();
|
||||
});
|
||||
|
||||
it("path: another key on the reopened bar -> stays open", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "denied");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await act(async () => {});
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
|
||||
await user.keyboard("x{Tab}");
|
||||
|
||||
expect(screen.queryByRole("dialog")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("path: reopened with a refusal stored -> the bar announces 'recusado'", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "denied");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await act(async () => {});
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
|
||||
expect(screen.getByRole("dialog")).toHaveAccessibleDescription(
|
||||
/Escolha atual: recusado/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GoogleAnalytics: aceitar e recusar", () => {
|
||||
it("path: reopened while already granted, 'Aceitar' only closes the bar", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
analyticsWindow.gtag = jest.fn();
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
const gtag = analyticsWindow.gtag as jest.Mock;
|
||||
const callsAfterMount = gtag.mock.calls.length;
|
||||
const opener = screen.getByRole("button", {
|
||||
name: "Preferências de cookies",
|
||||
});
|
||||
|
||||
await user.click(opener);
|
||||
await user.click(screen.getByRole("button", { name: "Aceitar" }));
|
||||
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
expect(gtag.mock.calls).toHaveLength(callsAfterMount);
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("granted");
|
||||
expect(screen.getByTestId("google-analytics-script")).toBeInTheDocument();
|
||||
expect(opener).toHaveFocus();
|
||||
});
|
||||
|
||||
it("path: granting again after a revocation -> bootstrap runs once, GA is re-enabled", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
const opener = screen.getByRole("button", {
|
||||
name: "Preferências de cookies",
|
||||
});
|
||||
|
||||
await user.click(opener);
|
||||
await user.click(screen.getByRole("button", { name: "Recusar" }));
|
||||
expect(analyticsWindow[GA_DISABLE_KEY]).toBe(true);
|
||||
expect(screen.queryByTestId("google-analytics-script")).toBeNull();
|
||||
|
||||
await user.click(opener);
|
||||
await user.click(screen.getByRole("button", { name: "Aceitar" }));
|
||||
|
||||
expect(analyticsWindow[GA_DISABLE_KEY]).toBe(false);
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("granted");
|
||||
expect(screen.getByTestId("google-analytics-script")).toBeInTheDocument();
|
||||
// "default" e "js" só na primeira ativação; a configuração dos dois destinos roda a cada concessão
|
||||
expect(commandsNamed("consent", "default")).toHaveLength(1);
|
||||
expect(commandsNamed("js")).toHaveLength(1);
|
||||
expect(commandsNamed("config", "G-QTT9QT0T3S")).toHaveLength(2);
|
||||
expect(commandsNamed("config", "AW-18432694647")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("path: first visit refused -> nothing is revoked because nothing was granted", async () => {
|
||||
const user = userEvent.setup();
|
||||
setCookie("_ga=keep-me; Path=/");
|
||||
const cookieWrites = jest.spyOn(document, "cookie", "set");
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "Recusar" }));
|
||||
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("denied");
|
||||
expect(analyticsWindow[GA_DISABLE_KEY]).toBeUndefined();
|
||||
expect(analyticsWindow.dataLayer).toBeUndefined();
|
||||
expect(cookieWrites).not.toHaveBeenCalled();
|
||||
expect(document.cookie).toContain("_ga=keep-me");
|
||||
});
|
||||
|
||||
it("path: revoking after gtag disappeared -> still persists, disables GA and does not throw", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
delete analyticsWindow.gtag;
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Recusar" }));
|
||||
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("denied");
|
||||
expect(analyticsWindow[GA_DISABLE_KEY]).toBe(true);
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GoogleAnalytics: cookies na revogação", () => {
|
||||
it("path: only _ga and _ga_* are expired; look-alikes and other cookies stay", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
for (const name of COOKIES) setCookie(`${name}=1; Path=/`);
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Recusar" }));
|
||||
|
||||
const remaining = document.cookie.split(";").map((c) => c.trim());
|
||||
expect(remaining).toEqual(
|
||||
expect.arrayContaining(["_gid=1", "_gat=1", "portfolio_session=1"]),
|
||||
);
|
||||
expect(remaining.some((c) => c.startsWith("_ga="))).toBe(false);
|
||||
expect(remaining.some((c) => c.startsWith("_ga_ABC123="))).toBe(false);
|
||||
});
|
||||
|
||||
it("path: no analytics cookie present -> revocation writes no cookie", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
setCookie("portfolio_session=1; Path=/");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
const cookieWrites = jest.spyOn(document, "cookie", "set");
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Recusar" }));
|
||||
|
||||
expect(cookieWrites).not.toHaveBeenCalled();
|
||||
expect(document.cookie).toContain("portfolio_session=1");
|
||||
});
|
||||
|
||||
it("path: host is not the site domain (localhost) -> expiry is written without a Domain attribute", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
setCookie("_ga=1; Path=/");
|
||||
render(
|
||||
<>
|
||||
<OpenPreferencesButton />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
const cookieWrites = jest.spyOn(document, "cookie", "set");
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de cookies" }),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Recusar" }));
|
||||
|
||||
expect(cookieWrites).toHaveBeenCalledTimes(1);
|
||||
expect(cookieWrites.mock.calls[0][0]).toMatch(/^_ga=; Max-Age=0;/);
|
||||
expect(cookieWrites.mock.calls[0][0]).not.toContain("Domain=");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GoogleAnalytics: gtag, dataLayer e carregador", () => {
|
||||
it("path: dataLayer already exists -> the same array receives the commands", async () => {
|
||||
const user = userEvent.setup();
|
||||
const existing: IArguments[] = [];
|
||||
analyticsWindow.dataLayer = existing;
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "Aceitar" }));
|
||||
|
||||
expect(analyticsWindow.dataLayer).toBe(existing);
|
||||
expect(existing.length).toBeGreaterThan(0);
|
||||
expect(typeof analyticsWindow.gtag).toBe("function");
|
||||
});
|
||||
|
||||
it("path: a gtag already exists -> it is reused instead of being replaced", async () => {
|
||||
const user = userEvent.setup();
|
||||
const gtag = jest.fn();
|
||||
analyticsWindow.gtag = gtag;
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "Aceitar" }));
|
||||
|
||||
expect(analyticsWindow.gtag).toBe(gtag);
|
||||
expect(gtag).toHaveBeenCalledWith("js", expect.any(Date));
|
||||
expect(analyticsWindow.dataLayer).toBeDefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["denied", "denied"],
|
||||
["no choice yet", null],
|
||||
])(
|
||||
"path: %s -> a loader left by next/script is removed",
|
||||
async (_label, stored) => {
|
||||
if (stored) localStorage.setItem(CONSENT_KEY, stored);
|
||||
const loader = document.createElement("script");
|
||||
loader.id = "google-analytics-script";
|
||||
document.head.append(loader);
|
||||
|
||||
render(<GoogleAnalytics />);
|
||||
await act(async () => {});
|
||||
|
||||
expect(document.getElementById("google-analytics-script")).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("path: granted -> the loader stays", async () => {
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
const loader = document.createElement("script");
|
||||
loader.id = "google-analytics-script";
|
||||
document.head.append(loader);
|
||||
|
||||
render(<GoogleAnalytics />);
|
||||
await act(async () => {});
|
||||
|
||||
expect(document.getElementById("google-analytics-script")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GoogleAnalytics: foco e falha de gravação", () => {
|
||||
it("path: opener is not an HTML element (SVG) -> closing the bar has no element to refocus", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "denied");
|
||||
render(
|
||||
<>
|
||||
{/* biome-ignore lint/a11y/noNoninteractiveTabindex: elemento SVG de teste só para receber o foco */}
|
||||
<svg data-testid="svg-opener" tabIndex={0} />
|
||||
<GoogleAnalytics />
|
||||
</>,
|
||||
);
|
||||
await act(async () => {});
|
||||
const svg = screen.getByTestId("svg-opener");
|
||||
svg.focus();
|
||||
expect(document.activeElement).toBe(svg);
|
||||
|
||||
act(() => openCookiePreferences());
|
||||
await user.keyboard("{Escape}");
|
||||
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
expect(document.activeElement).not.toBe(svg);
|
||||
});
|
||||
|
||||
it("path: acceptance fails to persist, then succeeds -> the alert goes away and analytics turns on", async () => {
|
||||
const user = userEvent.setup();
|
||||
const setItem = jest
|
||||
.spyOn(Storage.prototype, "setItem")
|
||||
.mockImplementation(() => {
|
||||
throw new Error("storage blocked");
|
||||
});
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(await screen.findByRole("button", { name: "Aceitar" }));
|
||||
expect(
|
||||
screen.getByRole("alert", { name: "Falha ao salvar o consentimento" }),
|
||||
).toHaveTextContent(
|
||||
"Não foi possível salvar sua aceitação. Tente novamente para concluir.",
|
||||
);
|
||||
expect(screen.getByRole("dialog")).toBeVisible();
|
||||
expect(analyticsWindow.dataLayer).toBeUndefined();
|
||||
|
||||
setItem.mockRestore();
|
||||
await user.click(screen.getByRole("button", { name: "Aceitar" }));
|
||||
|
||||
await waitFor(() => expect(screen.queryByRole("alert")).toBeNull());
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("granted");
|
||||
expect(screen.getByTestId("google-analytics-script")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user