feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
@@ -0,0 +1,220 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import type { ComponentProps } from "react";
|
||||
import GoogleAnalytics from "./GoogleAnalytics";
|
||||
|
||||
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";
|
||||
const GA_SRC = "https://www.googletagmanager.com/gtag/js?id=G-QTT9QT0T3S";
|
||||
|
||||
type AnalyticsWindow = Window & {
|
||||
dataLayer?: IArguments[];
|
||||
gtag?: (...args: unknown[]) => void;
|
||||
[GA_DISABLE_KEY]?: boolean;
|
||||
};
|
||||
|
||||
const analyticsWindow = window as AnalyticsWindow;
|
||||
|
||||
function consentUpdates(gtag: jest.Mock) {
|
||||
return gtag.mock.calls.filter(
|
||||
([command, action]) => command === "consent" && action === "update",
|
||||
);
|
||||
}
|
||||
|
||||
function setCookie(value: string) {
|
||||
// biome-ignore lint/suspicious/noDocumentCookie: JSDOM cookie setup is the behavior boundary exercised by revocation.
|
||||
document.cookie = value;
|
||||
}
|
||||
|
||||
describe("GoogleAnalytics consent", () => {
|
||||
beforeEach(() => {
|
||||
jest.replaceProperty(process.env, "NODE_ENV", "production");
|
||||
document.getElementById("google-analytics-script")?.remove();
|
||||
localStorage.clear();
|
||||
setCookie("_ga=; Max-Age=0; Path=/");
|
||||
setCookie("_ga_PORTFOLIO=; Max-Age=0; Path=/");
|
||||
setCookie("portfolio_session=; Max-Age=0; Path=/");
|
||||
analyticsWindow.dataLayer = undefined;
|
||||
analyticsWindow.gtag = undefined;
|
||||
analyticsWindow[GA_DISABLE_KEY] = undefined;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("waits for a decision before loading analytics", async () => {
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
expect(
|
||||
await screen.findByRole("button", { name: "Aceitar analytics" }),
|
||||
).toBeVisible();
|
||||
expect(screen.getByRole("dialog")).toHaveAccessibleDescription();
|
||||
expect(
|
||||
screen.queryByTestId("google-analytics-script"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("persists acceptance and enables analytics without advertising consent", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole("button", { name: "Aceitar analytics" }),
|
||||
);
|
||||
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("granted");
|
||||
expect(screen.getByTestId("google-analytics-script")).toHaveAttribute(
|
||||
"src",
|
||||
GA_SRC,
|
||||
);
|
||||
const dataLayer = analyticsWindow.dataLayer;
|
||||
expect(dataLayer).toHaveLength(4);
|
||||
expect(dataLayer?.every((entry) => !Array.isArray(entry))).toBe(true);
|
||||
expect(Array.from(dataLayer?.[0] ?? [])).toEqual([
|
||||
"consent",
|
||||
"default",
|
||||
{
|
||||
analytics_storage: "denied",
|
||||
ad_storage: "denied",
|
||||
ad_user_data: "denied",
|
||||
ad_personalization: "denied",
|
||||
},
|
||||
]);
|
||||
expect(Array.from(dataLayer?.[1] ?? [])).toEqual(["js", expect.any(Date)]);
|
||||
expect(Array.from(dataLayer?.[2] ?? [])).toEqual([
|
||||
"consent",
|
||||
"update",
|
||||
{
|
||||
analytics_storage: "granted",
|
||||
ad_storage: "denied",
|
||||
ad_user_data: "denied",
|
||||
ad_personalization: "denied",
|
||||
},
|
||||
]);
|
||||
expect(Array.from(dataLayer?.[3] ?? [])).toEqual([
|
||||
"config",
|
||||
"G-QTT9QT0T3S",
|
||||
{
|
||||
allow_ad_personalization_signals: false,
|
||||
anonymize_ip: true,
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Preferências de analytics" }),
|
||||
).not.toHaveFocus();
|
||||
});
|
||||
|
||||
it("persists refusal and keeps analytics absent after reload", async () => {
|
||||
const user = userEvent.setup();
|
||||
const firstRender = render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole("button", { name: "Recusar analytics" }),
|
||||
);
|
||||
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("denied");
|
||||
expect(
|
||||
screen.queryByTestId("google-analytics-script"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
firstRender.unmount();
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
expect(
|
||||
await screen.findByRole("button", { name: "Preferências de analytics" }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
screen.queryByTestId("google-analytics-script"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps preferences reachable and fully revokes analytics only", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "granted");
|
||||
setCookie("_ga=main; Path=/");
|
||||
setCookie("_ga_PORTFOLIO=property; Path=/");
|
||||
setCookie("portfolio_session=keep; Path=/");
|
||||
const gtag = jest.fn();
|
||||
analyticsWindow.gtag = gtag;
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await screen.findByTestId("google-analytics-script");
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Preferências de analytics" }),
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Revogar analytics" }));
|
||||
|
||||
expect(localStorage.getItem(CONSENT_KEY)).toBe("denied");
|
||||
expect(analyticsWindow[GA_DISABLE_KEY]).toBe(true);
|
||||
expect(consentUpdates(gtag)).toContainEqual([
|
||||
"consent",
|
||||
"update",
|
||||
{
|
||||
analytics_storage: "denied",
|
||||
ad_storage: "denied",
|
||||
ad_user_data: "denied",
|
||||
ad_personalization: "denied",
|
||||
},
|
||||
]);
|
||||
expect(document.cookie).not.toContain("_ga=");
|
||||
expect(document.cookie).not.toContain("_ga_PORTFOLIO=");
|
||||
expect(document.cookie).toContain("portfolio_session=keep");
|
||||
expect(
|
||||
screen.queryByTestId("google-analytics-script"),
|
||||
).not.toBeInTheDocument();
|
||||
const preferencesButton = screen.getByRole("button", {
|
||||
name: "Preferências de analytics",
|
||||
});
|
||||
expect(preferencesButton).toBeVisible();
|
||||
expect(preferencesButton).toHaveFocus();
|
||||
});
|
||||
|
||||
it("restores focus after closing or applying opened preferences", async () => {
|
||||
const user = userEvent.setup();
|
||||
localStorage.setItem(CONSENT_KEY, "denied");
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
const preferencesButton = await screen.findByRole("button", {
|
||||
name: "Preferências de analytics",
|
||||
});
|
||||
await user.click(preferencesButton);
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Fechar preferências" }),
|
||||
);
|
||||
expect(preferencesButton).toHaveFocus();
|
||||
|
||||
await user.click(preferencesButton);
|
||||
await user.click(screen.getByRole("button", { name: "Aceitar analytics" }));
|
||||
expect(preferencesButton).toHaveFocus();
|
||||
});
|
||||
|
||||
it("fails closed when local storage is unavailable", async () => {
|
||||
const user = userEvent.setup();
|
||||
jest.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
|
||||
throw new Error("storage blocked");
|
||||
});
|
||||
jest.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
|
||||
throw new Error("storage blocked");
|
||||
});
|
||||
render(<GoogleAnalytics />);
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole("button", { name: "Aceitar analytics" }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId("google-analytics-script"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user