brackt/app/components/__tests__/MiniDraftGrid.test.tsx

172 lines
6.2 KiB
TypeScript
Raw Permalink Normal View History

Add commish right-click context menus to MiniDraftGrid team headers (#327) * Add commish right-click context menus to MiniDraftGrid team headers Adds onAdjustTimeBankOpen and onSetAutodraftOpen props to MiniDraftGrid so the last-two-rounds display on the Participants tab shows the same "Adjust Time Bank…" / "Set Autodraft…" context menu on right-click that the full Draft Board already exposes. Also passes those callbacks from the route's miniDraftGrid useMemo so they are wired up for commissioners. https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1 * Address all code review issues for draft room context menus Layout bug: flex-1 min-w-20 was nested inside headerInner instead of the direct flex-child wrapper in MiniDraftGrid, breaking equal column sizing. Fixed by moving the classes to the outermost element in both the menu and non-menu paths (matching DraftGridSection's pattern). MiniDraftGrid improvements: - Hoist hasHeaderMenu constant above the draftSlots.map() loop - Add optional connectedTeams prop; disconnected teams render italic + muted-foreground, consistent with DraftGridSection - connectedTeams now wired through the route's miniDraftGrid useMemo DraftGridSection interface: - Make onForceAutopick and onForceManualPickOpen optional (?) to match every other commissioner callback; add null checks at all three call sites (context menu, mobile MoreVertical button, mobile Sheet) - Gate those two callbacks behind isCommissioner at the route level Tests (new files): - app/components/__tests__/MiniDraftGrid.test.tsx — covers layout classes, connected/disconnected styling, and all four context menu interactions - app/components/__tests__/DraftGridSection.test.tsx — covers all three context menu surfaces for both commissioner and non-commissioner roles Storybook: add CommissionerView and DisconnectedTeams stories to MiniDraftGrid.stories.tsx https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1 * Move hasHeaderMenu out of IIFE into component body Computing it before the return statement is the right place since it only depends on props, not loop variables. Removes the IIFE entirely and fixes the indentation of the map callback body. https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1 * Mock HTMLElement.scrollTo in test setup jsdom does not implement scrollTo on elements, causing any component that calls element.scrollTo() (e.g. MiniDraftGrid's scroll-to-current-cell effect) to throw in unit tests. https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-04-25 07:42:43 -07:00
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { MiniDraftGrid } from "~/components/draft/MiniDraftGrid";
const draftSlots = [
{ id: "slot-1", draftOrder: 1, team: { id: "team-1", name: "Alpha", logoUrl: null } },
{ id: "slot-2", draftOrder: 2, team: { id: "team-2", name: "Bravo", logoUrl: null } },
];
const ownerMap = { "team-1": "Alpha", "team-2": "Bravo" };
// Round 1: team-1 pick 1 (filled), team-2 pick 2 (current/empty)
// Round 2 (snake): team-2 pick 3, team-1 pick 4 — both empty
const draftGrid = [
[
{ pickNumber: 1, round: 1, pickInRound: 1, teamId: "team-1", pick: { participant: { name: "Player A" }, sport: { name: "NFL" } } },
{ pickNumber: 2, round: 1, pickInRound: 2, teamId: "team-2" },
],
[
{ pickNumber: 3, round: 2, pickInRound: 1, teamId: "team-2" },
{ pickNumber: 4, round: 2, pickInRound: 2, teamId: "team-1" },
],
];
const baseProps = {
draftSlots,
draftGrid,
currentPick: 2,
currentRound: 1,
ownerMap,
};
describe("MiniDraftGrid", () => {
beforeEach(() => vi.clearAllMocks());
describe("Team header layout", () => {
it("renders all team names", () => {
render(<MiniDraftGrid {...baseProps} />);
expect(screen.getAllByText("Alpha").length).toBeGreaterThan(0);
expect(screen.getAllByText("Bravo").length).toBeGreaterThan(0);
});
it("non-commissioner headers carry flex sizing classes", () => {
render(<MiniDraftGrid {...baseProps} />);
const alphaHeader = screen.getAllByText("Alpha")[0].closest(".flex-1");
expect(alphaHeader).toHaveClass("flex-1", "min-w-20");
});
it("commissioner headers carry flex sizing classes", () => {
render(
<MiniDraftGrid
{...baseProps}
onAdjustTimeBankOpen={vi.fn()}
onSetAutodraftOpen={vi.fn()}
/>
);
const alphaHeader = screen.getAllByText("Alpha")[0].closest(".flex-1");
expect(alphaHeader).toHaveClass("flex-1", "min-w-20");
});
});
describe("Disconnected team styling", () => {
it("applies italic + muted style to disconnected teams when connectedTeams provided", () => {
render(
<MiniDraftGrid
{...baseProps}
connectedTeams={new Set(["team-1"])}
/>
);
const bravoSpan = screen.getAllByText("Bravo")[0];
expect(bravoSpan).toHaveClass("italic", "text-muted-foreground");
const alphaSpan = screen.getAllByText("Alpha")[0];
expect(alphaSpan).not.toHaveClass("italic");
});
it("treats all teams as connected when connectedTeams is not provided", () => {
render(<MiniDraftGrid {...baseProps} />);
expect(screen.getAllByText("Alpha")[0]).not.toHaveClass("italic");
expect(screen.getAllByText("Bravo")[0]).not.toHaveClass("italic");
});
});
describe("Team header context menus (commissioner)", () => {
it("shows no context menu when callbacks are absent", () => {
render(<MiniDraftGrid {...baseProps} />);
const alphaHeader = screen.getAllByText("Alpha")[0];
fireEvent.contextMenu(alphaHeader);
expect(screen.queryByText("Adjust Time Bank...")).not.toBeInTheDocument();
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
});
it("shows Adjust Time Bank and Set Autodraft on right-click when both callbacks provided", () => {
render(
<MiniDraftGrid
{...baseProps}
onAdjustTimeBankOpen={vi.fn()}
onSetAutodraftOpen={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.getByText("Adjust Time Bank...")).toBeInTheDocument();
expect(screen.getByText("Set Autodraft...")).toBeInTheDocument();
});
it("shows only Adjust Time Bank when only that callback is provided", () => {
render(
<MiniDraftGrid {...baseProps} onAdjustTimeBankOpen={vi.fn()} />
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.getByText("Adjust Time Bank...")).toBeInTheDocument();
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
});
});
describe("Current cell context menu (commissioner)", () => {
it("shows Force Auto Pick and Force Manual Pick on right-click of current cell", () => {
render(
<MiniDraftGrid
{...baseProps}
onForceAutopick={vi.fn()}
onForceManualPickOpen={vi.fn()}
/>
);
// Pick 2 is the current cell (team-2, round 1)
const currentCell = screen.getByTitle("Overall Pick #2");
fireEvent.contextMenu(currentCell);
expect(screen.getByText("Force Auto Pick")).toBeInTheDocument();
expect(screen.getByText("Force Manual Pick")).toBeInTheDocument();
});
it("shows no force-pick menu when no callbacks provided", () => {
render(<MiniDraftGrid {...baseProps} />);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
expect(screen.queryByText("Force Manual Pick")).not.toBeInTheDocument();
});
});
describe("Picked cell context menu (commissioner)", () => {
it("shows Replace Pick and Roll Back on right-click of a picked cell", () => {
render(
<MiniDraftGrid
{...baseProps}
onReplacePick={vi.fn()}
onRollbackToPick={vi.fn()}
/>
);
// Pick 1 is already picked (team-1)
const pickedCell = screen.getByTitle("Overall Pick #1");
fireEvent.contextMenu(pickedCell);
expect(screen.getByText("Replace Pick")).toBeInTheDocument();
expect(screen.getByText("Roll Back to This Pick")).toBeInTheDocument();
});
it("shows no menu on picked cell when no callbacks provided", () => {
render(<MiniDraftGrid {...baseProps} />);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
expect(screen.queryByText("Replace Pick")).not.toBeInTheDocument();
});
});
describe("Empty grid", () => {
it("renders nothing when draftGrid is empty", () => {
const { container } = render(
<MiniDraftGrid {...baseProps} draftGrid={[]} />
);
expect(container.firstChild).toBeNull();
});
});
});