Make team score breakdown page mobile-friendly
The team breakdown page rendered a 5-column table that overflowed on phones, forcing horizontal scroll. Add a responsive card layout for mobile that shows every field (pick #, sport, participant, position, points) with no horizontal scroll, while keeping the existing sortable table on desktop (md+). - Header now stacks on mobile (flex-col -> sm:flex-row). - Mobile renders one card per pick with a compact sort control (field select + asc/desc toggle) instead of clickable column headers. - Extract shared PositionCell / PointsValue / pickLabel helpers so the table and cards stay in sync. - Update tests to scope table assertions and cover the mobile view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EGQoRPjRycNSjwLrUwuKhw
This commit is contained in:
parent
f078eab492
commit
215c37350b
2 changed files with 242 additions and 54 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";
|
import { ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
import { Card, CardContent } from "~/components/ui/card";
|
import { Card, CardContent } from "~/components/ui/card";
|
||||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "~/components/ui/table";
|
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "~/components/ui/table";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
@ -97,6 +98,54 @@ function sortPicks(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Pick = TeamScoreBreakdownProps["breakdown"]["picks"][number];
|
||||||
|
|
||||||
|
const SORT_OPTIONS: Array<{ value: SortColumn; label: string }> = [
|
||||||
|
{ value: "pick", label: "Pick #" },
|
||||||
|
{ value: "sport", label: "Sport" },
|
||||||
|
{ value: "participant", label: "Participant" },
|
||||||
|
{ value: "points", label: "Points" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function pickLabel(pick: Pick, numTeams: number): string {
|
||||||
|
return numTeams > 0
|
||||||
|
? `${pick.round}.${String(pick.pickNumber - (pick.round - 1) * numTeams).padStart(2, "0")}`
|
||||||
|
: `#${pick.pickNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Position badge shared by the desktop table and the mobile cards. */
|
||||||
|
function PositionCell({ pick }: { pick: Pick }) {
|
||||||
|
if (pick.isComplete && !pick.isPartialScore) {
|
||||||
|
return (pick.finalPosition ?? 0) === 0 ? (
|
||||||
|
<Badge variant="secondary">Did Not Score</Badge>
|
||||||
|
) : (
|
||||||
|
<PlacementBadge position={pick.finalPosition ?? 0} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <Badge variant="outline">Pending</Badge>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Points value (actual, with projected underneath when incomplete). */
|
||||||
|
function PointsValue({ pick }: { pick: Pick }) {
|
||||||
|
if (pick.isComplete && !pick.isPartialScore) {
|
||||||
|
return (
|
||||||
|
<span className="font-semibold">
|
||||||
|
{pick.points > 0 ? pick.points.toFixed(2) : "0.00"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-end">
|
||||||
|
<span className="font-semibold">{pick.points.toFixed(2)}</span>
|
||||||
|
{pick.projectedPoints !== null && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{pick.projectedPoints.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display detailed team score breakdown with all drafted participants
|
* Display detailed team score breakdown with all drafted participants
|
||||||
* Phase 4.3: Team breakdown pages
|
* Phase 4.3: Team breakdown pages
|
||||||
|
|
@ -130,17 +179,23 @@ export function TeamScoreBreakdown({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile sort control: picking a column applies its default direction.
|
||||||
|
function handleSortColumn(column: SortColumn) {
|
||||||
|
setSortColumn(column);
|
||||||
|
setSortDirection(column === "points" ? "desc" : "asc");
|
||||||
|
}
|
||||||
|
|
||||||
const allPicks = sortPicks(breakdown.picks, sortColumn, sortDirection);
|
const allPicks = sortPicks(breakdown.picks, sortColumn, sortDirection);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">{breakdown.team.name}</h1>
|
<h1 className="text-2xl sm:text-3xl font-bold">{breakdown.team.name}</h1>
|
||||||
<p className="text-muted-foreground mt-1">Team Score Breakdown</p>
|
<p className="text-muted-foreground mt-1">Team Score Breakdown</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-left sm:text-right">
|
||||||
<div className="text-4xl font-bold text-primary">
|
<div className="text-4xl font-bold text-primary">
|
||||||
{breakdown.actualPoints.toFixed(2)}
|
{breakdown.actualPoints.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -164,8 +219,8 @@ export function TeamScoreBreakdown({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* All picks — single flat table */}
|
{/* All picks — desktop table */}
|
||||||
<Card>
|
<Card className="hidden md:block">
|
||||||
<CardContent className="pt-4">
|
<CardContent className="pt-4">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|
@ -198,9 +253,7 @@ export function TeamScoreBreakdown({
|
||||||
{allPicks.map((pick) => (
|
{allPicks.map((pick) => (
|
||||||
<TableRow key={pick.pickNumber}>
|
<TableRow key={pick.pickNumber}>
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground">
|
||||||
{numTeams > 0
|
{pickLabel(pick, numTeams)}
|
||||||
? `${pick.round}.${String(pick.pickNumber - (pick.round - 1) * numTeams).padStart(2, "0")}`
|
|
||||||
: `#${pick.pickNumber}`}
|
|
||||||
<span className="text-xs ml-1">(#{pick.pickNumber})</span>
|
<span className="text-xs ml-1">(#{pick.pickNumber})</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
|
@ -215,31 +268,12 @@ export function TeamScoreBreakdown({
|
||||||
{pick.participant.name}
|
{pick.participant.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{pick.isComplete && !pick.isPartialScore ? (
|
<PositionCell pick={pick} />
|
||||||
(pick.finalPosition ?? 0) === 0 ? (
|
|
||||||
<Badge variant="secondary">Did Not Score</Badge>
|
|
||||||
) : (
|
|
||||||
<PlacementBadge position={pick.finalPosition ?? 0} />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Badge variant="outline">Pending</Badge>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{pick.isComplete && !pick.isPartialScore ? (
|
|
||||||
<span className="font-semibold">
|
|
||||||
{pick.points > 0 ? pick.points.toFixed(2) : "0.00"}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col items-end">
|
<div className="flex flex-col items-end">
|
||||||
<span className="font-semibold">{pick.points.toFixed(2)}</span>
|
<PointsValue pick={pick} />
|
||||||
{pick.projectedPoints !== null && (
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{pick.projectedPoints.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|
@ -248,6 +282,67 @@ export function TeamScoreBreakdown({
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* All picks — mobile cards (no horizontal scroll, all fields visible) */}
|
||||||
|
<div className="md:hidden space-y-3" data-testid="picks-mobile">
|
||||||
|
{/* Sort control */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label htmlFor="picks-sort" className="text-sm text-muted-foreground shrink-0">
|
||||||
|
Sort by
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="picks-sort"
|
||||||
|
value={sortColumn}
|
||||||
|
onChange={(e) => handleSortColumn(e.target.value as SortColumn)}
|
||||||
|
className="flex-1 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||||
|
>
|
||||||
|
{SORT_OPTIONS.map((opt) => (
|
||||||
|
<option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSortDirection(sortDirection === "asc" ? "desc" : "asc")}
|
||||||
|
aria-label={`Sort ${sortDirection === "asc" ? "ascending" : "descending"}`}
|
||||||
|
>
|
||||||
|
{sortDirection === "asc" ? (
|
||||||
|
<ArrowUp className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{allPicks.map((pick) => (
|
||||||
|
<Card key={pick.pickNumber} className="py-0" data-testid="pick-card">
|
||||||
|
<CardContent className="p-3 space-y-2">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{pickLabel(pick, numTeams)}
|
||||||
|
<span className="text-xs ml-1">(#{pick.pickNumber})</span>
|
||||||
|
</div>
|
||||||
|
<PositionCell pick={pick} />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="font-medium break-words">{pick.participant.name}</div>
|
||||||
|
<Link
|
||||||
|
to={`/leagues/${leagueId}/sports-seasons/${pick.participant.sportsSeasonId}`}
|
||||||
|
className="text-sm font-medium hover:underline text-primary"
|
||||||
|
>
|
||||||
|
{pick.participant.sport}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-baseline justify-between border-t border-border/30 pt-2">
|
||||||
|
<span className="text-sm text-muted-foreground">Points</span>
|
||||||
|
<PointsValue pick={pick} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<div className="flex justify-between pt-4">
|
<div className="flex justify-between pt-4">
|
||||||
<Link
|
<Link
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen, within } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { BrowserRouter } from "react-router";
|
import { BrowserRouter } from "react-router";
|
||||||
import { TeamScoreBreakdown } from "../TeamScoreBreakdown";
|
import { TeamScoreBreakdown } from "../TeamScoreBreakdown";
|
||||||
|
|
@ -160,9 +160,10 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("1.01")).toBeInTheDocument();
|
// Pick labels render in both the desktop table and mobile cards.
|
||||||
expect(screen.getByText("1.02")).toBeInTheDocument();
|
expect(screen.getAllByText("1.01").length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText("2.01")).toBeInTheDocument();
|
expect(screen.getAllByText("1.02").length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(screen.getAllByText("2.01").length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show overall pick number in parentheses", () => {
|
it("should show overall pick number in parentheses", () => {
|
||||||
|
|
@ -176,9 +177,9 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("(#1)")).toBeInTheDocument();
|
expect(screen.getAllByText("(#1)").length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText("(#2)")).toBeInTheDocument();
|
expect(screen.getAllByText("(#2)").length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText("(#3)")).toBeInTheDocument();
|
expect(screen.getAllByText("(#3)").length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fall back to #pickNumber when numTeams is 0", () => {
|
it("should fall back to #pickNumber when numTeams is 0", () => {
|
||||||
|
|
@ -192,9 +193,9 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("#1")).toBeInTheDocument();
|
expect(screen.getAllByText("#1").length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText("#2")).toBeInTheDocument();
|
expect(screen.getAllByText("#2").length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText("#3")).toBeInTheDocument();
|
expect(screen.getAllByText("#3").length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -210,9 +211,10 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("Team A")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
expect(screen.getByText("Driver B")).toBeInTheDocument();
|
expect(table.getByText("Team A")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Team C")).toBeInTheDocument();
|
expect(table.getByText("Driver B")).toBeInTheDocument();
|
||||||
|
expect(table.getByText("Team C")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show sport names as links to sport season pages", () => {
|
it("should show sport names as links to sport season pages", () => {
|
||||||
|
|
@ -226,14 +228,15 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const nflLinks = screen.getAllByRole("link", { name: "NFL" });
|
const table = within(screen.getByRole("table"));
|
||||||
|
const nflLinks = table.getAllByRole("link", { name: "NFL" });
|
||||||
expect(nflLinks.length).toBeGreaterThanOrEqual(1);
|
expect(nflLinks.length).toBeGreaterThanOrEqual(1);
|
||||||
expect(nflLinks[0]).toHaveAttribute(
|
expect(nflLinks[0]).toHaveAttribute(
|
||||||
"href",
|
"href",
|
||||||
`/leagues/${mockLeagueId}/sports-seasons/ss-nfl`
|
`/leagues/${mockLeagueId}/sports-seasons/ss-nfl`
|
||||||
);
|
);
|
||||||
|
|
||||||
const f1Link = screen.getByRole("link", { name: "F1" });
|
const f1Link = table.getByRole("link", { name: "F1" });
|
||||||
expect(f1Link).toHaveAttribute(
|
expect(f1Link).toHaveAttribute(
|
||||||
"href",
|
"href",
|
||||||
`/leagues/${mockLeagueId}/sports-seasons/ss-f1`
|
`/leagues/${mockLeagueId}/sports-seasons/ss-f1`
|
||||||
|
|
@ -251,8 +254,9 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("1st")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
expect(screen.getByText("3rd")).toBeInTheDocument();
|
expect(table.getByText("1st")).toBeInTheDocument();
|
||||||
|
expect(table.getByText("3rd")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show Pending badge for incomplete participants", () => {
|
it("should show Pending badge for incomplete participants", () => {
|
||||||
|
|
@ -266,7 +270,8 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("Pending")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
|
expect(table.getByText("Pending")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show Did Not Score badge when finalPosition is 0", () => {
|
it("should show Did Not Score badge when finalPosition is 0", () => {
|
||||||
|
|
@ -291,7 +296,8 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("Did Not Score")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
|
expect(table.getByText("Did Not Score")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show Did Not Score badge when isComplete but finalPosition is null", () => {
|
it("should show Did Not Score badge when isComplete but finalPosition is null", () => {
|
||||||
|
|
@ -318,7 +324,8 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("Did Not Score")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
|
expect(table.getByText("Did Not Score")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show projected EV for incomplete participants", () => {
|
it("should show projected EV for incomplete participants", () => {
|
||||||
|
|
@ -333,8 +340,9 @@ describe("TeamScoreBreakdown", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Incomplete participant shows 0.00 (actual) and 25.00 (EV) in the row
|
// Incomplete participant shows 0.00 (actual) and 25.00 (EV) in the row
|
||||||
expect(screen.getByText("0.00")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
expect(screen.getByText("25.00")).toBeInTheDocument();
|
expect(table.getByText("0.00")).toBeInTheDocument();
|
||||||
|
expect(table.getByText("25.00")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display 0.00 for completed picks with zero points", () => {
|
it("should display 0.00 for completed picks with zero points", () => {
|
||||||
|
|
@ -353,7 +361,8 @@ describe("TeamScoreBreakdown", () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("0.00")).toBeInTheDocument();
|
const table = within(screen.getByRole("table"));
|
||||||
|
expect(table.getByText("0.00")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -501,6 +510,90 @@ describe("TeamScoreBreakdown", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Mobile view", () => {
|
||||||
|
function getCardOrder() {
|
||||||
|
const mobile = screen.getByTestId("picks-mobile");
|
||||||
|
const cards = within(mobile).getAllByTestId("pick-card");
|
||||||
|
return cards.map((card) =>
|
||||||
|
["Team A", "Driver B", "Team C"].find((n) => card.textContent?.includes(n))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("renders a card per pick with all fields visible", () => {
|
||||||
|
renderWithRouter(
|
||||||
|
<TeamScoreBreakdown
|
||||||
|
leagueId={mockLeagueId}
|
||||||
|
seasonId={mockSeasonId}
|
||||||
|
numTeams={numTeams}
|
||||||
|
breakdown={mockBreakdown}
|
||||||
|
standing={mockStanding}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const mobile = within(screen.getByTestId("picks-mobile"));
|
||||||
|
expect(mobile.getAllByTestId("pick-card")).toHaveLength(3);
|
||||||
|
// Participant names, sports, positions all visible without hiding data
|
||||||
|
expect(mobile.getByText("Team A")).toBeInTheDocument();
|
||||||
|
expect(mobile.getByText("Driver B")).toBeInTheDocument();
|
||||||
|
expect(mobile.getByText("Team C")).toBeInTheDocument();
|
||||||
|
expect(mobile.getByText("1st")).toBeInTheDocument();
|
||||||
|
expect(mobile.getByText("Pending")).toBeInTheDocument();
|
||||||
|
expect(mobile.getByRole("link", { name: "F1" })).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
`/leagues/${mockLeagueId}/sports-seasons/ss-f1`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to pick order", () => {
|
||||||
|
renderWithRouter(
|
||||||
|
<TeamScoreBreakdown
|
||||||
|
leagueId={mockLeagueId}
|
||||||
|
seasonId={mockSeasonId}
|
||||||
|
numTeams={numTeams}
|
||||||
|
breakdown={mockBreakdown}
|
||||||
|
standing={mockStanding}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getCardOrder()).toEqual(["Team A", "Driver B", "Team C"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reorders cards when sorting by points (desc) via the select", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderWithRouter(
|
||||||
|
<TeamScoreBreakdown
|
||||||
|
leagueId={mockLeagueId}
|
||||||
|
seasonId={mockSeasonId}
|
||||||
|
numTeams={numTeams}
|
||||||
|
breakdown={mockBreakdown}
|
||||||
|
standing={mockStanding}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// picks: Team A=100, Driver B=50, Team C=0; points defaults to descending
|
||||||
|
await user.selectOptions(screen.getByLabelText(/sort by/i), "points");
|
||||||
|
expect(getCardOrder()).toEqual(["Team A", "Driver B", "Team C"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles sort direction with the direction button", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderWithRouter(
|
||||||
|
<TeamScoreBreakdown
|
||||||
|
leagueId={mockLeagueId}
|
||||||
|
seasonId={mockSeasonId}
|
||||||
|
numTeams={numTeams}
|
||||||
|
breakdown={mockBreakdown}
|
||||||
|
standing={mockStanding}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.selectOptions(screen.getByLabelText(/sort by/i), "points"); // desc
|
||||||
|
await user.click(screen.getByRole("button", { name: /sort descending/i }));
|
||||||
|
// now ascending: Team C (0), Driver B (50), Team A (100)
|
||||||
|
expect(getCardOrder()).toEqual(["Team C", "Driver B", "Team A"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Without Standing Data", () => {
|
describe("Without Standing Data", () => {
|
||||||
it("should render without standing prop", () => {
|
it("should render without standing prop", () => {
|
||||||
renderWithRouter(
|
renderWithRouter(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue