mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-23 11:57:50 -05:00
831 lines
15 KiB
TypeScript
831 lines
15 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import type { PreparedMaps as PreparedMapsType } from "~/db/tables";
|
|
import * as PreparedMaps from "./PreparedMaps";
|
|
import { testTournament } from "./tests/test-utils";
|
|
|
|
const getTestTournament = (thirdPlaceMatchesForBoth = true) =>
|
|
testTournament({
|
|
ctx: {
|
|
settings: {
|
|
bracketProgression: [
|
|
{
|
|
type: "round_robin",
|
|
name: "Round Robin",
|
|
requiresCheckIn: false,
|
|
settings: {},
|
|
sources: [],
|
|
},
|
|
{
|
|
type: "single_elimination",
|
|
name: "Top Cut",
|
|
requiresCheckIn: false,
|
|
settings: {
|
|
thirdPlaceMatch: true,
|
|
},
|
|
sources: [
|
|
{
|
|
bracketIdx: 0,
|
|
placements: [1, 2],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "single_elimination",
|
|
name: "Underground Bracket",
|
|
requiresCheckIn: false,
|
|
settings: {
|
|
thirdPlaceMatch: thirdPlaceMatchesForBoth,
|
|
},
|
|
sources: [
|
|
{
|
|
bracketIdx: 0,
|
|
placements: [3, 4],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
describe("PreparedMaps - resolvePreparedForTheBracket", () => {
|
|
const tournament = getTestTournament();
|
|
|
|
test("returns null if no prepared maps at all", () => {
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament,
|
|
bracketIdx: 1,
|
|
});
|
|
|
|
expect(prepared).toBeNull();
|
|
});
|
|
|
|
test("returns null if no prepared maps for that bracket", () => {
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament,
|
|
bracketIdx: 1,
|
|
preparedByBracket: [
|
|
{
|
|
authorId: 1,
|
|
createdAt: 1,
|
|
maps: [],
|
|
},
|
|
null,
|
|
null,
|
|
],
|
|
});
|
|
|
|
expect(prepared).toBeNull();
|
|
});
|
|
|
|
test("returns prepared maps for that bracket if exists", () => {
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament,
|
|
bracketIdx: 1,
|
|
preparedByBracket: [
|
|
null,
|
|
{
|
|
authorId: 1,
|
|
createdAt: 1,
|
|
maps: [],
|
|
},
|
|
null,
|
|
],
|
|
});
|
|
|
|
expect(prepared).not.toBeNull();
|
|
});
|
|
|
|
test("returns 'sibling bracket' prepared maps if exists", () => {
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament,
|
|
bracketIdx: 1,
|
|
preparedByBracket: [
|
|
null,
|
|
null,
|
|
{
|
|
authorId: 1,
|
|
createdAt: 1,
|
|
maps: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(prepared).not.toBeNull();
|
|
});
|
|
|
|
test("returns null if the sibling does not have third place match while this one does", () => {
|
|
const tournament = getTestTournament(false);
|
|
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament,
|
|
bracketIdx: 1,
|
|
preparedByBracket: [
|
|
null,
|
|
null,
|
|
{
|
|
authorId: 1,
|
|
createdAt: 1,
|
|
maps: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(prepared).toBeNull();
|
|
});
|
|
|
|
test("multiple starting brackets (RR) feeding into SE brackets at same depth should share maps", () => {
|
|
const tournamentWithTwoStartingBrackets = testTournament({
|
|
ctx: {
|
|
settings: {
|
|
bracketProgression: [
|
|
{
|
|
type: "round_robin",
|
|
name: "Group A",
|
|
requiresCheckIn: false,
|
|
settings: {},
|
|
},
|
|
{
|
|
type: "round_robin",
|
|
name: "Group B",
|
|
requiresCheckIn: false,
|
|
settings: {},
|
|
},
|
|
{
|
|
type: "single_elimination",
|
|
name: "SE from Group A",
|
|
requiresCheckIn: false,
|
|
settings: {},
|
|
sources: [
|
|
{
|
|
bracketIdx: 0,
|
|
placements: [1, 2],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "single_elimination",
|
|
name: "SE from Group B",
|
|
requiresCheckIn: false,
|
|
settings: {},
|
|
sources: [
|
|
{
|
|
bracketIdx: 1,
|
|
placements: [1, 2],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
const prepared = PreparedMaps.resolvePreparedForTheBracket({
|
|
tournament: tournamentWithTwoStartingBrackets,
|
|
bracketIdx: 3,
|
|
preparedByBracket: [
|
|
null,
|
|
null,
|
|
{ authorId: 1, createdAt: 1, maps: [] },
|
|
null,
|
|
],
|
|
});
|
|
|
|
expect(prepared).not.toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("PreparedMaps - eliminationTeamCountOptions", () => {
|
|
test("returns options greater than the count given", () => {
|
|
expect(
|
|
PreparedMaps.eliminationTeamCountOptions(3).every(
|
|
(option) => option.max > 3,
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("returns the option equivalent to the current count", () => {
|
|
expect(PreparedMaps.eliminationTeamCountOptions(32)[0].max).toBe(32);
|
|
});
|
|
});
|
|
|
|
describe("PreparedMaps - trimPreparedEliminationMaps", () => {
|
|
const tournament = testTournament({
|
|
ctx: {
|
|
settings: {
|
|
bracketProgression: [
|
|
{
|
|
type: "single_elimination",
|
|
settings: { thirdPlaceMatch: true },
|
|
name: "X",
|
|
requiresCheckIn: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
test("returns null if no prepared maps", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: null,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed).toBeNull();
|
|
});
|
|
|
|
test("returns null if didn't prepare for enough teams", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: FOUR_TEAM_SE_PREPARED,
|
|
teamCount: 8,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed).toBeNull();
|
|
});
|
|
|
|
test("returns null if no elimination team count recorded", () => {
|
|
const copy = structuredClone(FOUR_TEAM_SE_PREPARED);
|
|
delete copy.eliminationTeamCount;
|
|
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: copy,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed).toBeNull();
|
|
});
|
|
|
|
test("returns the maps untouched if no need to trim", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: FOUR_TEAM_SE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed).toBe(FOUR_TEAM_SE_PREPARED);
|
|
});
|
|
|
|
test("returns trimmed if third place match disappeared", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: FOUR_TEAM_SE_PREPARED,
|
|
teamCount: 3,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed?.maps.length).toBe(FOUR_TEAM_SE_PREPARED.maps.length - 1);
|
|
expect(trimmed?.maps.some((m) => m.groupId === 1)).toBe(false);
|
|
});
|
|
|
|
test("trims the maps (SE - 1 extra round)", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_SE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed?.maps.length).toBe(EIGHT_TEAM_SE_PREPARED.maps.length - 1);
|
|
});
|
|
|
|
test("trimming happens from the earlier rounds, not the latest", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_SE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed?.maps[0].list?.[0].stageId).toBe(
|
|
// biome-ignore lint/suspicious/noNonNullAssertedOptionalChain: Biome 2.3.1 upgrade
|
|
EIGHT_TEAM_SE_PREPARED.maps[1].list?.[0].stageId!,
|
|
);
|
|
});
|
|
|
|
test("trimmed rounds have the same round ids (SE)", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_SE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
const actualBracket = tournament
|
|
.bracketByIdx(0)!
|
|
.generateMatchesData([1, 2, 3, 4]);
|
|
|
|
for (const round of actualBracket.round) {
|
|
expect(
|
|
trimmed!.maps.some((map) => map.roundId === round.id),
|
|
`Round ID ${round.id} not found in the actual bracket`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("trimmed rounds start with round id 0", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_SE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed?.maps[0].roundId).toBe(0);
|
|
});
|
|
|
|
test("trims the maps (SE - disappearing 3rd place match)", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_SE_PREPARED,
|
|
teamCount: 3,
|
|
bracket: tournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
expect(trimmed?.maps.length).toBe(EIGHT_TEAM_SE_PREPARED.maps.length - 2);
|
|
|
|
const uniqueGroupIds = new Set(trimmed?.maps.map((map) => map.groupId));
|
|
|
|
expect(uniqueGroupIds.size).toBe(1);
|
|
});
|
|
|
|
const doubleEliminationTournament = testTournament({
|
|
ctx: {
|
|
settings: {
|
|
bracketProgression: [
|
|
{
|
|
type: "double_elimination",
|
|
settings: { thirdPlaceMatch: true },
|
|
name: "X",
|
|
requiresCheckIn: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
test("trims the maps (DE - both winners and losers)", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_DE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: doubleEliminationTournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
const expectedWinnersCount = 2;
|
|
const expectedLosersCount = 2;
|
|
const expectedFinalsCount = 2;
|
|
|
|
expect(
|
|
trimmed?.maps.filter((m) => m.groupId === 0).length,
|
|
"Winners count is wrong",
|
|
).toBe(expectedWinnersCount);
|
|
expect(
|
|
trimmed?.maps.filter((m) => m.groupId === 1).length,
|
|
"Losers count is wrong",
|
|
).toBe(expectedLosersCount);
|
|
expect(
|
|
trimmed?.maps.filter((m) => m.groupId === 2).length,
|
|
"Finals count is wrong",
|
|
).toBe(expectedFinalsCount);
|
|
});
|
|
|
|
test("trimmed rounds have the same round ids (DE)", () => {
|
|
const trimmed = PreparedMaps.trimPreparedEliminationMaps({
|
|
preparedMaps: EIGHT_TEAM_DE_PREPARED,
|
|
teamCount: 4,
|
|
bracket: doubleEliminationTournament.bracketByIdx(0)!,
|
|
});
|
|
|
|
const actualBracket = doubleEliminationTournament
|
|
.bracketByIdx(0)!
|
|
.generateMatchesData([1, 2, 3, 4]);
|
|
|
|
for (const round of actualBracket.round) {
|
|
expect(
|
|
trimmed!.maps.some((map) => map.roundId === round.id),
|
|
`Round ID ${round.id} not found in the actual bracket`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
const FOUR_TEAM_SE_PREPARED: PreparedMapsType = {
|
|
maps: [
|
|
{
|
|
roundId: 0,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "TC",
|
|
stageId: 10,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 4,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 18,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 13,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 1,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 1,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 16,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 21,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 2,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 12,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 14,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 2,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "TC",
|
|
stageId: 3,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 0,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 7,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 15,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 6,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
],
|
|
authorId: 274,
|
|
eliminationTeamCount: 4,
|
|
createdAt: 1724481143,
|
|
};
|
|
|
|
const EIGHT_TEAM_SE_PREPARED: PreparedMapsType = {
|
|
maps: [
|
|
{
|
|
roundId: 0,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 0,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 21,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 2,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 1,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "RM",
|
|
stageId: 3,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 18,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 13,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 1,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 4,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 2,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 15,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 6,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 10,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 12,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 16,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 3,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 14,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 7,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 19,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 2,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 8,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
],
|
|
authorId: 274,
|
|
eliminationTeamCount: 8,
|
|
createdAt: 1724481176,
|
|
};
|
|
|
|
const EIGHT_TEAM_DE_PREPARED: PreparedMapsType = {
|
|
maps: [
|
|
{
|
|
roundId: 0,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 18,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 0,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 10,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 3,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 13,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 1,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 2,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 1,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "RM",
|
|
stageId: 4,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 8,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 21,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 4,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "TC",
|
|
stageId: 6,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 12,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 7,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 2,
|
|
groupId: 0,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 16,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 18,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 3,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 0,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 14,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 5,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "CB",
|
|
stageId: 15,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 19,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 10,
|
|
},
|
|
],
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 6,
|
|
groupId: 1,
|
|
list: [
|
|
{
|
|
mode: "RM",
|
|
stageId: 2,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 1,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 8,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 13,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 4,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 7,
|
|
groupId: 2,
|
|
list: [
|
|
{
|
|
mode: "RM",
|
|
stageId: 6,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 21,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 3,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 14,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 12,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
{
|
|
roundId: 8,
|
|
groupId: 2,
|
|
list: [
|
|
{
|
|
mode: "TC",
|
|
stageId: 10,
|
|
},
|
|
{
|
|
mode: "RM",
|
|
stageId: 18,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 7,
|
|
},
|
|
{
|
|
mode: "CB",
|
|
stageId: 0,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 19,
|
|
},
|
|
],
|
|
count: 5,
|
|
type: "BEST_OF",
|
|
},
|
|
],
|
|
authorId: 274,
|
|
eliminationTeamCount: 8,
|
|
createdAt: 1724482944,
|
|
};
|
|
});
|