sendou.ink/app/features/tournament-bracket/core/summarizer.test.ts
Kalle eae3d529e2
Bracket component rewrite (#1653)
* Remove old code

* Add prefetching

* Elim bracket initial

* Hide rounds with only byes

* Round hiding logic

* Align stuff

* Add TODO

* Adjustments

* Deadline

* Compactify button

* Simulations

* Round robin bracket initial

* eventId -> tournamentId

* seedByTeamId removed

* Couple more TODOs

* RR placements table

* Locking matches

* Extract TournamentStream component

* Bracket streams

* Remove extras for tournament-manager, misc

* Fix E2E tests

* Fix SKALOP_SYSTEM_MESSAGE_URL in env.example

* TODOs

* TODO moved to GitHub

* Handle team changing in match cache invalidation

* Fix streamer seeing undo last score button

* Show "Sub" badge on team roster page

* Show who didn't play yet on match teams preview

* Ranked/unranked badge

* Bracket hover show roster

* Add lock/unlock match test

* Fix score reporting
2024-02-11 10:49:12 +02:00

359 lines
7.9 KiB
TypeScript

import { suite } from "uvu";
import * as assert from "uvu/assert";
import { tournamentSummary } from "./summarizer.server";
import { ordinal, rating } from "openskill";
import type { AllMatchResult } from "../queries/allMatchResultsByTournamentId.server";
import type { TournamentDataTeam } from "./Tournament.server";
const TournamentSummary = suite("tournamentSummary()");
const createTeam = (teamId: number, userIds: number[]): TournamentDataTeam => ({
checkIns: [],
createdAt: 0,
id: teamId,
inviteCode: null,
mapPool: [],
members: userIds.map((userId) => ({
country: null,
customUrl: null,
discordAvatar: null,
discordId: "123",
discordName: "test",
inGameName: "test",
isOwner: 0,
plusTier: null,
createdAt: 0,
userId,
})),
name: "Team " + teamId,
prefersNotToHost: 0,
seed: 1,
});
function summarize({ results }: { results?: AllMatchResult[] } = {}) {
return tournamentSummary({
finalStandings: [
{
placement: 1,
team: createTeam(1, [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,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
],
teams: [
{
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 },
],
},
],
queryCurrentTeamRating: () => rating(),
queryCurrentUserRating: () => rating(),
queryTeamPlayerRatingAverage: () => rating(),
});
}
TournamentSummary("calculates final standings", () => {
const summary = summarize();
// each player of each team should have one result
assert.equal(summary.tournamentResults.length, 4 * 4);
});
TournamentSummary(
"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);
assert.ok(winnerSkill);
assert.ok(loserSkill);
assert.ok(ordinal(winnerSkill) > ordinal(loserSkill));
},
);
const resultsWith20: AllMatchResult[] = [
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 0,
},
},
];
TournamentSummary("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);
assert.ok(twoTimeWinnerSkill);
assert.ok(oneTimeWinnerSkill);
assert.ok(ordinal(twoTimeWinnerSkill) > ordinal(oneTimeWinnerSkill));
});
TournamentSummary("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",
);
assert.ok(teamOneRosterOne);
assert.ok(teamOneRosterTwo);
});
const resultsWithSubbedRoster: AllMatchResult[] = [
{
maps: [
{
mode: "SZ",
stageId: 1,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 2, 3, 4, 5, 6, 7, 8],
winnerTeamId: 2,
},
{
mode: "TC",
stageId: 2,
userIds: [1, 20, 3, 4, 5, 6, 7, 8],
winnerTeamId: 1,
},
],
opponentOne: {
id: 1,
result: "win",
score: 2,
},
opponentTwo: {
id: 2,
result: "loss",
score: 1,
},
},
];
TournamentSummary(
"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",
);
assert.ok(teamOneRosterOne);
assert.not.ok(teamOneRosterTwo);
},
);
TournamentSummary(
"In the case of sub calculates player results based on the most common roster",
() => {
const summary = summarize({
results: resultsWithSubbedRoster,
});
assert.not.ok(
summary.playerResultDeltas.find(
(p) =>
p.ownerUserId === 5 &&
p.otherUserId === 20 &&
(p.setWins > 0 || p.setLosses > 0),
),
"player 5 should not have a result against player 20 (sub for only one game)",
);
},
);
TournamentSummary("calculates results of mates", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 1 && r.otherUserId === 2,
);
assert.ok(result);
assert.equal(result.setWins, 1);
assert.equal(result.setLosses, 0);
assert.equal(result.mapWins, 2);
assert.equal(result.mapLosses, 0);
assert.equal(result.type, "MATE");
});
TournamentSummary("calculates results of opponents", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 1 && r.otherUserId === 5,
);
assert.ok(result);
assert.equal(result.setWins, 1);
assert.equal(result.setLosses, 0);
assert.equal(result.mapWins, 2);
assert.equal(result.mapLosses, 0);
assert.equal(result.type, "ENEMY");
});
TournamentSummary("calculates results of opponents (losing side)", () => {
const summary = summarize();
const result = summary.playerResultDeltas.find(
(r) => r.ownerUserId === 5 && r.otherUserId === 1,
);
assert.ok(result);
assert.equal(result.setWins, 0);
assert.equal(result.setLosses, 1);
assert.equal(result.mapWins, 0);
assert.equal(result.mapLosses, 2);
assert.equal(result.type, "ENEMY");
});
TournamentSummary("calculates map results", () => {
const summary = summarize();
const result = summary.mapResultDeltas.filter((r) => r.userId === 1);
assert.equal(result.length, 2);
assert.ok(result.every((r) => r.wins === 1 && r.losses === 0));
});
TournamentSummary.run();