import { AnchoredBottomBanner } from "./index";
import { act, render, screen } from "@testing-library/react";
// Mock next/link
jest.mock("next/link", () => {
// eslint-disable-next-line react/display-name
return ({ children, href, target, ...rest }: any) => (
{children}
);
});
describe("AnchoredBottomBanner", () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2026-05-19T12:00:00Z"));
});
afterEach(() => {
jest.useRealTimers();
});
it("renders with default anchorId", () => {
const { container } = render();
expect(container.querySelector("#anchored-banner")).toBeInTheDocument();
});
it("renders with custom anchorId", () => {
const { container } = render(
);
expect(container.querySelector("#custom-banner")).toBeInTheDocument();
});
it("renders ctaButtonLabel", () => {
render();
expect(screen.getByText("Shop now")).toBeInTheDocument();
});
it("renders ctaSuffixText", () => {
render();
expect(screen.getByText("Limited time")).toBeInTheDocument();
});
it("renders iconName", () => {
render();
expect(screen.getByText("local_offer")).toBeInTheDocument();
});
it("does not render icon when iconName is not provided", () => {
const { container } = render();
expect(
container.querySelector(".material-symbols-rounded")
).not.toBeInTheDocument();
});
it("uses ctaButtonLink in the link href", () => {
render();
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", "/offers");
});
it("defaults link href to # when ctaButtonLink is not provided", () => {
render();
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", "#");
});
it("uses ctaButtonTarget for link target", () => {
render();
const link = screen.getByRole("link");
expect(link).toHaveAttribute("target", "_blank");
});
it("defaults link target to _self", () => {
render();
const link = screen.getByRole("link");
expect(link).toHaveAttribute("target", "_self");
});
// Background colors
it("applies yellow background by default (no backgroundColor)", () => {
const { container } = render();
const div = container.querySelector(".bg-bg-fill-brand-accent");
expect(div).toBeInTheDocument();
});
it("applies navy background", () => {
const { container } = render(
);
expect(container.querySelector(".bg-bg-fill-inverse")).toBeInTheDocument();
});
it("applies green background", () => {
const { container } = render(
);
expect(container.querySelector(".bg-bg-fill-success")).toBeInTheDocument();
});
it("applies blue background", () => {
const { container } = render(
);
expect(
container.querySelector(".bg-bg-fill-brand-supporting")
).toBeInTheDocument();
});
it("applies purple background", () => {
const { container } = render(
);
expect(
container.querySelector(".bg-bg-fill-brand-tertiary")
).toBeInTheDocument();
});
it("applies white background", () => {
const { container } = render(
);
expect(container.querySelector(".bg-white")).toBeInTheDocument();
});
// Text colors
it("uses dark text for yellow/white/no background", () => {
const { container } = render(
);
expect(container.querySelector(".text-text-primary")).toBeInTheDocument();
});
it("uses white text for dark backgrounds", () => {
const { container } = render(
);
expect(container.querySelector(".text-white")).toBeInTheDocument();
});
// Box shadow
it("uses default shadow when boxShadow is not provided", () => {
const { container } = render();
const div = container.querySelector(
'[class*="shadow-[0_-4px_10px_rgba(0,0,0,0.1)]"]'
);
expect(div).toBeInTheDocument();
});
it("uses boxShadow value when provided", () => {
const { container } = render(
);
const div = container.querySelector(
'[class*="shadow-[0_-4px_10px_rgba(0,0,0,0.1)]"]'
);
expect(div).not.toBeInTheDocument();
});
// Countdown timer
it("shows countdown when timer is enabled and within range", () => {
const end = new Date("2026-05-19T13:00:00Z").toISOString();
render(
);
// 1 hour remaining: 01H : 00M : 00
expect(screen.getByText(/01H : 00M : 00/)).toBeInTheDocument();
});
it("does not show countdown when timer is disabled", () => {
const end = new Date("2026-05-19T13:00:00Z").toISOString();
render();
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
});
it("does not show countdown before start time", () => {
const start = new Date("2026-05-19T14:00:00Z").toISOString();
const end = new Date("2026-05-19T15:00:00Z").toISOString();
render(
);
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
});
it("does not show countdown after end time", () => {
const end = new Date("2026-05-19T11:00:00Z").toISOString();
render(
);
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
});
it("updates countdown every second", () => {
const end = new Date("2026-05-19T12:00:10Z").toISOString();
render(
);
expect(screen.getByText(/00H : 00M : 10/)).toBeInTheDocument();
act(() => {
jest.advanceTimersByTime(1000);
});
expect(screen.getByText(/00H : 00M : 09/)).toBeInTheDocument();
});
it("clears interval when countdown reaches zero", () => {
const end = new Date("2026-05-19T12:00:01Z").toISOString();
render(
);
act(() => {
jest.advanceTimersByTime(2000);
});
// After expiry, countdown should not show
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
});
it("handles invalid countdownEndDateTime gracefully", () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {
/* noop */
});
render(
);
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
consoleSpy.mockRestore();
});
it("handles invalid countdownStartDateTime gracefully", () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {
/* noop */
});
const end = new Date("2026-05-19T13:00:00Z").toISOString();
render(
);
expect(screen.queryByText(/H :/)).not.toBeInTheDocument();
consoleSpy.mockRestore();
});
it("logs error when start is after end", () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {
/* noop */
});
const start = new Date("2026-05-19T15:00:00Z").toISOString();
const end = new Date("2026-05-19T13:00:00Z").toISOString();
render(
);
expect(consoleSpy).toHaveBeenCalledWith(
"Invalid countdown range: start must be before end",
expect.any(Object)
);
consoleSpy.mockRestore();
});
});