diff --git a/apps/web/src/routes/initiatives/index.test.tsx b/apps/web/src/routes/initiatives/index.test.tsx new file mode 100644 index 0000000..8905cf5 --- /dev/null +++ b/apps/web/src/routes/initiatives/index.test.tsx @@ -0,0 +1,104 @@ +// @vitest-environment happy-dom +import "@testing-library/jest-dom/vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { vi, describe, it, expect, beforeEach } from "vitest"; + +// ── Mocks ──────────────────────────────────────────────────────────────────── + +vi.mock("@tanstack/react-router", () => ({ + createFileRoute: () => () => ({}), + useNavigate: () => vi.fn(), +})); + +vi.mock("@/lib/trpc", () => ({ + trpc: { + listProjects: { useQuery: () => ({ data: [] }) }, + useUtils: () => ({}), + }, +})); + +vi.mock("@/hooks", () => ({ + useLiveUpdates: vi.fn(), + INITIATIVE_LIST_RULES: {}, +})); + +vi.mock("@/components/InitiativeList", () => ({ + InitiativeList: () =>
, +})); + +vi.mock("@/components/CreateInitiativeDialog", () => ({ + CreateInitiativeDialog: () => null, +})); + +// ── Import after mocks ─────────────────────────────────────────────────────── + +import { DashboardPage } from "@/routes/initiatives/index"; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function renderPage() { + return render(); +} + +// ── Tests ──────────────────────────────────────────────────────────────────── + +describe("DashboardPage — statusFilter default and sessionStorage", () => { + beforeEach(() => { + sessionStorage.clear(); + vi.clearAllMocks(); + }); + + it("defaults to 'active' when sessionStorage is empty", () => { + renderPage(); + const select = screen.getByRole("combobox", { name: /status/i }) as HTMLSelectElement; + expect(select.value).toBe("active"); + }); + + it("displays the 'Active' option as selected on first render", () => { + renderPage(); + const select = screen.getByDisplayValue("Active") as HTMLSelectElement; + expect(select.value).toBe("active"); + }); + + it("reads 'completed' from sessionStorage as initial value", () => { + sessionStorage.setItem("initiatives.statusFilter", "completed"); + renderPage(); + const select = screen.getByDisplayValue("Completed") as HTMLSelectElement; + expect(select.value).toBe("completed"); + }); + + it("reads 'archived' from sessionStorage as initial value", () => { + sessionStorage.setItem("initiatives.statusFilter", "archived"); + renderPage(); + const select = screen.getByDisplayValue("Archived") as HTMLSelectElement; + expect(select.value).toBe("archived"); + }); + + it("falls back to 'active' when sessionStorage contains an invalid value", () => { + sessionStorage.setItem("initiatives.statusFilter", "bogus"); + renderPage(); + const select = screen.getByDisplayValue("Active") as HTMLSelectElement; + expect(select.value).toBe("active"); + }); + + it("writes the new value to sessionStorage when the filter changes", () => { + renderPage(); + const select = screen.getByDisplayValue("Active"); + fireEvent.change(select, { target: { value: "all" } }); + expect(sessionStorage.getItem("initiatives.statusFilter")).toBe("all"); + }); + + it("updates the select's displayed value after the filter changes", () => { + renderPage(); + const select = screen.getByDisplayValue("Active"); + fireEvent.change(select, { target: { value: "completed" } }); + expect((select as HTMLSelectElement).value).toBe("completed"); + }); + + it("writes 'archived' to sessionStorage when filter changes to archived", () => { + renderPage(); + const select = screen.getByDisplayValue("Active"); + fireEvent.change(select, { target: { value: "archived" } }); + expect(sessionStorage.getItem("initiatives.statusFilter")).toBe("archived"); + }); +}); diff --git a/apps/web/src/routes/initiatives/index.tsx b/apps/web/src/routes/initiatives/index.tsx index 140b7c3..0b6923f 100644 --- a/apps/web/src/routes/initiatives/index.tsx +++ b/apps/web/src/routes/initiatives/index.tsx @@ -21,11 +21,35 @@ const filterOptions: { value: StatusFilter; label: string }[] = [ { value: "archived", label: "Archived" }, ]; -function DashboardPage() { +export function DashboardPage() { const navigate = useNavigate(); - const [statusFilter, setStatusFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState(() => { + try { + const stored = sessionStorage.getItem("initiatives.statusFilter"); + if ( + stored === "all" || + stored === "active" || + stored === "completed" || + stored === "archived" + ) { + return stored; + } + } catch { + // sessionStorage unavailable (SSR, private-browsing restriction, etc.) + } + return "active"; + }); const [projectFilter, setProjectFilter] = useState("all"); const [createDialogOpen, setCreateDialogOpen] = useState(false); + + const handleStatusFilterChange = (value: StatusFilter) => { + try { + sessionStorage.setItem("initiatives.statusFilter", value); + } catch { + // ignore + } + setStatusFilter(value); + }; const projectsQuery = trpc.listProjects.useQuery(); // Single SSE stream for live updates @@ -55,9 +79,10 @@ function DashboardPage() { ))}