// @vitest-environment jsdom import { describe, it, expect, vi, afterEach } from "vitest"; import { render, screen, fireEvent, cleanup, within, act } from "@testing-library/react"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (k: string, o?: Record) => o && "from" in o ? `${k}:${String(o.from)}-${String(o.to)}` : k, i18n: { language: "en" }, }), }; }); vi.mock("../../lib/history", () => ({ useBackToClose: () => {} })); import YearPicker from "./YearPicker"; afterEach(cleanup); // Eight years over three decades, 1987 and 2014 without titles. const YEARS = [ { year: 2026, count: 3 }, { year: 2024, count: 9 }, { year: 2023, count: 1 }, { year: 2021, count: 4 }, { year: 2020, count: 2 }, { year: 2015, count: 5 }, { year: 2013, count: 7 }, { year: 1985, count: 6 }, ]; const open = () => fireEvent.click(screen.getByRole("button", { name: /sidebar\.year\.more/ })); describe("YearPicker", () => { it("shows the six newest years as chips, the selected one kept beside them", () => { render( {}} />); const chips = screen.getAllByRole("button", { pressed: false }).map((b) => b.textContent); expect(chips.slice(0, 6)).toEqual(["20263", "20249", "20231", "20214", "20202", "20155"]); expect(screen.getByRole("button", { pressed: true }).textContent).toBe("19856"); }); it("lays every year out by decade in the dialog; a year without titles is not a choice", () => { render( {}} />); open(); const dialog = screen.getByRole("dialog"); const rows = within(dialog).getAllByRole("group", { name: /sidebar\.year\.decade/ }); expect(rows.map((r) => r.getAttribute("aria-label"))).toEqual([ "sidebar.year.decade:2020-2029", "sidebar.year.decade:2010-2019", "sidebar.year.decade:1980-1989", ]); const in1980s = within(rows[2]!) .getAllByRole("button") .map((b) => b.dataset.year); expect(in1980s).toEqual(["1985"]); expect(rows[1]!.querySelector('[data-year="2014"]')).toBeNull(); }); it("sorted by count, the busiest year comes first", () => { render( {}} />); open(); fireEvent.click(screen.getByRole("button", { name: "sidebar.year.sortCount" })); const order = within(screen.getByRole("dialog")) .getAllByRole("button") .map((b) => b.dataset.year) .filter(Boolean); expect(order.slice(0, 3)).toEqual(["2024", "2013", "1985"]); }); it("a pick in the dialog names the year and closes it; the selected year again clears it", () => { const onPick = vi.fn(); const { rerender } = render(); open(); fireEvent.click(screen.getByRole("dialog").querySelector('[data-year="1985"]')!); expect(onPick).toHaveBeenLastCalledWith(1985); expect(screen.queryByRole("dialog")).toBeNull(); rerender(); fireEvent.click(screen.getByRole("button", { pressed: true })); expect(onPick).toHaveBeenLastCalledWith(undefined); }); it("offers More… while a year with titles is not among the chips, the kept selection aside", () => { // Seven years, and a selected year with no titles kept beside the six newest: 2013 is hidden. render( {}} />); expect(screen.getByRole("button", { name: "sidebar.year.more" })).toBeTruthy(); }); it("More… is a plain chip: the ellipsis says a dialog follows, no chevron", () => { render( {}} />); expect( screen.getByRole("button", { name: "sidebar.year.more" }).querySelector("svg"), ).toBeNull(); }); it("clearing the kept older year hands the focus on, not to ", () => { const { rerender } = render( {}} />); const kept = screen.getByRole("button", { pressed: true }); kept.focus(); fireEvent.click(kept); rerender( {}} />); expect(document.activeElement).toBe(screen.getByRole("button", { name: "sidebar.year.more" })); }); it("a decade row is named once: its caption is not read again", () => { render( {}} />); open(); const row = screen.getAllByRole("group", { name: /sidebar\.year\.decade/ })[0]!; expect(row.firstElementChild?.getAttribute("aria-hidden")).toBe("true"); }); it("a cell carries its count under the year, compact", () => { render( {}} />); open(); const cell = screen.getByRole("dialog").querySelector('[data-year="2025"]')!; expect(cell.textContent).toBe("202517.2K"); }); it("the arrows walk the cells: along a decade, then down to the next one", () => { render( {}} />); open(); const dialog = screen.getByRole("dialog"); dialog.querySelector('[data-year="2020"]')!.focus(); press("ArrowRight"); expect((document.activeElement as HTMLElement).dataset.year).toBe("2021"); press("ArrowDown"); expect((document.activeElement as HTMLElement).dataset.year).toMatch(/^201/); }); }); /** A keydown where a real one starts: on whatever holds focus, bubbling to the window listener. */ function press(key: string): void { act(() => { (document.activeElement ?? document.body).dispatchEvent( new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }), ); }); }