Adds an admin page at /admin/draft-schedule that visualizes sport-season draft windows (draftOn -> draftOff) as a Gantt-style timeline: Y axis is the sport, X axis is a 6- or 12-month horizon. A warning panel highlights sports with no open or upcoming draft window so gaps in coverage are obvious at a glance. - New findDraftScheduleForHorizon model query (overlap test against the horizon, admin sport-seasons only) - Presentational DraftScheduleGantt component with month gridlines, a "today" marker, and status-colored bars linking to each sport-season - Route registered in routes.ts and linked from the admin nav - Unit tests for the model query and component Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fd51U9DPMNz4KzBeucR6CK
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { MemoryRouter } from "react-router";
|
|
import { DraftScheduleGantt, type GanttSport } from "../DraftScheduleGantt";
|
|
|
|
const nbaWindow = {
|
|
id: "ss-1",
|
|
name: "2026 NBA Playoffs",
|
|
year: 2026,
|
|
status: "upcoming" as const,
|
|
draftOn: "2026-08-01",
|
|
draftOff: "2026-09-15",
|
|
sport: { id: "nba", name: "NBA", slug: "nba", iconUrl: null },
|
|
};
|
|
|
|
function renderGantt(sports: GanttSport[], months = 6) {
|
|
return render(
|
|
<MemoryRouter>
|
|
<DraftScheduleGantt sports={sports} today="2026-07-02" months={months} />
|
|
</MemoryRouter>
|
|
);
|
|
}
|
|
|
|
describe("DraftScheduleGantt", () => {
|
|
it("renders empty state when there are no sports", () => {
|
|
renderGantt([]);
|
|
expect(screen.getByText("No sports found.")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders a bar linking to the sport-season for each draft window", () => {
|
|
renderGantt([
|
|
{ id: "nba", name: "NBA", slug: "nba", iconUrl: null, windows: [nbaWindow] },
|
|
]);
|
|
|
|
const bar = screen.getByRole("link", { name: /2026 NBA Playoffs/ });
|
|
expect(bar).toHaveAttribute("href", "/admin/sports-seasons/ss-1");
|
|
});
|
|
|
|
it("shows a 'No draft window' hint for a sport with no windows", () => {
|
|
renderGantt([
|
|
{ id: "golf", name: "Golf", slug: "golf", iconUrl: null, windows: [] },
|
|
]);
|
|
|
|
expect(screen.getByText("Golf")).toBeInTheDocument();
|
|
expect(screen.getByText("No draft window")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the status legend", () => {
|
|
renderGantt([
|
|
{ id: "nba", name: "NBA", slug: "nba", iconUrl: null, windows: [nbaWindow] },
|
|
]);
|
|
|
|
expect(screen.getByText("Active")).toBeInTheDocument();
|
|
expect(screen.getByText("Upcoming")).toBeInTheDocument();
|
|
expect(screen.getByText("Completed")).toBeInTheDocument();
|
|
});
|
|
|
|
it("reflects the horizon length in the description", () => {
|
|
renderGantt(
|
|
[{ id: "nba", name: "NBA", slug: "nba", iconUrl: null, windows: [nbaWindow] }],
|
|
12
|
|
);
|
|
|
|
expect(
|
|
screen.getByText(/Draft windows across the next 12 months/)
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|