mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
945 lines
24 KiB
TypeScript
945 lines
24 KiB
TypeScript
import { ordinal, rating } from "openskill";
|
|
import { describe, expect, test } from "vitest";
|
|
import invariant from "~/utils/invariant";
|
|
import type { Tables } from "../../../db/tables";
|
|
import type { AllMatchResult } from "../queries/allMatchResultsByTournamentId.server";
|
|
import { tournamentSummary } from "./summarizer.server";
|
|
import type { TournamentDataTeam } from "./Tournament.server";
|
|
|
|
describe("tournamentSummary()", () => {
|
|
const createTeam = (
|
|
teamId: number,
|
|
userIds: number[],
|
|
): TournamentDataTeam => ({
|
|
checkIns: [],
|
|
createdAt: 0,
|
|
id: teamId,
|
|
inviteCode: null,
|
|
avgSeedingSkillOrdinal: null,
|
|
startingBracketIdx: null,
|
|
mapPool: [],
|
|
members: userIds.map((userId) => ({
|
|
country: null,
|
|
customUrl: null,
|
|
discordAvatar: null,
|
|
discordId: "123",
|
|
username: "test",
|
|
inGameName: "test",
|
|
twitch: null,
|
|
isOwner: 0,
|
|
plusTier: null,
|
|
createdAt: 0,
|
|
userId,
|
|
})),
|
|
name: `Team ${teamId}`,
|
|
prefersNotToHost: 0,
|
|
droppedOut: 0,
|
|
team: null,
|
|
seed: 1,
|
|
activeRosterUserIds: [],
|
|
pickupAvatarUrl: null,
|
|
});
|
|
|
|
function summarize({
|
|
results,
|
|
seedingSkillCountsFor,
|
|
withMemberInTwoTeams = false,
|
|
teamsWithStartingBrackets,
|
|
progression,
|
|
finalStandings,
|
|
}: {
|
|
results?: AllMatchResult[];
|
|
seedingSkillCountsFor?: Tables["SeedingSkill"]["type"];
|
|
withMemberInTwoTeams?: boolean;
|
|
teamsWithStartingBrackets?: Array<{
|
|
id: number;
|
|
startingBracketIdx: number | null;
|
|
}>;
|
|
progression?: Array<{
|
|
name: string;
|
|
type: "single_elimination";
|
|
settings: Record<string, never>;
|
|
requiresCheckIn: boolean;
|
|
sources?: Array<{ bracketIdx: number; placements: number[] }>;
|
|
}>;
|
|
finalStandings?: Array<{
|
|
placement: number;
|
|
team: TournamentDataTeam;
|
|
}>;
|
|
} = {}) {
|
|
const defaultTeams = [
|
|
{
|
|
id: 1,
|
|
members: [
|
|
{ userId: 1 },
|
|
{ userId: 2 },
|
|
{ userId: 3 },
|
|
{ userId: 4 },
|
|
{ userId: 20 },
|
|
],
|
|
},
|
|
{
|
|
id: 2,
|
|
members: [{ userId: 5 }, { userId: 6 }, { userId: 7 }, { userId: 8 }],
|
|
},
|
|
{
|
|
id: 3,
|
|
members: [
|
|
{ userId: 9 },
|
|
{ userId: 10 },
|
|
{ userId: 11 },
|
|
{ userId: 12 },
|
|
],
|
|
},
|
|
{
|
|
id: 4,
|
|
members: [
|
|
{ userId: 13 },
|
|
{ userId: 14 },
|
|
{ userId: 15 },
|
|
{ userId: 16 },
|
|
],
|
|
},
|
|
];
|
|
|
|
const teams = teamsWithStartingBrackets
|
|
? defaultTeams.map((team) => {
|
|
const startingBracket = teamsWithStartingBrackets.find(
|
|
(t) => t.id === team.id,
|
|
);
|
|
return {
|
|
...team,
|
|
startingBracketIdx: startingBracket?.startingBracketIdx ?? null,
|
|
};
|
|
})
|
|
: defaultTeams;
|
|
|
|
return tournamentSummary({
|
|
finalStandings: finalStandings ?? [
|
|
{
|
|
placement: 1,
|
|
team: createTeam(
|
|
1,
|
|
withMemberInTwoTeams ? [1, 2, 3, 4, 5] : [1, 2, 3, 4],
|
|
),
|
|
},
|
|
{
|
|
placement: 2,
|
|
team: createTeam(2, [5, 6, 7, 8]),
|
|
},
|
|
{
|
|
placement: 3,
|
|
team: createTeam(3, [9, 10, 11, 12]),
|
|
},
|
|
{
|
|
placement: 4,
|
|
team: createTeam(4, [13, 14, 15, 16]),
|
|
},
|
|
],
|
|
results: results ?? [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 2,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
],
|
|
teams,
|
|
queryCurrentTeamRating: () => rating(),
|
|
queryCurrentUserRating: () => ({ rating: rating(), matchesCount: 0 }),
|
|
queryTeamPlayerRatingAverage: () => rating(),
|
|
queryCurrentSeedingRating: () => rating(),
|
|
seedingSkillCountsFor: seedingSkillCountsFor ?? null,
|
|
progression: progression ?? [
|
|
{
|
|
name: "Main Bracket",
|
|
type: "single_elimination",
|
|
settings: {},
|
|
requiresCheckIn: false,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
test("calculates final standings", () => {
|
|
const summary = summarize();
|
|
expect(summary.tournamentResults.length).toBe(4 * 4);
|
|
});
|
|
|
|
test("calculates final standings, handling a player in two teams", () => {
|
|
const summary = summarize({ withMemberInTwoTeams: true });
|
|
expect(
|
|
summary.tournamentResults.some(
|
|
(result) => result.tournamentTeamId === 1 && result.userId === 5,
|
|
),
|
|
).toBeTruthy();
|
|
expect(summary.tournamentResults.length).toBe(4 * 4);
|
|
});
|
|
|
|
test("winners skill should go up, losers skill should go down", () => {
|
|
const summary = summarize();
|
|
const winnerSkill = summary.skills.find((s) => s.userId === 1);
|
|
const loserSkill = summary.skills.find((s) => s.userId === 5);
|
|
|
|
invariant(winnerSkill, "winnerSkill should be defined");
|
|
invariant(loserSkill, "loserSkill should be defined");
|
|
expect(ordinal(winnerSkill)).toBeGreaterThan(ordinal(loserSkill));
|
|
});
|
|
|
|
test("seeding skill is calculated the same as normal skill", () => {
|
|
const summary = summarize({ seedingSkillCountsFor: "RANKED" });
|
|
const winnerSkill = summary.skills.find((s) => s.userId === 1);
|
|
const winnerSeedingSkill = summary.skills.find((s) => s.userId === 1);
|
|
|
|
invariant(winnerSkill, "winnerSkill should be defined");
|
|
invariant(winnerSeedingSkill, "winnerSeedingSkill should be defined");
|
|
|
|
expect(ordinal(winnerSkill)).toBe(ordinal(winnerSeedingSkill));
|
|
});
|
|
|
|
test("no seeding skill calculated if seedingSkillCountsFor is null", () => {
|
|
const summary = summarize();
|
|
|
|
expect(summary.seedingSkills.length).toBe(0);
|
|
});
|
|
|
|
test("seeding skills type matches the given seedingSkillCountsFor", () => {
|
|
const summary = summarize({ seedingSkillCountsFor: "RANKED" });
|
|
expect(summary.seedingSkills[0].type).toBe("RANKED");
|
|
|
|
const summary2 = summarize({ seedingSkillCountsFor: "UNRANKED" });
|
|
expect(summary2.seedingSkills[0].type).toBe("UNRANKED");
|
|
});
|
|
|
|
const resultsWith20: AllMatchResult[] = [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 2,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 2,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
];
|
|
|
|
test("winning more than once makes the skill go up more", () => {
|
|
const summary = summarize({
|
|
results: resultsWith20,
|
|
});
|
|
const twoTimeWinnerSkill = summary.skills.find((s) => s.userId === 1);
|
|
const oneTimeWinnerSkill = summary.skills.find((s) => s.userId === 2);
|
|
|
|
invariant(twoTimeWinnerSkill, "twoTimeWinnerSkill should be defined");
|
|
invariant(oneTimeWinnerSkill, "oneTimeWinnerSkill should be defined");
|
|
expect(ordinal(twoTimeWinnerSkill)).toBeGreaterThan(
|
|
ordinal(oneTimeWinnerSkill),
|
|
);
|
|
});
|
|
|
|
test("calculates team skills (many rosters for same team)", () => {
|
|
const summary = summarize({
|
|
results: resultsWith20,
|
|
});
|
|
const teamOneRosterOne = summary.skills.find(
|
|
(s) => s.identifier === "1-2-3-4",
|
|
);
|
|
const teamOneRosterTwo = summary.skills.find(
|
|
(s) => s.identifier === "1-3-4-20",
|
|
);
|
|
expect(teamOneRosterOne).toBeTruthy();
|
|
expect(teamOneRosterTwo).toBeTruthy();
|
|
});
|
|
|
|
const resultsWithSubbedRoster: AllMatchResult[] = [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 2,
|
|
},
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 2,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 1,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
];
|
|
|
|
test("In the case of sub calculates skill based on the most common roster", () => {
|
|
const summary = summarize({
|
|
results: resultsWithSubbedRoster,
|
|
});
|
|
const teamOneRosterOne = summary.skills.find(
|
|
(s) => s.identifier === "1-2-3-4",
|
|
);
|
|
const teamOneRosterTwo = summary.skills.find(
|
|
(s) => s.identifier === "1-3-4-20",
|
|
);
|
|
expect(teamOneRosterOne).toBeTruthy();
|
|
expect(teamOneRosterTwo).toBeFalsy();
|
|
});
|
|
|
|
test("In the case of sub calculates player results based on the most common roster", () => {
|
|
const summary = summarize({
|
|
results: resultsWithSubbedRoster,
|
|
});
|
|
expect(
|
|
summary.playerResultDeltas.find(
|
|
(p) =>
|
|
p.ownerUserId === 5 &&
|
|
p.otherUserId === 20 &&
|
|
(p.setWins > 0 || p.setLosses > 0),
|
|
),
|
|
).toBeFalsy();
|
|
});
|
|
|
|
test("calculates results of mates", () => {
|
|
const summary = summarize();
|
|
const result = summary.playerResultDeltas.find(
|
|
(r) => r.ownerUserId === 1 && r.otherUserId === 2,
|
|
);
|
|
|
|
invariant(result, "result should be defined");
|
|
expect(result.setWins).toBe(1);
|
|
expect(result.setLosses).toBe(0);
|
|
expect(result.mapWins).toBe(2);
|
|
expect(result.mapLosses).toBe(0);
|
|
expect(result.type).toBe("MATE");
|
|
});
|
|
|
|
test("calculates results of opponents", () => {
|
|
const summary = summarize();
|
|
const result = summary.playerResultDeltas.find(
|
|
(r) => r.ownerUserId === 1 && r.otherUserId === 5,
|
|
);
|
|
|
|
invariant(result, "result should be defined");
|
|
expect(result.setWins).toBe(1);
|
|
expect(result.setLosses).toBe(0);
|
|
expect(result.mapWins).toBe(2);
|
|
expect(result.mapLosses).toBe(0);
|
|
expect(result.type).toBe("ENEMY");
|
|
});
|
|
|
|
test("calculates results of opponents (losing side)", () => {
|
|
const summary = summarize();
|
|
const result = summary.playerResultDeltas.find(
|
|
(r) => r.ownerUserId === 5 && r.otherUserId === 1,
|
|
);
|
|
|
|
invariant(result, "result should be defined");
|
|
expect(result.setWins).toBe(0);
|
|
expect(result.setLosses).toBe(1);
|
|
expect(result.mapWins).toBe(0);
|
|
expect(result.mapLosses).toBe(2);
|
|
expect(result.type).toBe("ENEMY");
|
|
});
|
|
|
|
test("calculates map results", () => {
|
|
const summary = summarize();
|
|
const result = summary.mapResultDeltas.filter((r) => r.userId === 1);
|
|
expect(result.length).toBe(2);
|
|
expect(result.every((r) => r.wins === 1 && r.losses === 0)).toBeTruthy();
|
|
});
|
|
|
|
test("calculates set results array", () => {
|
|
const summary = summarize();
|
|
|
|
const winner = summary.setResults.get(1);
|
|
const loser = summary.setResults.get(5);
|
|
const sub = summary.setResults.get(20);
|
|
|
|
invariant(winner, "winner should be defined");
|
|
invariant(loser, "loser should be defined");
|
|
invariant(sub, "sub should be defined");
|
|
|
|
expect(winner).toEqual(["W"]);
|
|
expect(loser).toEqual(["L"]);
|
|
expect(sub).toEqual([null]);
|
|
});
|
|
|
|
test("playing for many teams should include combined sets in the set results array", () => {
|
|
const summary = summarize({
|
|
withMemberInTwoTeams: true,
|
|
results: resultsWith20,
|
|
});
|
|
|
|
const results = summary.setResults.get(20);
|
|
|
|
// only sub for the first team (null) and winning for the second team (W)
|
|
expect(results).toEqual([null, "W"]);
|
|
});
|
|
|
|
test("playing minority of maps in a set should not be count for set results", () => {
|
|
const summary = summarize({
|
|
results: [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 3,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const results = summary.setResults.get(1);
|
|
expect(results).toEqual([null]);
|
|
});
|
|
|
|
test("playing in half the maps should be enough to count for set results", () => {
|
|
const summary = summarize({
|
|
results: [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 20 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 2,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
for (const userId of [1, 20]) {
|
|
const results = summary.setResults.get(userId);
|
|
invariant(results, `results for user ${userId} should be defined`);
|
|
expect(results).toEqual(["W"]);
|
|
}
|
|
});
|
|
|
|
test("div is null when teams have no startingBracketIdx", () => {
|
|
const summary = summarize();
|
|
|
|
for (const result of summary.tournamentResults) {
|
|
expect(result.div).toBeNull();
|
|
}
|
|
});
|
|
|
|
test("div is set correctly for teams with startingBracketIdx", () => {
|
|
const summary = summarize({
|
|
teamsWithStartingBrackets: [
|
|
{ id: 1, startingBracketIdx: 0 },
|
|
{ id: 2, startingBracketIdx: 1 },
|
|
{ id: 3, startingBracketIdx: 0 },
|
|
{ id: 4, startingBracketIdx: 1 },
|
|
],
|
|
progression: [
|
|
{
|
|
name: "Division 1",
|
|
type: "single_elimination",
|
|
settings: {},
|
|
requiresCheckIn: false,
|
|
},
|
|
{
|
|
name: "Division 2",
|
|
type: "single_elimination",
|
|
settings: {},
|
|
requiresCheckIn: false,
|
|
},
|
|
],
|
|
finalStandings: [
|
|
{
|
|
placement: 1,
|
|
team: createTeam(1, [1, 2, 3, 4]),
|
|
},
|
|
{
|
|
placement: 1,
|
|
team: createTeam(2, [5, 6, 7, 8]),
|
|
},
|
|
{
|
|
placement: 2,
|
|
team: createTeam(3, [9, 10, 11, 12]),
|
|
},
|
|
{
|
|
placement: 2,
|
|
team: createTeam(4, [13, 14, 15, 16]),
|
|
},
|
|
],
|
|
});
|
|
|
|
const team1Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 1,
|
|
);
|
|
const team2Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 2,
|
|
);
|
|
|
|
expect(team1Results.every((r) => r.div === "Division 1")).toBeTruthy();
|
|
expect(team2Results.every((r) => r.div === "Division 2")).toBeTruthy();
|
|
});
|
|
|
|
test("participantCount is correct for multi-division tournaments", () => {
|
|
const summary = summarize({
|
|
teamsWithStartingBrackets: [
|
|
{ id: 1, startingBracketIdx: 0 },
|
|
{ id: 2, startingBracketIdx: 1 },
|
|
{ id: 3, startingBracketIdx: 0 },
|
|
{ id: 4, startingBracketIdx: 1 },
|
|
],
|
|
progression: [
|
|
{
|
|
name: "Division 1",
|
|
type: "single_elimination",
|
|
settings: {},
|
|
requiresCheckIn: false,
|
|
},
|
|
{
|
|
name: "Division 2",
|
|
type: "single_elimination",
|
|
settings: {},
|
|
requiresCheckIn: false,
|
|
},
|
|
],
|
|
finalStandings: [
|
|
{
|
|
placement: 1,
|
|
team: createTeam(1, [1, 2, 3, 4]),
|
|
},
|
|
{
|
|
placement: 1,
|
|
team: createTeam(2, [5, 6, 7, 8]),
|
|
},
|
|
{
|
|
placement: 2,
|
|
team: createTeam(3, [9, 10, 11, 12]),
|
|
},
|
|
{
|
|
placement: 2,
|
|
team: createTeam(4, [13, 14, 15, 16]),
|
|
},
|
|
],
|
|
});
|
|
|
|
const team1Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 1,
|
|
);
|
|
const team2Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 2,
|
|
);
|
|
const team3Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 3,
|
|
);
|
|
const team4Results = summary.tournamentResults.filter(
|
|
(r) => r.tournamentTeamId === 4,
|
|
);
|
|
|
|
expect(team1Results.every((r) => r.participantCount === 2)).toBeTruthy();
|
|
expect(team2Results.every((r) => r.participantCount === 2)).toBeTruthy();
|
|
expect(team3Results.every((r) => r.participantCount === 2)).toBeTruthy();
|
|
expect(team4Results.every((r) => r.participantCount === 2)).toBeTruthy();
|
|
});
|
|
|
|
test("excludes matches ended early by organizer from calculations", () => {
|
|
const summary = summarize({
|
|
results: [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 0,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(summary.skills.length).toBe(0);
|
|
expect(summary.mapResultDeltas.length).toBe(0);
|
|
expect(summary.playerResultDeltas.length).toBe(0);
|
|
});
|
|
|
|
test("includes normal matches but excludes early-ended matches from calculations", () => {
|
|
const summary = summarize({
|
|
results: [
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "SZ",
|
|
stageId: 1,
|
|
participants: [
|
|
{ tournamentTeamId: 1, userId: 1 },
|
|
{ tournamentTeamId: 1, userId: 2 },
|
|
{ tournamentTeamId: 1, userId: 3 },
|
|
{ tournamentTeamId: 1, userId: 4 },
|
|
{ tournamentTeamId: 2, userId: 5 },
|
|
{ tournamentTeamId: 2, userId: 6 },
|
|
{ tournamentTeamId: 2, userId: 7 },
|
|
{ tournamentTeamId: 2, userId: 8 },
|
|
],
|
|
winnerTeamId: 1,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 1,
|
|
result: "win",
|
|
score: 1,
|
|
},
|
|
opponentTwo: {
|
|
id: 2,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
{
|
|
maps: [
|
|
{
|
|
mode: "TC",
|
|
stageId: 2,
|
|
participants: [
|
|
{ tournamentTeamId: 3, userId: 9 },
|
|
{ tournamentTeamId: 3, userId: 10 },
|
|
{ tournamentTeamId: 3, userId: 11 },
|
|
{ tournamentTeamId: 3, userId: 12 },
|
|
{ tournamentTeamId: 4, userId: 13 },
|
|
{ tournamentTeamId: 4, userId: 14 },
|
|
{ tournamentTeamId: 4, userId: 15 },
|
|
{ tournamentTeamId: 4, userId: 16 },
|
|
],
|
|
winnerTeamId: 3,
|
|
},
|
|
],
|
|
opponentOne: {
|
|
id: 3,
|
|
result: "win",
|
|
score: 0,
|
|
},
|
|
opponentTwo: {
|
|
id: 4,
|
|
result: "loss",
|
|
score: 0,
|
|
},
|
|
roundMaps: {
|
|
count: 3,
|
|
type: "BEST_OF",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const skillsFromTeam1 = summary.skills.filter((s) =>
|
|
[1, 2, 3, 4].includes(s.userId ?? 0),
|
|
);
|
|
const skillsFromTeam2 = summary.skills.filter((s) =>
|
|
[5, 6, 7, 8].includes(s.userId ?? 0),
|
|
);
|
|
const skillsFromTeam3 = summary.skills.filter((s) =>
|
|
[9, 10, 11, 12].includes(s.userId ?? 0),
|
|
);
|
|
const skillsFromTeam4 = summary.skills.filter((s) =>
|
|
[13, 14, 15, 16].includes(s.userId ?? 0),
|
|
);
|
|
|
|
expect(skillsFromTeam1.length).toBe(0);
|
|
expect(skillsFromTeam2.length).toBe(0);
|
|
expect(skillsFromTeam3.length).toBe(0);
|
|
expect(skillsFromTeam4.length).toBe(0);
|
|
});
|
|
});
|