sendou.ink/e2e/tournament.spec.ts
Kalle ef78d3a2c2
Tournament full (#1373)
* Got something going

* Style overwrites

* width != height

* More playing with lines

* Migrations

* Start bracket initial

* Unhardcode stage generation params

* Link to match page

* Matches page initial

* Support directly adding seed to map list generator

* Add docs

* Maps in matches page

* Add invariant about tie breaker map pool

* Fix PICNIC lacking tie breaker maps

* Only link in bracket when tournament has started

* Styled tournament roster inputs

* Prefer IGN in tournament match page

* ModeProgressIndicator

* Some conditional rendering

* Match action initial + better error display

* Persist bestOf in DB

* Resolve best of ahead of time

* Move brackets-manager to core

* Score reporting works

* Clear winner on score report

* ModeProgressIndicator: highlight winners

* Fix inconsistent input

* Better text when submitting match

* mapCountPlayedInSetWithCertainty that works

* UNDO_REPORT_SCORE implemented

* Permission check when starting tournament

* Remove IGN from upsert

* View match results page

* Source in DB

* Match page waiting for teams

* Move tournament bracket to feature folder

* REOPEN_MATCH initial

* Handle proper resetting of match

* Inline bracket-manager

* Syncify

* Transactions

* Handle match is locked gracefully

* Match page auto refresh

* Fix match refresh called "globally"

* Bracket autoupdate

* Move fillWithNullTillPowerOfTwo to utils with testing

* Fix map lists not visible after tournament started

* Optimize match events

* Show UI while in progress to members

* Fix start tournament alert not being responsive

* Teams can check in

* Fix map list 400

* xxx -> TODO

* Seeds page

* Remove map icons for team page

* Don't display link to seeds after tournament has started

* Admin actions initial

* Change captain admin action

* Make all hooks ts

* Admin actions functioning

* Fix validate error not displaying in CatchBoundary

* Adjust validate args order

* Remove admin loader

* Make delete team button menancing

* Only include checked in teams to bracket

* Optimize to.id route loads

* Working show map list generator toggle

* Update full tournaments flow

* Make full tournaments work with many start times

* Handle undefined in crud

* Dynamic stage banner

* Handle default strat if map list generation fails

* Fix crash on brackets if less than 2 teams

* Add commented out test for reference

* Add TODO

* Add players from team during register

* TrustRelationship

* Prefers not to host feature

* Last before merge

* Rename some vars

* More renames
2023-05-15 22:37:43 +03:00

195 lines
5.6 KiB
TypeScript

import { expect, test } from "@playwright/test";
import invariant from "tiny-invariant";
import type { TournamentLoaderData } from "~/features/tournament";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import {
fetchSendouInk,
impersonate,
isNotVisible,
navigate,
seed,
selectUser,
submit,
} from "~/utils/playwright";
import { tournamentBracketsPage, tournamentPage } from "~/utils/urls";
const fetchTournamentLoaderData = () =>
fetchSendouInk<TournamentLoaderData>(
"/to/1/admin?_data=features%2Ftournament%2Froutes%2Fto.%24id"
);
const getIsOwnerOfUser = ({
data,
userId,
teamId,
}: {
data: TournamentLoaderData;
userId: number;
teamId: number;
}) => {
const team = data.teams.find((t) => t.id === teamId);
invariant(team, "Team not found");
return team.members.find((m) => m.userId === userId)?.isOwner;
};
const getTeamCheckedInAt = ({
data,
teamId,
}: {
data: TournamentLoaderData;
teamId: number;
}) => {
const team = data.teams.find((t) => t.id === teamId);
invariant(team, "Team not found");
return team.checkedInAt;
};
test.describe("Tournament", () => {
test("registers for tournament", async ({ page }) => {
await seed(page, "NO_TOURNAMENT_TEAMS");
await impersonate(page);
await navigate({
page,
url: tournamentPage(1),
});
await page.getByLabel("Team name").type("Chimera");
await page.getByTestId("save-team-button").click();
await page.getByTestId("add-player-button").click();
await expect(page.getByTestId("member-num-2")).toBeVisible();
await page.getByTestId("add-player-button").click();
await expect(page.getByTestId("member-num-3")).toBeVisible();
await page.getByTestId("add-player-button").click();
await expect(page.getByTestId("member-num-4")).toBeVisible();
let stage = 5;
for (const mode of rankedModesShort) {
for (const num of [1, 2]) {
await page
.getByTestId(`counterpick-map-pool-${mode}-num-${num}`)
.selectOption(String(stage));
stage++;
}
}
await page.getByTestId("save-map-list-button").click();
await expect(page.getByTestId("checkmark-icon-num-3")).toBeVisible();
});
test("checks in and appears on the bracket", async ({ page }) => {
await seed(page);
await impersonate(page);
await navigate({
page,
url: tournamentBracketsPage(1),
});
await isNotVisible(page.getByText("Chimera"));
await page.getByTestId("register-tab").click();
await page.getByTestId("check-in-button").click();
await page.getByTestId("brackets-tab").click();
await page.getByText("#1 Chimera").waitFor();
});
test("operates admin controls", async ({ page }) => {
await seed(page);
await impersonate(page);
await navigate({
page,
url: tournamentPage(1),
});
await page.getByTestId("admin-tab").click();
const actionSelect = page.getByLabel("Action");
const teamSelect = page.getByLabel("Team");
const memberSelect = page.getByLabel("Member");
// Change team owner
let data = await fetchTournamentLoaderData();
expect(getIsOwnerOfUser({ data, userId: 1, teamId: 1 })).toBe(1);
await actionSelect.selectOption("CHANGE_TEAM_OWNER");
await teamSelect.selectOption("1");
await memberSelect.selectOption("2");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getIsOwnerOfUser({ data, userId: 1, teamId: 1 })).toBe(0);
expect(getIsOwnerOfUser({ data, userId: 2, teamId: 1 })).toBe(1);
// Check in team
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeFalsy();
await actionSelect.selectOption("CHECK_IN");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeTruthy();
// Check out team
await actionSelect.selectOption("CHECK_OUT");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeFalsy();
// Remove member...
const firstTeam = data.teams.find((t) => t.id === 1);
invariant(firstTeam, "First team not found");
const firstNonOwnerMember = firstTeam.members.find(
(m) => m.userId !== 1 && !m.isOwner
);
invariant(firstNonOwnerMember, "First non owner member not found");
await actionSelect.selectOption("REMOVE_MEMBER");
await memberSelect.selectOption(String(firstNonOwnerMember.userId));
await submit(page);
data = await fetchTournamentLoaderData();
const firstTeamAgain = data.teams.find((t) => t.id === 1);
invariant(firstTeamAgain, "First team again not found");
expect(firstTeamAgain.members.length).toBe(firstTeam.members.length - 1);
// ...and add to another team
const teamWithSpace = data.teams.find(
(t) => t.id !== 1 && t.members.length === 4
);
invariant(teamWithSpace, "Team with space not found");
await actionSelect.selectOption("ADD_MEMBER");
await teamSelect.selectOption(String(teamWithSpace.id));
await selectUser({
labelName: "User",
userName: firstNonOwnerMember.discordName,
page,
});
await submit(page);
data = await fetchTournamentLoaderData();
const teamWithSpaceAgain = data.teams.find(
(t) => t.id === teamWithSpace.id
);
invariant(teamWithSpaceAgain, "Team with space again not found");
expect(teamWithSpaceAgain.members.length).toBe(
teamWithSpace.members.length + 1
);
// Remove team
await actionSelect.selectOption("DELETE_TEAM");
await teamSelect.selectOption("1");
await submit(page);
data = await fetchTournamentLoaderData();
expect(data.teams.find((t) => t.id === 1)).toBeFalsy();
});
});