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
198 lines
7 KiB
TypeScript
198 lines
7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { DraftGridSection } from "~/components/draft/DraftGridSection";
|
|
|
|
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" };
|
|
|
|
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" },
|
|
],
|
|
];
|
|
|
|
const baseProps = {
|
|
draftSlots,
|
|
draftGrid,
|
|
currentPick: 2,
|
|
teamTimers: {} as Record<string, number | undefined>,
|
|
autodraftStatus: {} as Record<string, { isEnabled: boolean; mode: "next_pick" | "while_on"; queueOnly: boolean }>,
|
|
connectedTeams: new Set(["team-1", "team-2"]),
|
|
isCommissioner: false,
|
|
ownerMap,
|
|
seasonStatus: "draft" as const,
|
|
draftPaused: false,
|
|
};
|
|
|
|
describe("DraftGridSection", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
describe("Team header context menus", () => {
|
|
it("shows no header context menu for non-commissioner", () => {
|
|
render(<DraftGridSection {...baseProps} />);
|
|
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
|
|
expect(screen.queryByText("Adjust Time Bank...")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows Adjust Time Bank and Set Autodraft for commissioner on right-click", () => {
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
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("calls onAdjustTimeBankOpen with correct teamId", async () => {
|
|
const user = userEvent.setup();
|
|
const onAdjustTimeBankOpen = vi.fn();
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
onAdjustTimeBankOpen={onAdjustTimeBankOpen}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getAllByText("Bravo")[0]);
|
|
await user.click(screen.getByText("Adjust Time Bank..."));
|
|
expect(onAdjustTimeBankOpen).toHaveBeenCalledWith("team-2");
|
|
});
|
|
|
|
it("calls onSetAutodraftOpen with correct teamId", async () => {
|
|
const user = userEvent.setup();
|
|
const onSetAutodraftOpen = vi.fn();
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
onSetAutodraftOpen={onSetAutodraftOpen}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
|
|
await user.click(screen.getByText("Set Autodraft..."));
|
|
expect(onSetAutodraftOpen).toHaveBeenCalledWith("team-1");
|
|
});
|
|
});
|
|
|
|
describe("Current cell context menu", () => {
|
|
it("shows no context menu for non-commissioner on current cell", () => {
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
onForceAutopick={vi.fn()}
|
|
onForceManualPickOpen={vi.fn()}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
|
|
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows Force Auto Pick and Force Manual Pick for commissioner", () => {
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
onForceAutopick={vi.fn()}
|
|
onForceManualPickOpen={vi.fn()}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
|
|
expect(screen.getByText("Force Auto Pick")).toBeInTheDocument();
|
|
expect(screen.getByText("Force Manual Pick")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows no force-pick menu when callbacks absent even for commissioner", () => {
|
|
render(<DraftGridSection {...baseProps} isCommissioner />);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
|
|
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("calls onForceAutopick with correct args", async () => {
|
|
const user = userEvent.setup();
|
|
const onForceAutopick = vi.fn();
|
|
render(
|
|
<DraftGridSection {...baseProps} isCommissioner onForceAutopick={onForceAutopick} />
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
|
|
await user.click(screen.getByText("Force Auto Pick"));
|
|
expect(onForceAutopick).toHaveBeenCalledWith(2, "team-2");
|
|
});
|
|
|
|
it("calls onForceManualPickOpen with correct args", async () => {
|
|
const user = userEvent.setup();
|
|
const onForceManualPickOpen = vi.fn();
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
onForceManualPickOpen={onForceManualPickOpen}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
|
|
await user.click(screen.getByText("Force Manual Pick"));
|
|
expect(onForceManualPickOpen).toHaveBeenCalledWith(2, "team-2");
|
|
});
|
|
});
|
|
|
|
describe("Picked cell context menu", () => {
|
|
it("shows Replace Pick and Roll Back for commissioner on a picked cell", () => {
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
isCommissioner
|
|
onReplacePick={vi.fn()}
|
|
onRollbackToPick={vi.fn()}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
|
|
expect(screen.getByText("Replace Pick")).toBeInTheDocument();
|
|
expect(screen.getByText("Roll Back to This Pick")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows no context menu on picked cell for non-commissioner", () => {
|
|
render(
|
|
<DraftGridSection
|
|
{...baseProps}
|
|
onReplacePick={vi.fn()}
|
|
onRollbackToPick={vi.fn()}
|
|
/>
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
|
|
expect(screen.queryByText("Replace Pick")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("calls onReplacePick with correct args", async () => {
|
|
const user = userEvent.setup();
|
|
const onReplacePick = vi.fn();
|
|
render(
|
|
<DraftGridSection {...baseProps} isCommissioner onReplacePick={onReplacePick} />
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
|
|
await user.click(screen.getByText("Replace Pick"));
|
|
expect(onReplacePick).toHaveBeenCalledWith(1, "team-1");
|
|
});
|
|
|
|
it("calls onRollbackToPick with correct pickNumber", async () => {
|
|
const user = userEvent.setup();
|
|
const onRollbackToPick = vi.fn();
|
|
render(
|
|
<DraftGridSection {...baseProps} isCommissioner onRollbackToPick={onRollbackToPick} />
|
|
);
|
|
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
|
|
await user.click(screen.getByText("Roll Back to This Pick"));
|
|
expect(onRollbackToPick).toHaveBeenCalledWith(1);
|
|
});
|
|
});
|
|
});
|