brackt/app/components/sport-season/__tests__/UpcomingCalendarPanel.test.tsx
Chris Parsons 3c4ed67946
Add upcoming events pages and fix timezone filtering, fixes #213 (#235)
- Fix UTC midnight rollover bug: server now queries from yesterday UTC
  as a buffer; UpcomingCalendarPanel filters to local-today client-side
  via useEffect + Intl.DateTimeFormat, removing the need for any
  cookie or server-side timezone detection
- Cap homepage and league page panels at 6 events with a "View all" link
- Add /upcoming-events page (60-day view across all leagues)
- Add /leagues/:leagueId/upcoming-events page (60-day per-league view)
- Add emptyMessage prop to UpcomingCalendarPanel for context-specific copy
- Change getUpcomingEventsForDraftedParticipants to accept pre-computed
  date strings instead of Date objects

fixes #213

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 00:49:16 -07:00

217 lines
7.7 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router";
import { UpcomingCalendarPanel } from "../UpcomingCalendarPanel";
import type { CalendarPanelEvent } from "../UpcomingCalendarPanel";
function renderWithRouter(ui: React.ReactElement) {
return render(<BrowserRouter>{ui}</BrowserRouter>);
}
function makeEvent(overrides: Partial<CalendarPanelEvent> = {}): CalendarPanelEvent {
return {
id: overrides.id ?? "event-1",
name: overrides.name ?? "UCL Quarterfinals",
// Use explicit key check so callers can pass null to test the null-date path
eventDate: "eventDate" in overrides ? (overrides.eventDate as string | null) : "2099-04-09",
earliestGameTime: "earliestGameTime" in overrides ? (overrides.earliestGameTime as string | null) : null,
matchLabel: overrides.matchLabel ?? null,
eventType: overrides.eventType ?? "playoff_game",
sportsSeasonId: overrides.sportsSeasonId ?? "ss-1",
sportName: overrides.sportName ?? "Football",
sportSeasonName: overrides.sportSeasonName ?? "UCL 2025",
leagueName: overrides.leagueName,
leagueId: overrides.leagueId,
sportsSeasonPageUrl: overrides.sportsSeasonPageUrl,
relevantParticipants: overrides.relevantParticipants ?? [
{ id: "p1", name: "Man City" },
],
};
}
describe("UpcomingCalendarPanel", () => {
describe("empty state", () => {
it("shows empty state message when no events", () => {
renderWithRouter(<UpcomingCalendarPanel events={[]} />);
expect(screen.getByText(/No upcoming events in the next 30 days/i)).toBeInTheDocument();
});
it("renders the panel title", () => {
renderWithRouter(<UpcomingCalendarPanel events={[]} />);
expect(screen.getByText("Upcoming Events")).toBeInTheDocument();
});
});
describe("event display", () => {
it("shows event name and formatted date", () => {
renderWithRouter(
<UpcomingCalendarPanel events={[makeEvent({ name: "UCL QF", eventDate: "2099-04-09" })]} />
);
expect(screen.getByText("UCL QF")).toBeInTheDocument();
expect(screen.getByText("Apr 9")).toBeInTheDocument();
});
it("shows sport name and season name", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ sportName: "Football", sportSeasonName: "UCL 2025" })]}
/>
);
expect(screen.getByText(/Football · UCL 2025/)).toBeInTheDocument();
});
it("shows participant badge for bracket events", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({
eventType: "playoff_game",
relevantParticipants: [{ id: "p1", name: "Man City" }],
})]}
/>
);
expect(screen.getByText("Man City")).toBeInTheDocument();
});
it("shows 'N of your picks' for all-compete events with multiple participants", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({
eventType: "schedule_event",
name: "Australian GP",
relevantParticipants: [
{ id: "p1", name: "Verstappen" },
{ id: "p2", name: "Hamilton" },
{ id: "p3", name: "Leclerc" },
{ id: "p4", name: "Norris" },
{ id: "p5", name: "Russell" },
],
})]}
/>
);
expect(screen.getByText("5 of your picks")).toBeInTheDocument();
});
it("renders TBD when both eventDate and earliestGameTime are null", () => {
renderWithRouter(
<UpcomingCalendarPanel events={[makeEvent({ eventDate: null })]} />
);
expect(screen.getByText("TBD")).toBeInTheDocument();
});
it("uses earliestGameTime date when eventDate is null", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ eventDate: null, earliestGameTime: "2099-04-09T14:00:00.000Z" })]}
/>
);
// Should not show TBD
expect(screen.queryByText("TBD")).not.toBeInTheDocument();
});
it("shows matchLabel appended to event name when present", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ name: "PDC World Championship", matchLabel: "Semifinals Game #1" })]}
/>
);
expect(screen.getByText(/PDC World Championship — Semifinals Game #1/)).toBeInTheDocument();
});
it("shows plain event name when matchLabel is null", () => {
renderWithRouter(
<UpcomingCalendarPanel events={[makeEvent({ name: "UCL QF", matchLabel: null })]} />
);
expect(screen.getByText("UCL QF")).toBeInTheDocument();
});
});
describe("participant overflow", () => {
it("shows first 3 participants and an overflow badge for 4+", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({
eventType: "playoff_game",
relevantParticipants: [
{ id: "p1", name: "Alpha" },
{ id: "p2", name: "Beta" },
{ id: "p3", name: "Gamma" },
{ id: "p4", name: "Delta" },
{ id: "p5", name: "Epsilon" },
],
})]}
/>
);
expect(screen.getByText("Alpha")).toBeInTheDocument();
expect(screen.getByText("Beta")).toBeInTheDocument();
expect(screen.getByText("Gamma")).toBeInTheDocument();
expect(screen.queryByText("Delta")).not.toBeInTheDocument();
expect(screen.getByText("+2 more")).toBeInTheDocument();
});
it("shows all participants when exactly 3", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({
eventType: "playoff_game",
relevantParticipants: [
{ id: "p1", name: "Alpha" },
{ id: "p2", name: "Beta" },
{ id: "p3", name: "Gamma" },
],
})]}
/>
);
expect(screen.getByText("Alpha")).toBeInTheDocument();
expect(screen.getByText("Gamma")).toBeInTheDocument();
expect(screen.queryByText(/\+\d+ more/)).not.toBeInTheDocument();
});
});
describe("league display", () => {
it("does not show league name when showLeague is false (default)", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ leagueName: "My League" })]}
showLeague={false}
/>
);
expect(screen.queryByText("My League")).not.toBeInTheDocument();
});
it("shows league name badge when showLeague is true", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ leagueName: "My League" })]}
showLeague={true}
/>
);
expect(screen.getByText("My League")).toBeInTheDocument();
});
it("renders multiple events from different leagues", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[
makeEvent({ id: "e1", name: "UCL QF", leagueName: "League A" }),
makeEvent({ id: "e2", name: "Australian GP", leagueName: "League B", eventType: "schedule_event" }),
]}
showLeague={true}
/>
);
expect(screen.getByText("League A")).toBeInTheDocument();
expect(screen.getByText("League B")).toBeInTheDocument();
});
});
describe("link behaviour", () => {
it("wraps event row in a link when sportsSeasonPageUrl is provided", () => {
renderWithRouter(
<UpcomingCalendarPanel
events={[makeEvent({ sportsSeasonPageUrl: "/leagues/l1/sports-seasons/ss1" })]}
/>
);
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", "/leagues/l1/sports-seasons/ss1");
});
});
});