* Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout - mlb.ts: use streakCode alone (the API already returns the full string like "W3"); appending streakNumber was doubling the digit, causing "L33" display - Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W") - RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows have data, so pre-season or stats-free sports don't show blank columns - Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden) so all data stays visible without horizontal scroll on small screens; STK and W/L remain on the primary row; reduce min-w from 740px to 360px https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF * Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow - mlb.ts: drop redundant `?? undefined` after optional chain; document that streakCode is the full string (e.g. "W3") and streakNumber is unused - RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component level (it only depends on a prop, not on individual row data) - Remove non-functional `truncate`/`min-w-0` from team name cell — truncation requires table-layout:fixed which we don't use; team names size naturally - Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows inline for soccer on all viewports, hidden only for non-soccer (which has the secondary sub-row) - Add comment explaining totalCols counts hidden-on-mobile columns for colSpan - Add missing test for GB column hiding when no rows have gamesBack data https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF --------- Co-authored-by: Claude <noreply@anthropic.com>
370 lines
14 KiB
TypeScript
370 lines
14 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { RegularSeasonStandings } from "../RegularSeasonStandings";
|
|
|
|
function makeRow(overrides: Partial<{
|
|
id: string;
|
|
participantId: string;
|
|
wins: number;
|
|
losses: number;
|
|
otLosses: number | null;
|
|
ties: number | null;
|
|
tablePoints: number | null;
|
|
goalsFor: number | null;
|
|
goalsAgainst: number | null;
|
|
goalDifference: number | null;
|
|
gamesPlayed: number;
|
|
winPct: string | null;
|
|
gamesBack: string | null;
|
|
conference: string | null;
|
|
division: string | null;
|
|
conferenceRank: number | null;
|
|
divisionRank: number | null;
|
|
leagueRank: number | null;
|
|
streak: string | null;
|
|
lastTen: string | null;
|
|
homeRecord: string | null;
|
|
awayRecord: string | null;
|
|
syncedAt: string | null;
|
|
participant: { id: string; name: string; shortName?: string | null };
|
|
}> = {}) {
|
|
return {
|
|
id: overrides.id ?? "row-1",
|
|
participantId: overrides.participantId ?? "p-1",
|
|
wins: overrides.wins ?? 40,
|
|
losses: overrides.losses ?? 28,
|
|
otLosses: overrides.otLosses ?? null,
|
|
ties: overrides.ties ?? null,
|
|
tablePoints: overrides.tablePoints ?? null,
|
|
goalsFor: overrides.goalsFor ?? null,
|
|
goalsAgainst: overrides.goalsAgainst ?? null,
|
|
goalDifference: overrides.goalDifference ?? null,
|
|
gamesPlayed: overrides.gamesPlayed ?? 68,
|
|
winPct: overrides.winPct ?? "0.5882",
|
|
gamesBack: overrides.gamesBack ?? null,
|
|
conference: overrides.conference ?? null,
|
|
division: overrides.division ?? null,
|
|
conferenceRank: overrides.conferenceRank ?? null,
|
|
divisionRank: overrides.divisionRank ?? null,
|
|
leagueRank: overrides.leagueRank ?? 1,
|
|
streak: overrides.streak ?? null,
|
|
lastTen: overrides.lastTen ?? null,
|
|
homeRecord: overrides.homeRecord ?? null,
|
|
awayRecord: overrides.awayRecord ?? null,
|
|
syncedAt: overrides.syncedAt ?? null,
|
|
participant: overrides.participant ?? { id: "p-1", name: "Boston Celtics" },
|
|
};
|
|
}
|
|
|
|
const NO_OWNERSHIPS = {};
|
|
const NO_USER_IDS: string[] = [];
|
|
|
|
describe("RegularSeasonStandings", () => {
|
|
it("renders nothing when standings array is empty", () => {
|
|
const { container } = render(
|
|
<RegularSeasonStandings
|
|
standings={[]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it("renders team names", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[
|
|
makeRow({ participant: { id: "p-1", name: "Boston Celtics" } }),
|
|
makeRow({ id: "row-2", participantId: "p-2", participant: { id: "p-2", name: "Toronto Raptors" } }),
|
|
]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.getByText("Boston Celtics")).toBeInTheDocument();
|
|
expect(screen.getByText("Toronto Raptors")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows OTL column when showOtLosses=true", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ otLosses: 5 })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
showOtLosses={true}
|
|
/>
|
|
);
|
|
expect(screen.getByText("OTL")).toBeInTheDocument();
|
|
});
|
|
|
|
it("hides OTL column when showOtLosses=false (default)", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow()]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.queryByText("OTL")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides STK column when no rows have streak data", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ streak: null }), makeRow({ id: "row-2", participantId: "p-2", streak: null, participant: { id: "p-2", name: "Toronto Raptors" } })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.queryByText("STK")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows STK column when at least one row has streak data", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ streak: "W3" }), makeRow({ id: "row-2", participantId: "p-2", streak: null, participant: { id: "p-2", name: "Toronto Raptors" } })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.getByText("STK")).toBeInTheDocument();
|
|
});
|
|
|
|
it("hides L10 column when no rows have lastTen data", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ lastTen: null })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.queryByText("L10")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides GB column when no rows have gamesBack data", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ gamesBack: null })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.queryByText("GB")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows soccer table columns when showSoccerTable=true", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[
|
|
makeRow({
|
|
participant: { id: "p-1", name: "Arsenal" },
|
|
gamesPlayed: 30,
|
|
wins: 21,
|
|
ties: 6,
|
|
losses: 3,
|
|
goalsFor: 72,
|
|
goalsAgainst: 25,
|
|
goalDifference: 47,
|
|
tablePoints: 69,
|
|
}),
|
|
]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
showSoccerTable={true}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText("D")).toBeInTheDocument();
|
|
expect(screen.getByText("GF")).toBeInTheDocument();
|
|
expect(screen.getByText("GA")).toBeInTheDocument();
|
|
expect(screen.getByText("GD")).toBeInTheDocument();
|
|
expect(screen.getByText("PTS")).toBeInTheDocument();
|
|
expect(screen.getByText("Arsenal")).toBeInTheDocument();
|
|
expect(screen.getByText("69")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders conference headings and inline division labels when present", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[
|
|
makeRow({ conference: "Eastern", division: "Atlantic", conferenceRank: 1, participant: { id: "p-1", name: "Boston Celtics" } }),
|
|
makeRow({ id: "row-2", participantId: "p-2", conference: "Western", division: "Pacific", conferenceRank: 1, participant: { id: "p-2", name: "LA Lakers" } }),
|
|
]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
// Conference group headings are rendered as <h3>
|
|
expect(screen.getByText(/Eastern Conference/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/Western Conference/i)).toBeInTheDocument();
|
|
// Division is now shown as an inline label per row (not a section header)
|
|
expect(screen.getAllByText(/Atlantic/i).length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText(/Pacific/i).length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("shows ownership badge for drafted teams", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ participantId: "p-1" })]}
|
|
teamOwnerships={{
|
|
"p-1": { teamName: "Team Alpha", ownerName: "Alice", teamId: "t-1" },
|
|
}}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
expect(screen.getByText("Alice")).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not show badge for undrafted teams", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ participantId: "p-1" })]}
|
|
teamOwnerships={{}}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
// No owner badge should be present
|
|
expect(screen.queryByText(/Alice/i)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("applies different styling for user's own team", () => {
|
|
const { container } = render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ participantId: "p-1" })]}
|
|
teamOwnerships={{}}
|
|
userParticipantIds={["p-1"]}
|
|
/>
|
|
);
|
|
// The user's row should have a primary styling class
|
|
const userRow = container.querySelector("tr.bg-primary\\/5");
|
|
expect(userRow).not.toBeNull();
|
|
});
|
|
|
|
it("renders streak with correct color class for wins", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ streak: "W5" })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
const streakEl = screen.getByText("W5");
|
|
expect(streakEl.className).toContain("emerald");
|
|
});
|
|
|
|
it("renders streak with destructive class for losses", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={[makeRow({ streak: "L3" })]}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
/>
|
|
);
|
|
const streakEl = screen.getByText("L3");
|
|
expect(streakEl.className).toContain("destructive");
|
|
});
|
|
});
|
|
|
|
// ─── mlb-divisions display mode ───────────────────────────────────────────────
|
|
|
|
function makeAlDivisionRows() {
|
|
return [
|
|
// AL East — division winner (divisionRank=1) + 2 WC candidates (divisionRank=2,3)
|
|
makeRow({ id: "r1", participantId: "p1", conference: "AL", division: "AL East", divisionRank: 1, conferenceRank: 1, participant: { id: "p1", name: "Baltimore Orioles" } }),
|
|
makeRow({ id: "r2", participantId: "p2", conference: "AL", division: "AL East", divisionRank: 2, conferenceRank: 4, participant: { id: "p2", name: "Boston Red Sox" } }),
|
|
// AL Central — division winner
|
|
makeRow({ id: "r3", participantId: "p3", conference: "AL", division: "AL Central", divisionRank: 1, conferenceRank: 2, participant: { id: "p3", name: "Cleveland Guardians" } }),
|
|
makeRow({ id: "r4", participantId: "p4", conference: "AL", division: "AL Central", divisionRank: 2, conferenceRank: 5, participant: { id: "p4", name: "Minnesota Twins" } }),
|
|
// AL West — division winner
|
|
makeRow({ id: "r5", participantId: "p5", conference: "AL", division: "AL West", divisionRank: 1, conferenceRank: 3, participant: { id: "p5", name: "Houston Astros" } }),
|
|
makeRow({ id: "r6", participantId: "p6", conference: "AL", division: "AL West", divisionRank: 2, conferenceRank: 6, participant: { id: "p6", name: "Seattle Mariners" } }),
|
|
// NL East
|
|
makeRow({ id: "r7", participantId: "p7", conference: "NL", division: "NL East", divisionRank: 1, conferenceRank: 1, participant: { id: "p7", name: "Philadelphia Phillies" } }),
|
|
makeRow({ id: "r8", participantId: "p8", conference: "NL", division: "NL East", divisionRank: 2, conferenceRank: 4, participant: { id: "p8", name: "Atlanta Braves" } }),
|
|
// NL Central
|
|
makeRow({ id: "r9", participantId: "p9", conference: "NL", division: "NL Central", divisionRank: 1, conferenceRank: 2, participant: { id: "p9", name: "Milwaukee Brewers" } }),
|
|
makeRow({ id: "r10", participantId: "p10", conference: "NL", division: "NL Central", divisionRank: 2, conferenceRank: 5, participant: { id: "p10", name: "Chicago Cubs" } }),
|
|
// NL West
|
|
makeRow({ id: "r11", participantId: "p11", conference: "NL", division: "NL West", divisionRank: 1, conferenceRank: 3, participant: { id: "p11", name: "Los Angeles Dodgers" } }),
|
|
makeRow({ id: "r12", participantId: "p12", conference: "NL", division: "NL West", divisionRank: 2, conferenceRank: 6, participant: { id: "p12", name: "San Diego Padres" } }),
|
|
];
|
|
}
|
|
|
|
describe("RegularSeasonStandings mlb-divisions mode", () => {
|
|
it("renders 'AL League' and 'NL League' headings (not Conference)", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={makeAlDivisionRows()}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
displayMode="mlb-divisions"
|
|
/>
|
|
);
|
|
expect(screen.getByText(/AL League/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/NL League/i)).toBeInTheDocument();
|
|
expect(screen.queryByText(/AL Conference/i)).not.toBeInTheDocument();
|
|
expect(screen.queryByText(/NL Conference/i)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders division section headings", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={makeAlDivisionRows()}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
displayMode="mlb-divisions"
|
|
/>
|
|
);
|
|
expect(screen.getByText("AL East Division")).toBeInTheDocument();
|
|
expect(screen.getByText("AL Central Division")).toBeInTheDocument();
|
|
expect(screen.getByText("AL West Division")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Wild Card section heading", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={makeAlDivisionRows()}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
displayMode="mlb-divisions"
|
|
/>
|
|
);
|
|
expect(screen.getAllByText("Wild Card").length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("division section contains only the division winner (divisionRank=1)", () => {
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={makeAlDivisionRows()}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
displayMode="mlb-divisions"
|
|
/>
|
|
);
|
|
// Division winners should be rendered
|
|
expect(screen.getByText("Baltimore Orioles")).toBeInTheDocument();
|
|
expect(screen.getByText("Cleveland Guardians")).toBeInTheDocument();
|
|
expect(screen.getByText("Houston Astros")).toBeInTheDocument();
|
|
// Wild card teams (rank > 1) also rendered in Wild Card section
|
|
expect(screen.getByText("Boston Red Sox")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows 'Playoff Line' after 3 teams in the Wild Card section", () => {
|
|
// Add enough WC teams to trigger the playoff line (need >3 in the WC section)
|
|
const standings = [
|
|
...makeAlDivisionRows(),
|
|
makeRow({ id: "r13", participantId: "p13", conference: "AL", division: "AL East", divisionRank: 3, conferenceRank: 7, participant: { id: "p13", name: "Tampa Bay Rays" } }),
|
|
makeRow({ id: "r14", participantId: "p14", conference: "AL", division: "AL Central", divisionRank: 3, conferenceRank: 8, participant: { id: "p14", name: "Detroit Tigers" } }),
|
|
];
|
|
render(
|
|
<RegularSeasonStandings
|
|
standings={standings}
|
|
teamOwnerships={NO_OWNERSHIPS}
|
|
userParticipantIds={NO_USER_IDS}
|
|
displayMode="mlb-divisions"
|
|
/>
|
|
);
|
|
expect(screen.getAllByText(/Playoff Line/i).length).toBeGreaterThan(0);
|
|
});
|
|
});
|