- Move buildDraftOrderTeams to app/lib/draft-order.ts so it is importable and testable; remove the local copy from the settings component - Add unit tests for buildDraftOrderTeams covering the empty, full, and partial-slot cases - Tighten the draft slot guard from existingSlots.length > 0 to existingSlots.length === currentTeamCount so partial legacy states are treated the same as "order not set" - Condense the 3-line server comment to a single line per project style - Rename getNumTeamsInSeason → getNumDraftSlotsBySeasonId to reflect what the function actually counts, and update its one call site - Add tests for the server-side slot-creation guard logic https://claude.ai/code/session_01M3H55gnMxRztJK9KMXqqZo
234 lines
6.8 KiB
TypeScript
234 lines
6.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { buildDraftOrderTeams, getTeamForPick, getProjectedPicks } from "~/lib/draft-order";
|
|
|
|
describe("buildDraftOrderTeams", () => {
|
|
const teams = [
|
|
{ id: "t1" }, { id: "t2" }, { id: "t3" },
|
|
];
|
|
|
|
it("returns all teams in natural order when no slots exist", () => {
|
|
expect(buildDraftOrderTeams([], teams)).toEqual(["t1", "t2", "t3"]);
|
|
});
|
|
|
|
it("returns slot order when all teams are slotted", () => {
|
|
const slots = [{ teamId: "t3" }, { teamId: "t1" }, { teamId: "t2" }];
|
|
expect(buildDraftOrderTeams(slots, teams)).toEqual(["t3", "t1", "t2"]);
|
|
});
|
|
|
|
it("appends unslotted teams after slotted ones in partial state", () => {
|
|
const slots = [{ teamId: "t2" }];
|
|
expect(buildDraftOrderTeams(slots, teams)).toEqual(["t2", "t1", "t3"]);
|
|
});
|
|
|
|
it("returns empty array when both slots and teams are empty", () => {
|
|
expect(buildDraftOrderTeams([], [])).toEqual([]);
|
|
});
|
|
});
|
|
|
|
function makeSlots(teamCount: number) {
|
|
return Array.from({ length: teamCount }, (_, i) => ({
|
|
draftOrder: i + 1,
|
|
teamId: `team-${i + 1}`,
|
|
}));
|
|
}
|
|
|
|
describe("getTeamForPick", () => {
|
|
const slots = makeSlots(4);
|
|
|
|
it("returns correct team for round 1 (forward)", () => {
|
|
expect(getTeamForPick(1, slots)?.teamId).toBe("team-1");
|
|
expect(getTeamForPick(2, slots)?.teamId).toBe("team-2");
|
|
expect(getTeamForPick(3, slots)?.teamId).toBe("team-3");
|
|
expect(getTeamForPick(4, slots)?.teamId).toBe("team-4");
|
|
});
|
|
|
|
it("returns correct team for round 2 (snake reversed)", () => {
|
|
expect(getTeamForPick(5, slots)?.teamId).toBe("team-4");
|
|
expect(getTeamForPick(6, slots)?.teamId).toBe("team-3");
|
|
expect(getTeamForPick(7, slots)?.teamId).toBe("team-2");
|
|
expect(getTeamForPick(8, slots)?.teamId).toBe("team-1");
|
|
});
|
|
|
|
it("returns correct team for round 3 (forward again)", () => {
|
|
expect(getTeamForPick(9, slots)?.teamId).toBe("team-1");
|
|
expect(getTeamForPick(10, slots)?.teamId).toBe("team-2");
|
|
expect(getTeamForPick(11, slots)?.teamId).toBe("team-3");
|
|
expect(getTeamForPick(12, slots)?.teamId).toBe("team-4");
|
|
});
|
|
|
|
it("returns undefined for empty slots", () => {
|
|
expect(getTeamForPick(1, [])).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("getProjectedPicks", () => {
|
|
it("returns empty array when no draft slots", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: [],
|
|
userTeamId: "team-1",
|
|
currentPick: 1,
|
|
totalRounds: 10,
|
|
existingPicks: [],
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("returns empty array when user has no team in slots", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: makeSlots(4),
|
|
userTeamId: "team-999",
|
|
currentPick: 1,
|
|
totalRounds: 10,
|
|
existingPicks: [],
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
describe("4-team snake draft, user at position 1", () => {
|
|
const slots = makeSlots(4);
|
|
|
|
it("computes picks from start of draft", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-1",
|
|
currentPick: 1,
|
|
totalRounds: 4,
|
|
existingPicks: [],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 1, pickNumber: 1, picksFromNow: 0 },
|
|
{ round: 2, pickNumber: 8, picksFromNow: 7 },
|
|
{ round: 3, pickNumber: 9, picksFromNow: 8 },
|
|
{ round: 4, pickNumber: 16, picksFromNow: 15 },
|
|
]);
|
|
});
|
|
|
|
it("computes picks from mid-draft, skipping past picks", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-1",
|
|
currentPick: 5,
|
|
totalRounds: 4,
|
|
existingPicks: [
|
|
{ pickNumber: 1, teamId: "team-1" },
|
|
],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 2, pickNumber: 8, picksFromNow: 3 },
|
|
{ round: 3, pickNumber: 9, picksFromNow: 4 },
|
|
{ round: 4, pickNumber: 16, picksFromNow: 11 },
|
|
]);
|
|
});
|
|
|
|
it("skips rounds already picked even if current pick is before them", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-1",
|
|
currentPick: 1,
|
|
totalRounds: 4,
|
|
existingPicks: [
|
|
{ pickNumber: 1, teamId: "team-1" },
|
|
{ pickNumber: 8, teamId: "team-1" },
|
|
],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 3, pickNumber: 9, picksFromNow: 8 },
|
|
{ round: 4, pickNumber: 16, picksFromNow: 15 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("4-team snake draft, user at position 3", () => {
|
|
const slots = makeSlots(4);
|
|
|
|
it("computes correct snake positions", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-3",
|
|
currentPick: 1,
|
|
totalRounds: 4,
|
|
existingPicks: [],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 1, pickNumber: 3, picksFromNow: 2 },
|
|
{ round: 2, pickNumber: 6, picksFromNow: 5 },
|
|
{ round: 3, pickNumber: 11, picksFromNow: 10 },
|
|
{ round: 4, pickNumber: 14, picksFromNow: 13 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("4-team snake draft, user at position 4 (last)", () => {
|
|
const slots = makeSlots(4);
|
|
|
|
it("picks first in round 2 due to snake reversal", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-4",
|
|
currentPick: 1,
|
|
totalRounds: 4,
|
|
existingPicks: [],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 1, pickNumber: 4, picksFromNow: 3 },
|
|
{ round: 2, pickNumber: 5, picksFromNow: 4 },
|
|
{ round: 3, pickNumber: 12, picksFromNow: 11 },
|
|
{ round: 4, pickNumber: 13, picksFromNow: 12 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
it("returns empty when all picks are in the past", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: makeSlots(4),
|
|
userTeamId: "team-1",
|
|
currentPick: 17,
|
|
totalRounds: 4,
|
|
existingPicks: [
|
|
{ pickNumber: 1, teamId: "team-1" },
|
|
{ pickNumber: 8, teamId: "team-1" },
|
|
{ pickNumber: 9, teamId: "team-1" },
|
|
{ pickNumber: 16, teamId: "team-1" },
|
|
],
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("handles 8-team snake draft correctly", () => {
|
|
const slots = makeSlots(8);
|
|
|
|
const result = getProjectedPicks({
|
|
draftSlots: slots,
|
|
userTeamId: "team-5",
|
|
currentPick: 1,
|
|
totalRounds: 3,
|
|
existingPicks: [],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 1, pickNumber: 5, picksFromNow: 4 },
|
|
{ round: 2, pickNumber: 12, picksFromNow: 11 },
|
|
{ round: 3, pickNumber: 21, picksFromNow: 20 },
|
|
]);
|
|
});
|
|
|
|
it("excludes picks before currentPick", () => {
|
|
const result = getProjectedPicks({
|
|
draftSlots: makeSlots(4),
|
|
userTeamId: "team-1",
|
|
currentPick: 9,
|
|
totalRounds: 4,
|
|
existingPicks: [],
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ round: 3, pickNumber: 9, picksFromNow: 0 },
|
|
{ round: 4, pickNumber: 16, picksFromNow: 7 },
|
|
]);
|
|
});
|
|
});
|