- 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>
217 lines
7.7 KiB
TypeScript
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");
|
|
});
|
|
});
|
|
});
|