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}); } 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, 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, 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, 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.50")).toBeInTheDocument(); expect(screen.getByText("245.00")).toBeInTheDocument(); expect(screen.getByText("200.00")).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(); 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(); // Only first two teams have rank changes; use \d to avoid matching sort-header arrows const arrows = screen.queryAllByText(/[↑↓]\d/); expect(arrows).toHaveLength(2); }); }); describe("7-Day Change Column", () => { it("should show dash when sevenDayPointChange is not present", () => { renderWithRouter(); // All mock standings lack sevenDayPointChange, so all rows show — const dashes = screen.getAllByText("—"); expect(dashes.length).toBeGreaterThanOrEqual(3); }); it("should display positive point change in green with + prefix", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], sevenDayPointChange: 12.5 }, ]; renderWithRouter(); expect(screen.getByText("+12.50 pts")).toBeInTheDocument(); }); it("should display negative point change with no + prefix", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], sevenDayPointChange: -3.25 }, ]; renderWithRouter(); expect(screen.getByText("-3.25 pts")).toBeInTheDocument(); }); it("should show rank change within the 7-day column", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], sevenDayPointChange: 10, rankChange: 2 }, ]; renderWithRouter(); expect(screen.getByText("↑2 rank")).toBeInTheDocument(); }); it("should show no rank change indicator when rank is unchanged", () => { const standings: TeamStanding[] = [ { ...mockStandings[0], sevenDayPointChange: 5, rankChange: 0 }, ]; renderWithRouter(); expect(screen.getByText("— rank")).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("7-Day Change")).toBeInTheDocument(); expect(screen.getByText("Remaining")).toBeInTheDocument(); }); it("should render teams in order by rank", () => { const { container } = renderWithRouter(); const rows = container.querySelectorAll("tbody tr"); expect(rows).toHaveLength(3); expect(rows[0].textContent).toContain("Champions United"); expect(rows[1].textContent).toContain("Second Place Squad"); 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, teamName: "Team C" }, ]; renderWithRouter(); const firstPlaceBadges = screen.getAllByText(/🏆.*1st/); expect(firstPlaceBadges).toHaveLength(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"); }); }); });