diff --git a/src/base/stage/creator.ts b/src/base/stage/creator.ts index 682f078..473a2b2 100644 --- a/src/base/stage/creator.ts +++ b/src/base/stage/creator.ts @@ -119,13 +119,23 @@ export class StageCreator { * One bracket and optionally a consolation final between semi-final losers. */ private async singleElimination(): Promise { - if (Array.isArray(this.stage.settings?.seedOrdering) && + if (!this.stage.settings?.manualOrdering && Array.isArray(this.stage.settings?.seedOrdering) && this.stage.settings?.seedOrdering.length !== 1) throw Error('You must specify one seed ordering method.'); - const slots = await this.getSlots(); + let ordered: ParticipantSlot[]; + + if (this.stage.settings?.manualOrdering) { + if (this.stage.settings.manualOrdering.length !== 1) + throw Error('Manual ordering for an elimination stage must have exactly one group.'); + + ordered = await this.getSlots(this.stage.settings.manualOrdering[0]); + } else { + const slots = await this.getSlots(); + const method = this.getStandardBracketFirstRoundOrdering(); + ordered = ordering[method](slots); + } + const stage = await this.createStage(); - const method = this.getStandardBracketFirstRoundOrdering(); - const ordered = ordering[method](slots); const { losers } = await this.createStandardBracket(stage.id, 1, ordered); await this.createConsolationFinal(stage.id, losers); @@ -140,13 +150,23 @@ export class StageCreator { * between the winner of both bracket, which can be simple or double. */ private async doubleElimination(): Promise { - if (this.stage.settings && Array.isArray(this.stage.settings.seedOrdering) && + if (!this.stage.settings?.manualOrdering && this.stage.settings && Array.isArray(this.stage.settings.seedOrdering) && this.stage.settings.seedOrdering.length < 1) throw Error('You must specify at least one seed ordering method.'); - const slots = await this.getSlots(); + let ordered: ParticipantSlot[]; + + if (this.stage.settings?.manualOrdering) { + if (this.stage.settings.manualOrdering.length !== 1) + throw Error('Manual ordering for an elimination stage must have exactly one group.'); + + ordered = await this.getSlots(this.stage.settings.manualOrdering[0]); + } else { + const slots = await this.getSlots(); + const method = this.getStandardBracketFirstRoundOrdering(); + ordered = ordering[method](slots); + } + const stage = await this.createStage(); - const method = this.getStandardBracketFirstRoundOrdering(); - const ordered = ordering[method](slots); if (this.stage.settings?.skipFirstRound) await this.createDoubleEliminationSkipFirstRound(stage.id, ordered); @@ -514,6 +534,9 @@ export class StageCreator { size, // Always set the size. }; + if (positions && positions.length !== size) + throw Error('Manual ordering does not have the same length as the seeding.'); + helpers.ensureNoDuplicates(seeding); seeding = helpers.fixSeeding(seeding, size); diff --git a/src/helpers.ts b/src/helpers.ts index b68e6d1..ca5ebe9 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1347,9 +1347,6 @@ export function mapParticipantsToDatabase(prop: 'id' | 'name', seeding: Seeding, if (!positions) return slots; - if (positions.length !== slots.length) - throw Error('Not enough seeds in at least one group of the manual ordering.'); - return positions.map(position => slots[position - 1]); // Because `position` is `i + 1`. } diff --git a/test/double-elimination.spec.js b/test/double-elimination.spec.js index 1eb0402..c182521 100644 --- a/test/double-elimination.spec.js +++ b/test/double-elimination.spec.js @@ -42,6 +42,57 @@ describe('Create double elimination stage', () => { assert.strictEqual((await storage.select('match')).length, 30); }); + it('should create a double elimination stage with manual ordering', async () => { + await manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'double_elimination', + seeding: [ + 'Team 1', 'Team 2', + 'Team 3', 'Team 4', + 'Team 5', 'Team 6', + 'Team 7', 'Team 8', + ], + settings: { + manualOrdering: [[1, 8, 4, 5, 2, 7, 3, 6]], + }, + }); + + const matches = await storage.select('match'); + assert.strictEqual(matches[0].opponent1.position, 1); + assert.strictEqual(matches[0].opponent2.position, 8); + assert.strictEqual(matches[1].opponent1.position, 4); + assert.strictEqual(matches[1].opponent2.position, 5); + assert.strictEqual(matches[2].opponent1.position, 2); + assert.strictEqual(matches[2].opponent2.position, 7); + assert.strictEqual(matches[3].opponent1.position, 3); + assert.strictEqual(matches[3].opponent2.position, 6); + }); + + it('should throw if manual ordering for double elimination has more than one group', async () => { + await assert.isRejected(manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'double_elimination', + seeding: ['Team 1', 'Team 2', 'Team 3', 'Team 4'], + settings: { + manualOrdering: [[1, 4], [2, 3]], + }, + }), 'Manual ordering for an elimination stage must have exactly one group.'); + }); + + it('should throw if manual ordering for double elimination has wrong length', async () => { + await assert.isRejected(manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'double_elimination', + seeding: ['Team 1', 'Team 2', 'Team 3', 'Team 4'], + settings: { + manualOrdering: [[1, 2]], + }, + }), 'Manual ordering does not have the same length as the seeding.'); + }); + it('should create a double elimination stage with only two participants', async () => { // This is an edge case. No lower bracket nor grand final will be created. await manager.create.stage({ diff --git a/test/round-robin.spec.js b/test/round-robin.spec.js index c330581..4d668ba 100644 --- a/test/round-robin.spec.js +++ b/test/round-robin.spec.js @@ -111,7 +111,7 @@ describe('Create a round-robin stage', () => { [2, 3], ], }, - }), 'Not enough seeds in at least one group of the manual ordering.'); + }), 'Manual ordering does not have the same length as the seeding.'); }); it('should create a round-robin stage without BYE vs. BYE matches', async () => { diff --git a/test/single-elimination.spec.js b/test/single-elimination.spec.js index 071407b..79d3646 100644 --- a/test/single-elimination.spec.js +++ b/test/single-elimination.spec.js @@ -1,4 +1,7 @@ -const assert = require('chai').assert; +const chai = require('chai'); +chai.use(require('chai-as-promised')); + +const assert = chai.assert; const { Status } = require('brackets-model'); const { BracketsManager } = require('../dist'); const { JsonDatabase } = require('brackets-json-db'); @@ -41,6 +44,57 @@ describe('Create single elimination stage', () => { assert.strictEqual((await storage.select('match')).length, 15); }); + it('should create a single elimination stage with manual ordering', async () => { + await manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'single_elimination', + seeding: [ + 'Team 1', 'Team 2', + 'Team 3', 'Team 4', + 'Team 5', 'Team 6', + 'Team 7', 'Team 8', + ], + settings: { + manualOrdering: [[1, 8, 4, 5, 2, 7, 3, 6]], + }, + }); + + const matches = await storage.select('match'); + assert.strictEqual(matches[0].opponent1.position, 1); + assert.strictEqual(matches[0].opponent2.position, 8); + assert.strictEqual(matches[1].opponent1.position, 4); + assert.strictEqual(matches[1].opponent2.position, 5); + assert.strictEqual(matches[2].opponent1.position, 2); + assert.strictEqual(matches[2].opponent2.position, 7); + assert.strictEqual(matches[3].opponent1.position, 3); + assert.strictEqual(matches[3].opponent2.position, 6); + }); + + it('should throw if manual ordering for single elimination has more than one group', async () => { + await assert.isRejected(manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'single_elimination', + seeding: ['Team 1', 'Team 2', 'Team 3', 'Team 4'], + settings: { + manualOrdering: [[1, 4], [2, 3]], + }, + }), 'Manual ordering for an elimination stage must have exactly one group.'); + }); + + it('should throw if manual ordering for single elimination has wrong length', async () => { + await assert.isRejected(manager.create.stage({ + name: 'Example', + tournamentId: 0, + type: 'single_elimination', + seeding: ['Team 1', 'Team 2', 'Team 3', 'Team 4'], + settings: { + manualOrdering: [[1, 2]], + }, + }), 'Manual ordering does not have the same length as the seeding.'); + }); + it('should create a single elimination stage with BYEs', async () => { await manager.create.stage({ name: 'Example with BYEs',