import { describe, it, expect } from "vitest"; import { getTeamData, seedConference, simulateConferenceBracket, } from "../nfl-simulator"; // ─── Team data ──────────────────────────────────────────────────────────────── describe("getTeamData", () => { it("finds teams by full name", () => { expect(getTeamData("Kansas City Chiefs")).toMatchObject({ conference: "AFC", division: "West", }); expect(getTeamData("Philadelphia Eagles")).toMatchObject({ conference: "NFC", division: "East", }); }); it("finds teams by short name", () => { expect(getTeamData("Chiefs")).toMatchObject({ conference: "AFC", division: "West" }); expect(getTeamData("Eagles")).toMatchObject({ conference: "NFC", division: "East" }); }); it("is case-insensitive", () => { expect(getTeamData("kansas city chiefs")).toBeDefined(); expect(getTeamData("PATRIOTS")).toBeDefined(); }); it("returns undefined for unknown teams", () => { expect(getTeamData("Springfield Isotopes")).toBeUndefined(); }); it("covers all 32 NFL teams across both conferences", () => { const allTeams = [ // AFC East "Buffalo Bills", "Miami Dolphins", "New England Patriots", "New York Jets", // AFC North "Baltimore Ravens", "Cincinnati Bengals", "Cleveland Browns", "Pittsburgh Steelers", // AFC South "Houston Texans", "Indianapolis Colts", "Jacksonville Jaguars", "Tennessee Titans", // AFC West "Denver Broncos", "Kansas City Chiefs", "Las Vegas Raiders", "Los Angeles Chargers", // NFC East "Dallas Cowboys", "New York Giants", "Philadelphia Eagles", "Washington Commanders", // NFC North "Chicago Bears", "Detroit Lions", "Green Bay Packers", "Minnesota Vikings", // NFC South "Atlanta Falcons", "Carolina Panthers", "New Orleans Saints", "Tampa Bay Buccaneers", // NFC West "Arizona Cardinals", "Los Angeles Rams", "San Francisco 49ers", "Seattle Seahawks", ]; for (const name of allTeams) { expect(getTeamData(name), `${name} not found`).toBeDefined(); } expect(allTeams).toHaveLength(32); }); it("has exactly 16 AFC and 16 NFC teams", () => { const allTeams = [ "Buffalo Bills", "Miami Dolphins", "New England Patriots", "New York Jets", "Baltimore Ravens", "Cincinnati Bengals", "Cleveland Browns", "Pittsburgh Steelers", "Houston Texans", "Indianapolis Colts", "Jacksonville Jaguars", "Tennessee Titans", "Denver Broncos", "Kansas City Chiefs", "Las Vegas Raiders", "Los Angeles Chargers", "Dallas Cowboys", "New York Giants", "Philadelphia Eagles", "Washington Commanders", "Chicago Bears", "Detroit Lions", "Green Bay Packers", "Minnesota Vikings", "Atlanta Falcons", "Carolina Panthers", "New Orleans Saints", "Tampa Bay Buccaneers", "Arizona Cardinals", "Los Angeles Rams", "San Francisco 49ers", "Seattle Seahawks", ]; const afcCount = allTeams.filter((n) => getTeamData(n)?.conference === "AFC").length; const nfcCount = allTeams.filter((n) => getTeamData(n)?.conference === "NFC").length; expect(afcCount).toBe(16); expect(nfcCount).toBe(16); }); it("has exactly 4 teams per division per conference", () => { const allTeams = [ "Buffalo Bills", "Miami Dolphins", "New England Patriots", "New York Jets", "Baltimore Ravens", "Cincinnati Bengals", "Cleveland Browns", "Pittsburgh Steelers", "Houston Texans", "Indianapolis Colts", "Jacksonville Jaguars", "Tennessee Titans", "Denver Broncos", "Kansas City Chiefs", "Las Vegas Raiders", "Los Angeles Chargers", "Dallas Cowboys", "New York Giants", "Philadelphia Eagles", "Washington Commanders", "Chicago Bears", "Detroit Lions", "Green Bay Packers", "Minnesota Vikings", "Atlanta Falcons", "Carolina Panthers", "New Orleans Saints", "Tampa Bay Buccaneers", "Arizona Cardinals", "Los Angeles Rams", "San Francisco 49ers", "Seattle Seahawks", ]; const divisions = ["East", "North", "South", "West"] as const; const conferences = ["AFC", "NFC"] as const; for (const conf of conferences) { for (const div of divisions) { const count = allTeams.filter((n) => { const d = getTeamData(n); return d?.conference === conf && d?.division === div; }).length; expect(count, `${conf} ${div}`).toBe(4); } } }); }); // ─── seedConference ─────────────────────────────────────────────────────────── function makeTeam( id: string, division: "East" | "North" | "South" | "West", projectedWins: number, elo = 1500 ) { return { participantId: id, elo, division, projectedWins }; } describe("seedConference", () => { // Build a deterministic 16-team AFC where wins are all unique (no tiebreaks needed) const afcTeams = [ // East: Bills win makeTeam("bills", "East", 13), makeTeam("dolphins", "East", 9), makeTeam("patriots", "East", 5), makeTeam("jets", "East", 4), // North: Ravens win makeTeam("ravens", "North", 12), makeTeam("bengals", "North", 10), makeTeam("steelers", "North", 8), makeTeam("browns", "North", 6), // South: Texans win makeTeam("texans", "South", 11), makeTeam("colts", "South", 7), makeTeam("jaguars", "South", 3), makeTeam("titans", "South", 2), // West: Chiefs win makeTeam("chiefs", "West", 14), makeTeam("chargers", "West", 10.5), // non-integer to stay unique makeTeam("broncos", "West", 8.5), makeTeam("raiders", "West", 1), ]; it("returns 7 seeds", () => { const seeds = seedConference(afcTeams); expect(seeds).toHaveLength(7); }); it("seed 1 is the team with the most wins (Chiefs, 14)", () => { const seeds = seedConference(afcTeams); const seed1 = seeds.find((s) => s.seed === 1); expect(seed1?.participantId).toBe("chiefs"); }); it("division winners occupy seeds 1-4", () => { const seeds = seedConference(afcTeams); const divWinnerIds = new Set(["chiefs", "bills", "ravens", "texans"]); const seedsOneToFour = seeds.filter((s) => s.seed <= 4).map((s) => s.participantId); expect(new Set(seedsOneToFour)).toEqual(divWinnerIds); }); it("wildcards occupy seeds 5-7", () => { const seeds = seedConference(afcTeams); // Wildcards = top 3 non-division-winners: bengals(10), chargers(10.5), colts(7) const wcIds = seeds.filter((s) => s.seed >= 5).map((s) => s.participantId); expect(wcIds).toHaveLength(3); // Top 3 non-winners by wins: chargers(10.5), bengals(10), dolphins(9) expect(new Set(wcIds)).toEqual(new Set(["chargers", "bengals", "dolphins"])); }); it("seed 5 is the best wildcard", () => { const seeds = seedConference(afcTeams); const seed5 = seeds.find((s) => s.seed === 5); // chargers has 10.5 wins, highest non-division-winner expect(seed5?.participantId).toBe("chargers"); }); }); // ─── simulateConferenceBracket ──────────────────────────────────────────────── describe("simulateConferenceBracket", () => { // Dominant team: one team has overwhelming Elo advantage const dominantSeeds = [ { participantId: "king", elo: 2500, seed: 1 }, // bye, nearly unbeatable { participantId: "s2", elo: 1500, seed: 2 }, { participantId: "s3", elo: 1500, seed: 3 }, { participantId: "s4", elo: 1500, seed: 4 }, { participantId: "s5", elo: 1500, seed: 5 }, { participantId: "s6", elo: 1500, seed: 6 }, { participantId: "s7", elo: 1500, seed: 7 }, ]; it("returns a finalist, confChampLoser, and two divisionalLosers", () => { const result = simulateConferenceBracket(dominantSeeds); expect(result.finalist).toBeDefined(); expect(result.confChampLoser).toBeDefined(); expect(result.divisionalLosers).toHaveLength(2); }); it("finalist and confChampLoser are different teams", () => { const result = simulateConferenceBracket(dominantSeeds); expect(result.finalist.participantId).not.toBe(result.confChampLoser.participantId); }); it("all returned participant IDs are distinct", () => { const result = simulateConferenceBracket(dominantSeeds); const ids = [ result.finalist.participantId, result.confChampLoser.participantId, ...result.divisionalLosers.map((l) => l.participantId), ]; expect(new Set(ids).size).toBe(ids.length); }); it("dominant seed 1 team reaches the conference championship with high frequency", () => { // Run 200 trials; seed 1 with Elo 2500 should reach conf champ almost always let reachedConfChamp = 0; for (let i = 0; i < 200; i++) { const result = simulateConferenceBracket(dominantSeeds); if ( result.finalist.participantId === "king" || result.confChampLoser.participantId === "king" ) { reachedConfChamp++; } } // With Elo 2500 vs 1500, P(win single game) ≈ 0.9997; reaching conf champ ≈ near certain expect(reachedConfChamp).toBeGreaterThan(190); }); it("higher seed wins more often than lower seed due to home-field advantage", () => { // Evenly matched teams (same Elo). Seed 2 should beat seed 7 more than 50% // because seed 2 is the home team (+48 Elo ≈ 57% expected win rate). const evenSeeds = [ { participantId: "s1", elo: 1500, seed: 1 }, { participantId: "s2", elo: 1500, seed: 2 }, { participantId: "s3", elo: 1500, seed: 3 }, { participantId: "s4", elo: 1500, seed: 4 }, { participantId: "s5", elo: 1500, seed: 5 }, { participantId: "s6", elo: 1500, seed: 6 }, { participantId: "s7", elo: 1500, seed: 7 }, ]; let seed2WonWildCard = 0; const trials = 500; for (let i = 0; i < trials; i++) { const result = simulateConferenceBracket(evenSeeds); // If s7 is absent from all scored positions, it lost in Wild Card to seed 2 const s7Survived = result.divisionalLosers.some((l) => l.participantId === "s7") || result.confChampLoser.participantId === "s7" || result.finalist.participantId === "s7"; if (!s7Survived) seed2WonWildCard++; } // Seed 2 (home, +48 Elo) vs seed 7 (away) with equal base Elo → ~57% win rate expect(seed2WonWildCard).toBeGreaterThan(trials * 0.50); }); }); // ─── Smoke test ─────────────────────────────────────────────────────────────── describe("seedConference + simulateConferenceBracket integration", () => { it("produces valid probability-like fractions when run many times", () => { const afcTeams = [ makeTeam("bills", "East", 13, 1650), makeTeam("dolphins", "East", 9, 1480), makeTeam("patriots", "East", 5, 1400), makeTeam("jets", "East", 4, 1380), makeTeam("ravens", "North", 12, 1620), makeTeam("bengals", "North", 10, 1520), makeTeam("steelers", "North", 8, 1490), makeTeam("browns", "North", 6, 1440), makeTeam("texans", "South", 11, 1560), makeTeam("colts", "South", 7, 1460), makeTeam("jaguars", "South", 3, 1390), makeTeam("titans", "South", 2, 1370), makeTeam("chiefs", "West", 14, 1700), makeTeam("chargers", "West", 10.5, 1530), makeTeam("broncos", "West", 8.5, 1500), makeTeam("raiders", "West", 1, 1360), ]; const finalistCounts: Record = {}; const N = 500; for (let i = 0; i < N; i++) { const seeds = seedConference(afcTeams); if (seeds.length < 7) continue; const result = simulateConferenceBracket(seeds); const id = result.finalist.participantId; finalistCounts[id] = (finalistCounts[id] ?? 0) + 1; } // Every team that appeared as finalist should have a non-negative count for (const count of Object.values(finalistCounts)) { expect(count).toBeGreaterThan(0); } // Total finalist appearances = N const total = Object.values(finalistCounts).reduce((a, b) => a + b, 0); expect(total).toBe(N); // Strong teams (Chiefs, Ravens, Bills) should make conference finals more often const chiefsFinalist = finalistCounts["chiefs"] ?? 0; const jetsFinalist = finalistCounts["jets"] ?? 0; expect(chiefsFinalist).toBeGreaterThan(jetsFinalist); }); });