import { describe, it, expect } from "vitest"; import { render, screen } from "@testing-library/react"; import { BrowserRouter } from "react-router"; import { StandingsTable } from "../StandingsTable"; import { type TeamStanding } from "~/models/standings"; // Helper to wrap component with router context function renderWithRouter(ui: React.ReactElement) { return render({ui}); } /** * StandingsTable Component Tests * Phase 4.1: Test standings display with tiebreakers and placement breakdown * Phase 4.3: Added tests for clickable team links */ describe("StandingsTable", () => { const mockLeagueId = "league-123"; const mockSeasonId = "season-456"; const mockStandings: TeamStanding[] = [ { teamId: "team1", teamName: "Champions United", totalPoints: 250.5, currentRank: 1, previousRank: 2, rankChange: 1, // Moved up placementCounts: { first: 2, second: 1, third: 0, fourth: 1, fifth: 0, sixth: 0, seventh: 0, eighth: 1, }, participantsRemaining: 3, calculatedAt: new Date(), }, { teamId: "team2", teamName: "Second Place Squad", totalPoints: 245.0, currentRank: 2, previousRank: 1, rankChange: -1, // Moved down placementCounts: { first: 1, second: 2, third: 1, fourth: 0, fifth: 1, sixth: 0, seventh: 0, eighth: 0, }, participantsRemaining: 0, calculatedAt: new Date(), }, { teamId: "team3", teamName: "Bronze Warriors", totalPoints: 200.0, currentRank: 3, previousRank: 3, rankChange: 0, // No change placementCounts: { first: 0, second: 1, third: 2, fourth: 1, fifth: 0, sixth: 0, seventh: 0, eighth: 0, }, participantsRemaining: 5, calculatedAt: new Date(), }, ]; describe("Basic Rendering", () => { it("should render standings table with all teams", () => { renderWithRouter(); expect(screen.getByText("Champions United")).toBeInTheDocument(); expect(screen.getByText("Second Place Squad")).toBeInTheDocument(); expect(screen.getByText("Bronze Warriors")).toBeInTheDocument(); }); it("should display total points for each team", () => { renderWithRouter(); expect(screen.getByText("250.5")).toBeInTheDocument(); expect(screen.getByText("245.0")).toBeInTheDocument(); expect(screen.getByText("200.0")).toBeInTheDocument(); }); it("should show empty state when no standings", () => { renderWithRouter(); expect(screen.getByText("No standings data available")).toBeInTheDocument(); }); }); describe("Rank Badges", () => { it("should show trophy icon for 1st place", () => { renderWithRouter(); expect(screen.getByText(/🏆.*1st/)).toBeInTheDocument(); }); it("should show silver medal icon for 2nd place", () => { renderWithRouter(); expect(screen.getByText(/🥈.*2nd/)).toBeInTheDocument(); }); it("should show bronze medal icon for 3rd place", () => { renderWithRouter(); expect(screen.getByText(/🥉.*3rd/)).toBeInTheDocument(); }); it("should show plain rank for 4th place and below", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], currentRank: 4, teamName: "Fourth Place", }, ]; renderWithRouter(); // Should not have emoji, just the number const badge = screen.getByText("4"); expect(badge).toBeInTheDocument(); }); }); describe("Movement Indicators", () => { it("should show up arrow for positive rank change", () => { renderWithRouter(); expect(screen.getByText("↑1")).toBeInTheDocument(); }); it("should show down arrow for negative rank change", () => { renderWithRouter(); expect(screen.getByText("↓1")).toBeInTheDocument(); }); it("should not show indicator for no rank change", () => { renderWithRouter(); // Third team has no change, so no arrow const arrows = screen.queryAllByText(/↑|↓/); expect(arrows).toHaveLength(2); // Only first two teams }); }); describe("Placement Breakdown", () => { it("should display placement counts", () => { const singleTeam: TeamStanding[] = [ { teamId: "team1", teamName: "Test Team", totalPoints: 250, currentRank: 1, previousRank: null, rankChange: 0, placementCounts: { first: 2, second: 1, third: 0, fourth: 1, fifth: 0, sixth: 0, seventh: 0, eighth: 1, }, participantsRemaining: 0, calculatedAt: new Date(), }, ]; renderWithRouter(); // Test Team: 2 firsts, 1 second, 1 fourth, 1 eighth expect(screen.getByText("1st×2")).toBeInTheDocument(); expect(screen.getByText("2nd×1")).toBeInTheDocument(); expect(screen.getByText("4th×1")).toBeInTheDocument(); expect(screen.getByText("8th×1")).toBeInTheDocument(); }); it("should only show non-zero placement counts", () => { renderWithRouter(); // Should not show "3rd×0" for Champions United const placements = screen.queryByText("3rd×0"); expect(placements).not.toBeInTheDocument(); }); it("should hide placement breakdown when disabled", () => { renderWithRouter(); expect(screen.queryByText("1st×2")).not.toBeInTheDocument(); }); it("should show 'None yet' when no placements", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], placementCounts: { first: 0, second: 0, third: 0, fourth: 0, fifth: 0, sixth: 0, seventh: 0, eighth: 0, }, }, ]; renderWithRouter(); expect(screen.getByText("None yet")).toBeInTheDocument(); }); }); describe("Participants Remaining", () => { it("should show remaining count when participants are incomplete", () => { renderWithRouter(); expect(screen.getByText("3 remaining")).toBeInTheDocument(); expect(screen.getByText("5 remaining")).toBeInTheDocument(); }); it("should show 'Complete' badge when all participants done", () => { renderWithRouter(); expect(screen.getByText("Complete")).toBeInTheDocument(); }); }); describe("Table Structure", () => { it("should have correct table headers", () => { renderWithRouter(); expect(screen.getByText("Rank")).toBeInTheDocument(); expect(screen.getByText("Team")).toBeInTheDocument(); expect(screen.getByText("Points")).toBeInTheDocument(); expect(screen.getByText("Placements")).toBeInTheDocument(); expect(screen.getByText("Remaining")).toBeInTheDocument(); }); it("should render teams in order", () => { const { container } = renderWithRouter(); const rows = container.querySelectorAll("tbody tr"); expect(rows).toHaveLength(3); // First row should be Champions United (rank 1) expect(rows[0].textContent).toContain("Champions United"); // Second row should be Second Place Squad (rank 2) expect(rows[1].textContent).toContain("Second Place Squad"); // Third row should be Bronze Warriors (rank 3) expect(rows[2].textContent).toContain("Bronze Warriors"); }); }); describe("Tied Ranks", () => { it("should handle teams with same rank", () => { const tiedStandings: TeamStanding[] = [ { ...mockStandings[0], currentRank: 1, teamName: "Team A", }, { ...mockStandings[1], currentRank: 1, teamName: "Team B", }, { ...mockStandings[2], currentRank: 3, // Skips 2 teamName: "Team C", }, ]; renderWithRouter(); // Both teams should show rank 1 const firstPlaceBadges = screen.getAllByText(/🏆.*1st/); expect(firstPlaceBadges).toHaveLength(2); // Third team should show rank 3 (not 2) expect(screen.getByText(/🥉.*3rd/)).toBeInTheDocument(); }); }); describe("Accessibility", () => { it("should have title attributes for movement indicators", () => { const { container } = renderWithRouter(); const upArrow = container.querySelector('[title*="Up"]'); expect(upArrow).toBeInTheDocument(); expect(upArrow?.getAttribute("title")).toContain("Up 1 place"); const downArrow = container.querySelector('[title*="Down"]'); expect(downArrow).toBeInTheDocument(); expect(downArrow?.getAttribute("title")).toContain("Down 1 place"); }); it("should have title attributes for placement counts", () => { const { container } = renderWithRouter(); const firstPlace = container.querySelector('[title*="1st place"]'); expect(firstPlace).toBeInTheDocument(); expect(firstPlace?.getAttribute("title")).toContain("2 1st place finishes"); }); }); });