@@ -198,9 +260,7 @@ export function TeamScoreBreakdown({
{allPicks.map((pick) => (
- {numTeams > 0
- ? `${pick.round}.${String(pick.pickNumber - (pick.round - 1) * numTeams).padStart(2, "0")}`
- : `#${pick.pickNumber}`}
+ {pickLabel(pick, numTeams)}
(#{pick.pickNumber})
@@ -215,31 +275,10 @@ export function TeamScoreBreakdown({
{pick.participant.name}
- {pick.isComplete && !pick.isPartialScore ? (
- (pick.finalPosition ?? 0) === 0 ? (
- Did Not Score
- ) : (
-
- )
- ) : (
- Pending
- )}
+
- {pick.isComplete && !pick.isPartialScore ? (
-
- {pick.points > 0 ? pick.points.toFixed(2) : "0.00"}
-
- ) : (
-
- {pick.points.toFixed(2)}
- {pick.projectedPoints !== null && (
-
- {pick.projectedPoints.toFixed(2)}
-
- )}
-
- )}
+
))}
@@ -248,6 +287,70 @@ export function TeamScoreBreakdown({
+ {/* All picks — mobile cards (no horizontal scroll, all fields visible) */}
+
+ {/* Sort control */}
+
+
+ Sort by
+
+
+
+
+
+ {allPicks.map((pick) => (
+
+
+
+
+ {pickLabel(pick, numTeams)}
+ (#{pick.pickNumber})
+
+
+
+
+
{pick.participant.name}
+
+ {pick.participant.sport}
+
+
+
+
+
+ ))}
+
+
{/* Navigation */}
{
/>
);
- expect(screen.getByText("1.01")).toBeInTheDocument();
- expect(screen.getByText("1.02")).toBeInTheDocument();
- expect(screen.getByText("2.01")).toBeInTheDocument();
+ // Pick labels render in both the desktop table and mobile cards.
+ expect(screen.getAllByText("1.01").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("1.02").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("2.01").length).toBeGreaterThanOrEqual(1);
});
it("should show overall pick number in parentheses", () => {
@@ -176,9 +177,9 @@ describe("TeamScoreBreakdown", () => {
/>
);
- expect(screen.getByText("(#1)")).toBeInTheDocument();
- expect(screen.getByText("(#2)")).toBeInTheDocument();
- expect(screen.getByText("(#3)")).toBeInTheDocument();
+ expect(screen.getAllByText("(#1)").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("(#2)").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("(#3)").length).toBeGreaterThanOrEqual(1);
});
it("should fall back to #pickNumber when numTeams is 0", () => {
@@ -192,9 +193,9 @@ describe("TeamScoreBreakdown", () => {
/>
);
- expect(screen.getByText("#1")).toBeInTheDocument();
- expect(screen.getByText("#2")).toBeInTheDocument();
- expect(screen.getByText("#3")).toBeInTheDocument();
+ expect(screen.getAllByText("#1").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("#2").length).toBeGreaterThanOrEqual(1);
+ expect(screen.getAllByText("#3").length).toBeGreaterThanOrEqual(1);
});
});
@@ -210,9 +211,10 @@ describe("TeamScoreBreakdown", () => {
/>
);
- expect(screen.getByText("Team A")).toBeInTheDocument();
- expect(screen.getByText("Driver B")).toBeInTheDocument();
- expect(screen.getByText("Team C")).toBeInTheDocument();
+ const table = within(screen.getByRole("table"));
+ expect(table.getByText("Team A")).toBeInTheDocument();
+ expect(table.getByText("Driver B")).toBeInTheDocument();
+ expect(table.getByText("Team C")).toBeInTheDocument();
});
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[0]).toHaveAttribute(
"href",
`/leagues/${mockLeagueId}/sports-seasons/ss-nfl`
);
- const f1Link = screen.getByRole("link", { name: "F1" });
+ const f1Link = table.getByRole("link", { name: "F1" });
expect(f1Link).toHaveAttribute(
"href",
`/leagues/${mockLeagueId}/sports-seasons/ss-f1`
@@ -251,8 +254,9 @@ describe("TeamScoreBreakdown", () => {
/>
);
- expect(screen.getByText("1st")).toBeInTheDocument();
- expect(screen.getByText("3rd")).toBeInTheDocument();
+ const table = within(screen.getByRole("table"));
+ expect(table.getByText("1st")).toBeInTheDocument();
+ expect(table.getByText("3rd")).toBeInTheDocument();
});
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", () => {
@@ -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", () => {
@@ -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", () => {
@@ -333,8 +340,9 @@ describe("TeamScoreBreakdown", () => {
);
// Incomplete participant shows 0.00 (actual) and 25.00 (EV) in the row
- expect(screen.getByText("0.00")).toBeInTheDocument();
- expect(screen.getByText("25.00")).toBeInTheDocument();
+ const table = within(screen.getByRole("table"));
+ expect(table.getByText("0.00")).toBeInTheDocument();
+ expect(table.getByText("25.00")).toBeInTheDocument();
});
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,96 @@ 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))
+ );
+ }
+
+ async function selectSortColumn(user: ReturnType, label: string) {
+ await user.click(screen.getByRole("combobox", { name: /sort by/i }));
+ await user.click(screen.getByRole("option", { name: label }));
+ }
+
+ it("renders a card per pick with all fields visible", () => {
+ renderWithRouter(
+
+ );
+
+ 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(
+
+ );
+
+ 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(
+
+ );
+
+ // picks: Team A=100, Driver B=50, Team C=0; points defaults to descending
+ await selectSortColumn(user, "Points");
+ expect(getCardOrder()).toEqual(["Team A", "Driver B", "Team C"]);
+ });
+
+ it("toggles sort direction with the direction button", async () => {
+ const user = userEvent.setup();
+ renderWithRouter(
+
+ );
+
+ await selectSortColumn(user, "Points"); // desc
+ // While descending, the toggle's action is to sort ascending.
+ await user.click(screen.getByRole("button", { name: /sort ascending/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", () => {
it("should render without standing prop", () => {
renderWithRouter(
diff --git a/app/test/setup.ts b/app/test/setup.ts
index f39c595..adf939d 100644
--- a/app/test/setup.ts
+++ b/app/test/setup.ts
@@ -29,6 +29,14 @@ process.env.NODE_ENV = 'test';
// jsdom does not implement scrollTo on elements
HTMLElement.prototype.scrollTo = vi.fn();
+// jsdom does not implement these APIs that Radix UI relies on for pointer
+// interactions and option positioning (Select, DropdownMenu, etc.). Without
+// them, opening a Radix Select in a test throws.
+HTMLElement.prototype.scrollIntoView = vi.fn();
+HTMLElement.prototype.hasPointerCapture = vi.fn(() => false);
+HTMLElement.prototype.setPointerCapture = vi.fn();
+HTMLElement.prototype.releasePointerCapture = vi.fn();
+
// Mock BetterAuth
vi.mock('~/lib/auth.server', () => ({
auth: {