import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection"; vi.mock("@tanstack/react-virtual", () => ({ useVirtualizer: ({ count, estimateSize, }: { count: number; estimateSize: () => number; }) => { const size = estimateSize(); const items = Array.from({ length: count }, (_, i) => ({ index: i, start: i * size, size, key: i, })); return { getTotalSize: () => count * size, getVirtualItems: () => items, measureElement: vi.fn(), }; }, })); const defaultProps = { participants: [ { id: "1", name: "Player A", sport: { id: "s1", name: "NFL" } }, { id: "2", name: "Player B", sport: { id: "s2", name: "NBA" } }, { id: "3", name: "Player C", sport: { id: "s1", name: "NFL" } }, ], searchQuery: "", sportFilters: [] as string[], hideDrafted: false, hideIneligible: false, hideCompletedSports: false, uniqueSports: ["NBA", "NFL"], userDraftedSportNames: new Set(), draftedParticipantIds: new Set(), queue: [] as Array<{ id: string; participantId: string }>, eligibility: null, canPick: false, hasTeam: false, onSearchChange: vi.fn(), onSportFiltersChange: vi.fn(), onHideDraftedChange: vi.fn(), onHideIneligibleChange: vi.fn(), onHideCompletedSportsChange: vi.fn(), onMakePick: vi.fn(), onAddToQueue: vi.fn(), onRemoveFromQueue: vi.fn(), participantRanks: new Map([ ["1", { overallRank: 1, sportRank: 1 }], ["2", { overallRank: 2, sportRank: 1 }], ["3", { overallRank: 3, sportRank: 2 }], ]), }; // Both the mobile Sheet trigger and desktop Popover trigger are rendered in jsdom // (CSS breakpoints are not applied). Click the first one to open the Sheet. let user: ReturnType; function clickFirstSportFilterTrigger(name: string | RegExp) { return user.click(screen.getAllByRole("button", { name })[0]); } describe("AvailableParticipantsSection", () => { beforeEach(() => { vi.clearAllMocks(); user = userEvent.setup(); }); describe("Rendering", () => { it("renders all participants by name", () => { render(); expect(screen.getAllByText("Player A").length).toBeGreaterThan(0); expect(screen.getAllByText("Player B").length).toBeGreaterThan(0); expect(screen.getAllByText("Player C").length).toBeGreaterThan(0); }); it("renders search input", () => { render(); expect( screen.getByPlaceholderText("Search participants...") ).toBeInTheDocument(); }); it("renders the Show Drafted toggle", () => { render(); expect(screen.getByText("Show Drafted")).toBeInTheDocument(); }); it("renders Show Ineligible and Show drafted sports toggles when hasTeam=true", async () => { render( ); expect(screen.getByText("Show Ineligible")).toBeInTheDocument(); // Show drafted sports lives inside the sport filter dropdown await clickFirstSportFilterTrigger("Filter by sport: All Sports"); const dialog = screen.getByRole("dialog"); expect(within(dialog).getByText("Show drafted sports")).toBeInTheDocument(); }); it("does not render Show Ineligible when hasTeam=false", () => { render( ); expect(screen.queryByText("Show Ineligible")).not.toBeInTheDocument(); }); it("renders empty state when no participants", () => { render( ); expect(screen.getAllByText("No participants found.").length).toBeGreaterThan(0); }); }); describe("Sport Filter - trigger label", () => { it('shows "All Sports" in aria-label when no filters selected', () => { render(); // Both mobile and desktop triggers share the same label const triggers = screen.getAllByRole("button", { name: "Filter by sport: All Sports", }); expect(triggers.length).toBeGreaterThanOrEqual(1); }); it("shows sport name in aria-label when exactly one filter selected", () => { render( ); const triggers = screen.getAllByRole("button", { name: "Filter by sport: NFL", }); expect(triggers.length).toBeGreaterThanOrEqual(1); }); it("shows count in aria-label when multiple filters selected", () => { render( ); const triggers = screen.getAllByRole("button", { name: "Filter by sport: 2 sports selected", }); expect(triggers.length).toBeGreaterThanOrEqual(1); }); it("does not render active-filter badges", () => { render( ); expect(screen.queryByLabelText(/Remove .* filter/)).not.toBeInTheDocument(); }); }); describe("Sport Filter - sheet/popover", () => { it("opens and shows checkboxes for each sport", async () => { render(); await clickFirstSportFilterTrigger("Filter by sport: All Sports"); const dialog = screen.getByRole("dialog"); expect(within(dialog).getByText("NBA")).toBeInTheDocument(); expect(within(dialog).getByText("NFL")).toBeInTheDocument(); }); it("calls onSportFiltersChange with sport added when checkbox clicked", async () => { render(); await clickFirstSportFilterTrigger("Filter by sport: All Sports"); const dialog = screen.getByRole("dialog"); await user.click(within(dialog).getByLabelText("NFL")); expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith(["NFL"]); }); it("calls onSportFiltersChange with sport removed when unchecked", async () => { render( ); await clickFirstSportFilterTrigger("Filter by sport: 2 sports selected"); const dialog = screen.getByRole("dialog"); await user.click(within(dialog).getByLabelText("NFL")); expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith(["NBA"]); }); it('does not show "Reset" when no filters active', async () => { render(); await clickFirstSportFilterTrigger("Filter by sport: All Sports"); const dialog = screen.getByRole("dialog"); expect(within(dialog).queryByText("Reset")).not.toBeInTheDocument(); }); it('shows "Reset" button when filters exist', async () => { render( ); await clickFirstSportFilterTrigger("Filter by sport: NFL"); const dialog = screen.getByRole("dialog"); expect(within(dialog).getByText("Reset")).toBeInTheDocument(); }); it('calls onSportFiltersChange([]) and onHideCompletedSportsChange(false) when "Reset" clicked', async () => { render( ); await clickFirstSportFilterTrigger("Filter by sport: NFL"); const dialog = screen.getByRole("dialog"); await user.click(within(dialog).getByText("Reset")); expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith([]); expect(defaultProps.onHideCompletedSportsChange).toHaveBeenCalledWith(false); }); it("Done button closes the sheet", async () => { render(); await clickFirstSportFilterTrigger("Filter by sport: All Sports"); expect(screen.getByRole("dialog")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Done" })); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); }); });