brackt/app/components/league/__tests__/StandingsPreview.test.tsx
Chris Parsons b960d39be3
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m23s
🚀 Deploy / 🧪 Test (push) Successful in 3m9s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m20s
🚀 Deploy / 🐳 Build (push) Successful in 1m9s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m23s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (push) Successful in 12s
Add projections to full standings; clean up change indicators
Surface projected final points on the full standings page and restructure
the rank/point change indicators so columns align cleanly on desktop and
mobile.

- StatHelpers: stacked label/value/delta columns with a per-row reservable
  delta line; merge rank/point indicators into a single DeltaBadge;
  parameterize StatDivider height.
- StandingsPreview: opt-in `showProjected` column (projected points only),
  gated on participants remaining; reserve the delta line only when a row
  actually moved (no stray em-dashes).
- Full standings page: pass projected data + showProjected (hidden when the
  season is complete).
- League home: fetch via getSevenDayStandingsChange so the preview shows the
  same 7-day rank/point changes as the full standings page.
- LeagueRow: top-align stats and restore h-8 dividers so the shared-component
  changes don't alter the league list rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:56:15 -07:00

156 lines
5.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router";
import { StandingsPreview, type StandingsPreviewEntry } from "../StandingsPreview";
function renderWithRouter(ui: React.ReactElement) {
return render(<BrowserRouter>{ui}</BrowserRouter>);
}
const baseEntry: StandingsPreviewEntry = {
teamId: "team-1",
teamName: "Lightning Wolves",
ownerName: "alice",
displayRank: 1,
currentRank: 1,
points: 2500,
href: "/leagues/1/standings/1/teams/team-1",
};
describe("StandingsPreview", () => {
describe("Avatar rendering", () => {
it("renders an img for uploaded avatar type", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
avatarType: "uploaded",
logoUrl: "https://example.com/team-logo.png",
};
renderWithRouter(<StandingsPreview entries={[entry]} />);
const img = screen.getByAltText("Lightning Wolves");
expect(img.tagName).toBe("IMG");
});
it("renders an SVG for flag avatar type", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
avatarType: "flag",
flagConfig: { pattern: "triband-h", colors: ["#ff0000", "#ffffff", "#0000ff"] },
};
const { container } = renderWithRouter(<StandingsPreview entries={[entry]} />);
expect(container.querySelector("svg")).toBeInTheDocument();
});
it("renders owner image when avatarType is owner with image ownerAvatarData", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
avatarType: "owner",
ownerAvatarData: { type: "image", url: "https://example.com/owner.png" },
};
renderWithRouter(<StandingsPreview entries={[entry]} />);
const img = screen.getByAltText("Lightning Wolves");
expect(img.tagName).toBe("IMG");
});
it("falls back to a generated flag when no avatar data is provided", () => {
const entry: StandingsPreviewEntry = { ...baseEntry, avatarType: undefined };
const { container } = renderWithRouter(<StandingsPreview entries={[entry]} />);
expect(container.querySelector("svg")).toBeInTheDocument();
});
});
describe("Basic rendering", () => {
it("renders team names for all entries", () => {
const entries: StandingsPreviewEntry[] = [
{ ...baseEntry, teamId: "t1", teamName: "Team Alpha", displayRank: 1 },
{ ...baseEntry, teamId: "t2", teamName: "Team Beta", displayRank: 2 },
];
renderWithRouter(<StandingsPreview entries={entries} />);
// Use selector to target only the <p> element — the team name also appears
// in the SVG <title> for accessibility, causing multiple matches otherwise.
expect(screen.getByText("Team Alpha", { selector: "p" })).toBeInTheDocument();
expect(screen.getByText("Team Beta", { selector: "p" })).toBeInTheDocument();
});
it("renders owner name below team name", () => {
renderWithRouter(<StandingsPreview entries={[baseEntry]} />);
expect(screen.getByText("alice")).toBeInTheDocument();
});
it("renders rounded points", () => {
renderWithRouter(<StandingsPreview entries={[{ ...baseEntry, points: 1500 }]} />);
expect(screen.getByText("1,500")).toBeInTheDocument();
});
it("renders description when provided", () => {
renderWithRouter(<StandingsPreview entries={[baseEntry]} description="Current Standings" />);
expect(screen.getByText("Current Standings")).toBeInTheDocument();
});
it("renders full standings link when fullStandingsHref is provided", () => {
renderWithRouter(
<StandingsPreview entries={[baseEntry]} fullStandingsHref="/leagues/1/standings/1" />
);
const link = screen.getByRole("link", { name: "Full Standings" });
expect(link).toHaveAttribute("href", "/leagues/1/standings/1");
});
});
describe("Projections", () => {
it("does not render a Projected column by default", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
projectedPoints: 3000,
participantsRemaining: 3,
};
renderWithRouter(<StandingsPreview entries={[entry]} />);
expect(screen.queryByText("Projected")).not.toBeInTheDocument();
expect(screen.queryByText("3,000")).not.toBeInTheDocument();
});
it("renders the projected value when showProjected and participants remain", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
projectedPoints: 3000,
participantsRemaining: 3,
};
renderWithRouter(<StandingsPreview entries={[entry]} showProjected />);
expect(screen.getByText("Projected")).toBeInTheDocument();
expect(screen.getByText("3,000")).toBeInTheDocument();
});
it("shows a placeholder instead of a value when no participants remain", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
projectedPoints: 9999,
participantsRemaining: 0,
};
renderWithRouter(<StandingsPreview entries={[entry]} showProjected />);
// Column header still present, but the projected total is not shown.
expect(screen.getByText("Projected")).toBeInTheDocument();
expect(screen.queryByText("9,999")).not.toBeInTheDocument();
});
it("shows a placeholder when projectedPoints is missing", () => {
const entry: StandingsPreviewEntry = {
...baseEntry,
projectedPoints: null,
participantsRemaining: 3,
};
renderWithRouter(<StandingsPreview entries={[entry]} showProjected />);
expect(screen.getByText("Projected")).toBeInTheDocument();
});
});
});