import "@testing-library/jest-dom";
import React from "react";
import CookieBanner from "./index";
import { act, fireEvent, render, screen } from "@testing-library/react";
// Mock cookie utilities
const mockGetCookie = jest.fn();
const mockSetCookie = jest.fn();
jest.mock("../../../utils/cookie", () => ({
getCookie: (...args: any[]) => mockGetCookie(...args),
setCookie: (...args: any[]) => mockSetCookie(...args),
}));
jest.mock("@shared/components/material-icon", () => ({
MaterialIcon: ({ name }: any) => (
{name}
),
}));
jest.mock("@shared/contentful/blocks/button", () => ({
Button: (props: any) => (
),
}));
describe("CookieBanner", () => {
const defaultContent = {
entryName: "Cookie Notice",
anchorId: "cookie-banner",
title: "Cookies",
richText:
We use cookies to improve your experience.
,
};
beforeEach(() => {
jest.useFakeTimers();
mockGetCookie.mockReset();
mockSetCookie.mockReset();
// Default: cookie not set (banner should show)
mockGetCookie.mockReturnValue(null);
// Reset body classes
document.body.classList.remove("cookie-banner-visible");
// Reset CSS custom property
document.documentElement.style.removeProperty("--cookie-banner-height");
});
afterEach(() => {
jest.useRealTimers();
});
describe("cookie management", () => {
it("shows the banner when cookieBannerClosed cookie is not set", () => {
mockGetCookie.mockReturnValue(null);
render();
expect(
screen.getByLabelText("Cookie usage notification")
).toBeInTheDocument();
});
it("hides the banner when cookieBannerClosed cookie is true", () => {
mockGetCookie.mockReturnValue("true");
const { container } = render();
expect(container.firstChild).toBeNull();
});
it("calls getCookie with cookieBannerClosed key", () => {
render();
expect(mockGetCookie).toHaveBeenCalledWith("cookieBannerClosed");
});
it("sets cookieBannerClosed cookie on close", () => {
mockGetCookie.mockReturnValue(null);
render();
fireEvent.click(screen.getByTestId("cookie-button"));
expect(mockSetCookie).toHaveBeenCalledWith(
"cookieBannerClosed",
true,
expect.objectContaining({ expires: expect.any(Date) })
);
});
it("sets cookie with correct expiration (30 days)", () => {
mockGetCookie.mockReturnValue(null);
render();
const now = Date.now();
fireEvent.click(screen.getByTestId("cookie-button"));
const callArgs = mockSetCookie.mock.calls[0];
const expirationDate = callArgs[2].expires;
// 43200 minutes * 60 * 1000 = 30 days in milliseconds
const expectedMs = 43200 * 60 * 1000;
const diff = expirationDate.getTime() - now;
expect(diff).toBeGreaterThanOrEqual(expectedMs - 1000);
expect(diff).toBeLessThanOrEqual(expectedMs + 1000);
});
});
describe("rendering", () => {
it("renders with fixed positioning", () => {
render();
const banner = screen.getByLabelText("Cookie usage notification");
expect(banner.className).toContain("fixed");
});
it("renders the rich text content", () => {
render();
expect(
screen.getByText("We use cookies to improve your experience.")
).toBeInTheDocument();
});
it("does not render rich text when richText is falsy", () => {
const content = { ...defaultContent, richText: null as any };
render();
const banner = screen.getByLabelText("Cookie usage notification");
// Banner is shown but no rich text inside
expect(banner.querySelector(".mx-auto")).toBeNull();
});
it("renders close button with close icon", () => {
render();
expect(screen.getByTestId("icon-close")).toBeInTheDocument();
});
it("has correct z-index class", () => {
render();
const banner = screen.getByLabelText("Cookie usage notification");
expect(banner.className).toContain("z-[1000]");
});
it("renders with correct max width", () => {
render();
const banner = screen.getByLabelText("Cookie usage notification");
expect(banner.className).toContain("max-w-[350px]");
});
it("renders with correct positioning styles", () => {
render();
const banner = screen.getByLabelText("Cookie usage notification");
expect(banner.style.bottom).toBe("0px");
expect(banner.style.right).toBe("20px");
});
it("has id cookie-banner", () => {
render();
expect(document.getElementById("cookie-banner")).toBeInTheDocument();
});
});
describe("close behavior", () => {
it("hides banner on close button click", () => {
mockGetCookie.mockReturnValue(null);
render();
expect(
screen.getByLabelText("Cookie usage notification")
).toBeInTheDocument();
fireEvent.click(screen.getByTestId("cookie-button"));
expect(
screen.queryByLabelText("Cookie usage notification")
).not.toBeInTheDocument();
});
});
describe("body class management", () => {
it("adds cookie-banner-visible class to body when visible", () => {
mockGetCookie.mockReturnValue(null);
render();
expect(document.body.classList.contains("cookie-banner-visible")).toBe(
true
);
});
it("removes cookie-banner-visible class when banner is hidden", () => {
mockGetCookie.mockReturnValue(null);
render();
fireEvent.click(screen.getByTestId("cookie-button"));
expect(document.body.classList.contains("cookie-banner-visible")).toBe(
false
);
});
it("removes cookie-banner-visible class on unmount", () => {
mockGetCookie.mockReturnValue(null);
const { unmount } = render();
expect(document.body.classList.contains("cookie-banner-visible")).toBe(
true
);
unmount();
expect(document.body.classList.contains("cookie-banner-visible")).toBe(
false
);
});
});
describe("sticky footer detection", () => {
it("uses larger margin when anchored-banner element exists", () => {
const anchoredBanner = document.createElement("div");
anchoredBanner.id = "anchored-banner";
document.body.appendChild(anchoredBanner);
mockGetCookie.mockReturnValue(null);
render();
const banner = screen.getByLabelText("Cookie usage notification");
// marginBottom = 14 * 4 = 56px when anchored-banner exists
expect(banner.style.marginBottom).toBe("56px");
document.body.removeChild(anchoredBanner);
});
it("uses smaller margin when anchored-banner does not exist", () => {
mockGetCookie.mockReturnValue(null);
render();
const banner = screen.getByLabelText("Cookie usage notification");
// marginBottom = 3 * 4 = 12px when no anchored-banner
expect(banner.style.marginBottom).toBe("12px");
});
});
describe("CSS custom property for height", () => {
it("calculates and sets --cookie-banner-height property", () => {
mockGetCookie.mockReturnValue(null);
render();
act(() => {
jest.advanceTimersByTime(350);
});
// The property should be set (value depends on getBoundingClientRect mock)
// In jsdom, getBoundingClientRect returns zeros, so spaceFromBottom = innerHeight - 0
const value = document.documentElement.style.getPropertyValue(
"--cookie-banner-height"
);
// Should be set (even though value may be 0px in jsdom)
expect(value).toBeDefined();
});
it("removes --cookie-banner-height on unmount", () => {
mockGetCookie.mockReturnValue(null);
const { unmount } = render();
act(() => {
jest.advanceTimersByTime(350);
});
unmount();
const value = document.documentElement.style.getPropertyValue(
"--cookie-banner-height"
);
expect(value).toBe("");
});
it("recalculates height on window resize", () => {
mockGetCookie.mockReturnValue(null);
render();
act(() => {
jest.advanceTimersByTime(350);
});
act(() => {
window.dispatchEvent(new Event("resize"));
jest.advanceTimersByTime(200);
});
// Should not throw and property remains set
const value = document.documentElement.style.getPropertyValue(
"--cookie-banner-height"
);
expect(value).toBeDefined();
});
});
});