mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-24 04:22:10 -05:00
* Renders groups * Bracket data refactoring * Starting bracket working (first bracket only) * TODOs + crash fix * Source bracket logic initial * Bracket progression (DE underground bracket) * Preview working for second bracket * Bracket nav initial * Check-in to bracket feature * Start Underground bracket * Team/teams pages tweaks to support underground bracket * Underground bracket finalization progress * Tournament class * id -> userId + more useOutletContext removed * Bracket loader refactored out * Migrate admin to useTournament * Bracket.settings * Slim tournament loader * Fix useEffect infinite loop * Adjust waiting for teams text * Refactor old tournament DB call from to admin * Admin action: check in/out from specific bracket * Standings work * Back button from match page -> correct bracket * Standings logic for DE grand finals * Standings + finalize bracket * Dev log * Unit tests utils etc. * Adjust TODOs * Fix round robin issues * Add RR tests * Round robin standings initial * Wins against tied + points tiebreaker progress * Fix losing state when switching between tabs * Add check-in indications to seeding page * Link to user page on seed tool * Submit points * Total points from bracket manager * findById gonezino * Ahead of time check-in * Couple todos * Reopen logic refactor * Tournament format settings * RR->SE placements, skipping underground bracket * Fix tournament team page round names * More teams to UG bracket if first round of DE only byes * Fix graphics bug * Fixes * Fix some E2E tests * Fix E2E tests
204 lines
6.2 KiB
TypeScript
204 lines
6.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import invariant from "tiny-invariant";
|
|
import { ADMIN_ID } from "~/constants";
|
|
import { NZAP_TEST_ID } from "~/db/seed/constants";
|
|
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
|
|
import type { TournamentLoaderData } from "~/features/tournament/routes/to.$id";
|
|
import type { StageId } from "~/modules/in-game-lists";
|
|
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.tournament.ctx.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.tournament.ctx.teams.find((t) => t.id === teamId);
|
|
invariant(team, "Team not found");
|
|
return team.checkIns.length > 0;
|
|
};
|
|
|
|
test.describe("Tournament", () => {
|
|
test("registers for tournament", async ({ page }) => {
|
|
await seed(page, "REG_OPEN");
|
|
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]) {
|
|
while (BANNED_MAPS[mode].includes(stage as StageId)) {
|
|
stage++;
|
|
}
|
|
|
|
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, "REG_OPEN");
|
|
await impersonate(page);
|
|
|
|
await navigate({
|
|
page,
|
|
url: tournamentBracketsPage({ tournamentId: 3 }),
|
|
});
|
|
|
|
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 expect(page.getByTestId("brackets-viewer")).toBeVisible();
|
|
await page.getByText("Chimera").nth(0).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: ADMIN_ID, 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: ADMIN_ID, teamId: 1 })).toBe(0);
|
|
expect(getIsOwnerOfUser({ data, userId: NZAP_TEST_ID, 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.tournament.ctx.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.tournament.ctx.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.tournament.ctx.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.tournament.ctx.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.tournament.ctx.teams.find((t) => t.id === 1)).toBeFalsy();
|
|
});
|
|
});
|