From 3b0bd509cd83aa2c02a15314d518a8dcab1e88a3 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 28 May 2026 17:22:31 +0300 Subject: [PATCH] Fix can't start bracket if teams dropped in groups Fixes bug introduced in commit 3df1b93e6a4594093d4a80e18d6ef67b5bfac180 --- .../tournament-bracket/core/Bracket.test.ts | 40 +++++++++++++++++++ .../core/Bracket/RoundRobinBracket.ts | 14 +++++++ 2 files changed, 54 insertions(+) diff --git a/app/features/tournament-bracket/core/Bracket.test.ts b/app/features/tournament-bracket/core/Bracket.test.ts index ea800c980..120183c49 100644 --- a/app/features/tournament-bracket/core/Bracket.test.ts +++ b/app/features/tournament-bracket/core/Bracket.test.ts @@ -313,6 +313,46 @@ describe("round robin standings - dropped out teams", () => { expect(team3Standing?.stats?.setWins).toBe(1); expect(team3Standing?.stats?.setLosses).toBe(2); }); + + it("should not credit wins against a team that dropped out before completing all of their matches (forfeit-closed)", () => { + // Production scenario: team 4 dropped before playing 3-4, then admin's + // drop action ran endDroppedTeamMatches which closed 3-4 with a result + // (team 3 marked winner) but no score on either side. Wins against team + // 4 should still be excluded from tiebreakers — same intent as the + // skipMatchups variant above, but matching the real production shape. + const tournament = droppedOutTournament({ forfeitMatchups: ["3-4"] }); + const standings = tournament.bracketByIdx(0)!.currentStandings(true); + + const team1Standing = standings.find((s) => s.team.id === 1); + const team2Standing = standings.find((s) => s.team.id === 2); + const team3Standing = standings.find((s) => s.team.id === 3); + + expect(team1Standing?.stats?.setWins).toBe(2); + expect(team1Standing?.stats?.setLosses).toBe(0); + + expect(team2Standing?.stats?.setWins).toBe(1); + expect(team2Standing?.stats?.setLosses).toBe(1); + + expect(team3Standing?.stats?.setWins).toBe(0); + expect(team3Standing?.stats?.setLosses).toBe(2); + }); + + it("should report relevantMatchesFinished=true when a dropped team's remaining matches were forfeited (no score)", () => { + const tournament = droppedOutTournament({ forfeitMatchups: ["3-4"] }); + + const { relevantMatchesFinished } = tournament + .bracketByIdx(0)! + .source({ placements: [1] }); + + expect(relevantMatchesFinished).toBe(true); + }); + + it("includes a fully-forfeited dropped team in standings", () => { + const tournament = droppedOutTournament({ forfeitMatchups: ["3-4"] }); + const standings = tournament.bracketByIdx(0)!.standings; + + expect(standings.map((s) => s.team.id)).toContain(4); + }); }); describe("round robin A/B divisions standings", () => { diff --git a/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts b/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts index 039983087..d32b411b8 100644 --- a/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/RoundRobinBracket.ts @@ -266,6 +266,20 @@ export class RoundRobinBracket extends Bracket { } } + // Seed dropped teams here (after wins-against-tied) so forfeit matches don't credit opponents while still keeping them in standings. + for (const teamId of droppedOutWithIncompleteMatches) { + teams.push({ + id: teamId, + setWins: 0, + setLosses: 0, + mapWins: 0, + mapLosses: 0, + winsAgainstTied: 0, + points: 0, + koCount: 0, + }); + } + const droppedOutTeams = this.tournament.ctx.teams .filter((t) => t.droppedOut) .map((t) => t.id);