brackt/app/components/standings/__tests__/StandingsTable.test.tsx
Chris Parsons e9798263b5
Add rules page, rewrite how-to-play, and fix scoring display (#20)
- Add new /rules route with official league rules covering rosters,
  scoring, major-based QP scoring, tiebreakers, draft, and season rules
- Rewrite how-to-play page with a tutorial/marketing tone, highlighting
  the Fischer increment draft clock as a novel feature
- Add Rules link to navbar (desktop and mobile)
- Align QP values in both pages with DEFAULT_QP_VALUES in code
- Fix tiebreaker description to match placement-count logic in code
- Update all fantasy point displays from toFixed(1) to toFixed(2) across
  StandingsTable, TeamScoreBreakdown, and PointProgressionChart
- Update tests to match new two-decimal-place point display format

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 21:58:51 -08:00

332 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(<BrowserRouter>{ui}</BrowserRouter>);
}
/**
* 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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={[]} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText("No standings data available")).toBeInTheDocument();
});
});
describe("Rank Badges", () => {
it("should show trophy icon for 1st place", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText(/🏆.*1st/)).toBeInTheDocument();
});
it("should show silver medal icon for 2nd place", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText(/🥈.*2nd/)).toBeInTheDocument();
});
it("should show bronze medal icon for 3rd place", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={standings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
// 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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText("↑1")).toBeInTheDocument();
});
it("should show down arrow for negative rank change", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText("↓1")).toBeInTheDocument();
});
it("should not show indicator for no rank change", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
// 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(<StandingsTable standings={singleTeam} leagueId={mockLeagueId} seasonId={mockSeasonId} showPlacementBreakdown={true} />);
// 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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} showPlacementBreakdown={true} />);
// 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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} showPlacementBreakdown={false} />);
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(<StandingsTable standings={standings} leagueId={mockLeagueId} seasonId={mockSeasonId} showPlacementBreakdown={true} />);
expect(screen.getByText("None yet")).toBeInTheDocument();
});
});
describe("Participants Remaining", () => {
it("should show remaining count when participants are incomplete", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText("3 remaining")).toBeInTheDocument();
expect(screen.getByText("5 remaining")).toBeInTheDocument();
});
it("should show 'Complete' badge when all participants done", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
expect(screen.getByText("Complete")).toBeInTheDocument();
});
});
describe("Table Structure", () => {
it("should have correct table headers", () => {
renderWithRouter(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={tiedStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
// 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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} />);
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(<StandingsTable standings={mockStandings} leagueId={mockLeagueId} seasonId={mockSeasonId} showPlacementBreakdown={true} />);
const firstPlace = container.querySelector('[title*="1st place"]');
expect(firstPlace).toBeInTheDocument();
expect(firstPlace?.getAttribute("title")).toContain("2 1st place finishes");
});
});
});