Skip to content

Tournament admin · E2E tests

0 modules · 0 exports · 0 repositories · 9 e2e tests · 10 routes

  • NN-ZAP
  • Arrange data setup
  • Act what the user does
  • Assert what is expected

Tournament admin team management

edits a registration, checks a team in and out, unregisters it and records it in the audit log

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/admin/audit/to/:id/admin/registration/:tid?/to/:id/teams/:tid

Setup
Arrange
  1. an established organization's tournament, so its captain's tournament name can be set as part of the edit

    Create tournament established organization true → tournament
    const tournament = await createTournament(factories, {
    	establishedOrganization: true,
    });
  2. Create 4 × user → roster
    const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
  3. Create tournament team tournament id tournament.id team pickUpTeam("Original Team") member user ids roster.map((user) => user.id) → team
    const team = await factories.TournamentTeamFactory.create({
    	tournamentId: tournament.id,
    	team: pickUpTeam("Original Team"),
    	memberUserIds: roster.map((user) => user.id),
    });
NN-ZAP
Tournament admin registration page
Act
  1. Go to edit tournament.id, team.id
    await registration.gotoEdit(tournament.id, team.id);
Assert
  1. edit heading is visible
    await expect(registration.locators.editHeading).toBeVisible();
Act
  1. Fill Team name with “Renamed Team”
    await registration.form.fill("pickUpName", "Renamed Team");
  2. Set tournament name 0, “Riko”
    await registration.setTournamentName(0, "Riko");
  3. Save
    await registration.save();
Tournament admin page
Assert
  1. search input is visible
    await expect(admin.locators.searchInput).toBeVisible();
  2. team name “Renamed Team” is visible
    await expect(admin.teamName("Renamed Team")).toBeVisible();
Tournament team page
Act
  1. Open the tournament team page tournament.id, team.id
    await teamPage.goto(tournament.id, team.id);
Assert
  1. first member names reads “Riko”
    await expect(teamPage.locators.memberNames.first()).toHaveText("Riko");
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. Check team in 0
    await admin.checkTeamIn(0);
  3. Check team out 0
    await admin.checkTeamOut(0);
  4. Unregister team 0
    await admin.unregisterTeam(0);
Assert
  1. unregister dialog heading is visible
    await expect(admin.locators.unregisterDialogHeading).toBeVisible();
Act
  1. Confirm unregister
    await admin.confirmUnregister();
Tournament admin audit page
Act
  1. Open the tournament admin audit page tournament.id
    await audit.goto(tournament.id);
Assert
  1. event cell “Team checked in” is visible
    await expect(audit.eventCell("Team checked in")).toBeVisible();
  2. event cell “Team checked out” is visible
    await expect(audit.eventCell("Team checked out")).toBeVisible();
  3. event cell “Team unregistered” is visible
    await expect(audit.eventCell("Team unregistered")).toBeVisible();
  4. event cell “Tournament name changed” is visible
    await expect(audit.eventCell("Tournament name changed")).toBeVisible();
Source
test("edits a registration, checks a team in and out, unregisters it and records it in the audit log", async ({
	page,
	factories,
}) => {
	// an established organization's tournament, so its captain's tournament name
	// can be set as part of the edit
	const tournament = await createTournament(factories, {
		establishedOrganization: true,
	});
	const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
	const team = await factories.TournamentTeamFactory.create({
		tournamentId: tournament.id,
		team: pickUpTeam("Original Team"),
		memberUserIds: roster.map((user) => user.id),
	});

	await impersonate(page, NZAP_TEST_ID);

	const registration = new TournamentAdminRegistrationPage(page);
	await registration.gotoEdit(tournament.id, team.id);
	await expect(registration.locators.editHeading).toBeVisible();

	await registration.form.fill("pickUpName", "Renamed Team");
	await registration.setTournamentName(0, "Riko");
	await registration.save();

	// back on the team list, the rename is reflected
	const admin = new TournamentAdminPage(page);
	await expect(admin.locators.searchInput).toBeVisible();
	await expect(admin.teamName("Renamed Team")).toBeVisible();

	// the captain is shown under the name the organizer gave them
	const teamPage = new TournamentTeamPage(page);
	await teamPage.goto(tournament.id, team.id);
	await expect(teamPage.locators.memberNames.first()).toHaveText("Riko");

	await admin.goto(tournament.id);
	await admin.checkTeamIn(0);
	await admin.checkTeamOut(0);

	await admin.unregisterTeam(0);
	await expect(admin.locators.unregisterDialogHeading).toBeVisible();
	await admin.confirmUnregister();

	const audit = new TournamentAdminAuditPage(page);
	await audit.goto(tournament.id);
	await expect(audit.eventCell("Team checked in")).toBeVisible();
	await expect(audit.eventCell("Team checked out")).toBeVisible();
	await expect(audit.eventCell("Team unregistered")).toBeVisible();
	await expect(audit.eventCell("Tournament name changed")).toBeVisible();
})

adds a new team and records it in the audit log

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/admin/audit/to/:id/admin/registration/:tid?

Setup
Arrange
  1. Create tournament → tournament
    const tournament = await createTournament(factories);
  2. Create user discord name “Rostered Riko” → player
    const player = await factories.UserFactory.create({
    	discordName: "Rostered Riko",
    });
NN-ZAP
Tournament admin registration page
Act
  1. Go to new tournament.id
    await registration.gotoNew(tournament.id);
Assert
  1. add heading is visible
    await expect(registration.locators.addHeading).toBeVisible();
Act
  1. Fill Team name with “Panda Squad”
    await registration.form.fill("pickUpName", "Panda Squad");
  2. Select player “Rostered Riko”
    await registration.selectPlayer("Rostered Riko");
  3. Select captain player.id
    await registration.selectCaptain(player.id);
  4. Save
    await registration.save();
Tournament admin page
Assert
  1. team name “Panda Squad” is visible
    await expect(admin.teamName("Panda Squad")).toBeVisible();
Tournament admin audit page
Act
  1. Open the tournament admin audit page tournament.id
    await audit.goto(tournament.id);
Assert
  1. event cell “Team registered” is visible
    await expect(audit.eventCell("Team registered")).toBeVisible();
Source
test("adds a new team and records it in the audit log", async ({
	page,
	factories,
}) => {
	const tournament = await createTournament(factories);
	const player = await factories.UserFactory.create({
		discordName: "Rostered Riko",
	});

	await impersonate(page, NZAP_TEST_ID);

	const registration = new TournamentAdminRegistrationPage(page);
	await registration.gotoNew(tournament.id);
	await expect(registration.locators.addHeading).toBeVisible();

	await registration.form.fill("pickUpName", "Panda Squad");
	await registration.selectPlayer("Rostered Riko");
	await registration.selectCaptain(player.id);
	await registration.save();

	const admin = new TournamentAdminPage(page);
	await expect(admin.teamName("Panda Squad")).toBeVisible();

	const audit = new TournamentAdminAuditPage(page);
	await audit.goto(tournament.id);
	await expect(audit.eventCell("Team registered")).toBeVisible();
})

imports a roster from another tournament and registers it

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/admin/registration/:tid?/to/:id/teams

Setup
Arrange
  1. Create tournament → tournament
    const tournament = await createTournament(factories);
  2. the tournament search of the import dialog only finds past tournaments

    Create tournament name “Paddling Pool 253” author N-ZAP start times dateToDatabaseTimestamp(subDays(new Date(), 2)) → pastTournament
    const pastTournament = await factories.TournamentFactory.create({
    	name: "Paddling Pool 253",
    	authorId: NZAP_TEST_ID,
    	startTimes: [dateToDatabaseTimestamp(subDays(new Date(), 2))],
    });
  3. importedRosterNames = Array.from( { length: ROSTER_SIZE }, (_, i) => `Imported Player ${i + 1}`, )
    const importedRosterNames = Array.from(
    	{ length: ROSTER_SIZE },
    	(_, i) => `Imported Player ${i + 1}`,
    );
  4. Create 4 × user (i) => ({ discordName: importedRosterNames[i] }) → importedRoster
    const importedRoster = await factories.UserFactory.createMany(
    	ROSTER_SIZE,
    	(i) => ({ discordName: importedRosterNames[i] }),
    );
  5. Create tournament team tournament id pastTournament.id team pickUpTeam("Imported Legends") member user ids importedRoster.map((user) => user.id)
    await factories.TournamentTeamFactory.create({
    	tournamentId: pastTournament.id,
    	team: pickUpTeam("Imported Legends"),
    	memberUserIds: importedRoster.map((user) => user.id),
    });
NN-ZAP
Tournament admin registration page
Act
  1. Go to new tournament.id
    await registration.gotoNew(tournament.id);
Assert
  1. add heading is visible
    await expect(registration.locators.addHeading).toBeVisible();
Act
  1. Open import dialog
    await registration.openImportDialog();
Assert
  1. import dialog heading is visible
    await expect(registration.locators.importDialogHeading).toBeVisible();
Act
  1. Import first team from “Paddling Pool”
    await registration.importFirstTeamFrom("Paddling Pool");
Assert
  1. the dialog closes and the imported roster prefills the form

    import dialog heading count is 0
    await expect(registration.locators.importDialogHeading).toHaveCount(0);
  2. team name input has value “Imported Legends”
    await expect(registration.locators.teamNameInput).toHaveValue(
    	"Imported Legends",
    );
  3. For each name of importedRosterNames
    for (const name of importedRosterNames) {
    	await expect(registration.memberWithName(name)).toBeVisible();
    }
    1. member with name name is visible
      await expect(registration.memberWithName(name)).toBeVisible();
Act
  1. Save
    await registration.save();
Tournament admin page
Assert
  1. team name “Imported Legends” is visible
    await expect(admin.teamName("Imported Legends")).toBeVisible();
Tournament teams page
Act
  1. Open the tournament teams page tournament.id
    await teamsPage.goto(tournament.id);
Assert
  1. team named “Imported Legends” is visible
    await expect(teamsPage.teamNamed("Imported Legends")).toBeVisible();
  2. For each name of importedRosterNames
    for (const name of importedRosterNames) {
    	await expect(teamsPage.memberNamed(name)).toBeVisible();
    }
    1. member named name is visible
      await expect(teamsPage.memberNamed(name)).toBeVisible();
Source
test("imports a roster from another tournament and registers it", async ({
	page,
	factories,
}) => {
	const tournament = await createTournament(factories);

	// the tournament search of the import dialog only finds past tournaments
	const pastTournament = await factories.TournamentFactory.create({
		name: "Paddling Pool 253",
		authorId: NZAP_TEST_ID,
		startTimes: [dateToDatabaseTimestamp(subDays(new Date(), 2))],
	});
	const importedRosterNames = Array.from(
		{ length: ROSTER_SIZE },
		(_, i) => `Imported Player ${i + 1}`,
	);
	const importedRoster = await factories.UserFactory.createMany(
		ROSTER_SIZE,
		(i) => ({ discordName: importedRosterNames[i] }),
	);
	await factories.TournamentTeamFactory.create({
		tournamentId: pastTournament.id,
		team: pickUpTeam("Imported Legends"),
		memberUserIds: importedRoster.map((user) => user.id),
	});

	await impersonate(page, NZAP_TEST_ID);

	const registration = new TournamentAdminRegistrationPage(page);
	await registration.gotoNew(tournament.id);
	await expect(registration.locators.addHeading).toBeVisible();

	await registration.openImportDialog();
	await expect(registration.locators.importDialogHeading).toBeVisible();

	await registration.importFirstTeamFrom("Paddling Pool");

	// the dialog closes and the imported roster prefills the form
	await expect(registration.locators.importDialogHeading).toHaveCount(0);
	await expect(registration.locators.teamNameInput).toHaveValue(
		"Imported Legends",
	);
	for (const name of importedRosterNames) {
		await expect(registration.memberWithName(name)).toBeVisible();
	}

	await registration.save();

	const admin = new TournamentAdminPage(page);
	await expect(admin.teamName("Imported Legends")).toBeVisible();

	// the imported team registered with its full roster
	const teamsPage = new TournamentTeamsPage(page);
	await teamsPage.goto(tournament.id);
	await expect(teamsPage.teamNamed("Imported Legends")).toBeVisible();
	for (const name of importedRosterNames) {
		await expect(teamsPage.memberNamed(name)).toBeVisible();
	}
})

sets the counterpick map pool of a team that has none, rejects an incomplete edit to it and then edits it

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/admin/registration/:tid?/to/:id/teams/:tid

Setup
Arrange
  1. teams pre-pick their maps, so the registration form has a map pool to edit

    Create tournament map picking style “AUTO” → tournament
    const tournament = await createTournament(factories, {
    	mapPickingStyle: "AUTO",
    });
  2. Create 4 × user → roster
    const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
  3. Create tournament team tournament id tournament.id team pickUpTeam("Poolless Pandas") member user ids roster.map((user) => user.id) → team
    const team = await factories.TournamentTeamFactory.create({
    	tournamentId: tournament.id,
    	team: pickUpTeam("Poolless Pandas"),
    	memberUserIds: roster.map((user) => user.id),
    });
NN-ZAP
Tournament admin registration page
Act
  1. Go to edit tournament.id, team.id
    await registration.gotoEdit(tournament.id, team.id);
Assert
  1. edit heading is visible
    await expect(registration.locators.editHeading).toBeVisible();
Act
  1. Pick counterpick maps → picked
    const picked = await registration.pickCounterpickMaps();
  2. Save
    await registration.save();
Tournament admin page
Assert
  1. search input is visible
    await expect(admin.locators.searchInput).toBeVisible();
Tournament team page
Act
  1. Open the tournament team page tournament.id, team.id
    await teamPage.goto(tournament.id, team.id);
Assert
  1. For each { mode, stageId } of picked
    for (const { mode, stageId } of picked) {
    	await expect(teamPage.mapPoolStage(mode, stageId)).toBeVisible();
    }
    1. map pool stage mode, stageId is visible
      await expect(teamPage.mapPoolStage(mode, stageId)).toBeVisible();
Act
  1. a pool left incomplete is rejected instead of overwriting the saved one

    [replaced] = picked
    const [replaced] = picked;
Tournament admin registration page
Act
  1. Go to edit tournament.id, team.id
    await registration.gotoEdit(tournament.id, team.id);
Assert
  1. picked counterpick map replaced.mode, replaced.stageId is visible
    await expect(
    	registration.pickedCounterpickMap(replaced.mode, replaced.stageId),
    ).toBeVisible();
Act
  1. Unpick counterpick map replaced.mode, replaced.stageId
    await registration.unpickCounterpickMap(replaced.mode, replaced.stageId);
  2. Save
    await registration.save();
Assert
  1. invalid map pool error is visible
    await expect(registration.locators.invalidMapPoolError).toBeVisible();
Tournament team page
Act
  1. Open the tournament team page tournament.id, team.id
    await teamPage.goto(tournament.id, team.id);
Assert
  1. map pool stage replaced.mode, replaced.stageId is visible
    await expect(
    	teamPage.mapPoolStage(replaced.mode, replaced.stageId),
    ).toBeVisible();
Tournament admin registration page
Act
  1. swapping one of the picked maps for another is reflected on the team page

    Go to edit tournament.id, team.id
    await registration.gotoEdit(tournament.id, team.id);
  2. Unpick counterpick map replaced.mode, replaced.stageId
    await registration.unpickCounterpickMap(replaced.mode, replaced.stageId);
  3. Pick counterpick map replaced.mode, 17
    await registration.pickCounterpickMap(replaced.mode, REPLACEMENT_STAGE_ID);
  4. Save
    await registration.save();
Tournament admin page
Assert
  1. search input is visible
    await expect(admin.locators.searchInput).toBeVisible();
Tournament team page
Act
  1. Open the tournament team page tournament.id, team.id
    await teamPage.goto(tournament.id, team.id);
Assert
  1. map pool stage replaced.mode, 17 is visible
    await expect(
    	teamPage.mapPoolStage(replaced.mode, REPLACEMENT_STAGE_ID),
    ).toBeVisible();
  2. map pool stage replaced.mode, replaced.stageId count is 0
    await expect(
    	teamPage.mapPoolStage(replaced.mode, replaced.stageId),
    ).toHaveCount(0);
Source
test("sets the counterpick map pool of a team that has none, rejects an incomplete edit to it and then edits it", async ({
	page,
	factories,
}) => {
	// teams pre-pick their maps, so the registration form has a map pool to edit
	const tournament = await createTournament(factories, {
		mapPickingStyle: "AUTO",
	});
	const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
	const team = await factories.TournamentTeamFactory.create({
		tournamentId: tournament.id,
		team: pickUpTeam("Poolless Pandas"),
		memberUserIds: roster.map((user) => user.id),
	});

	await impersonate(page, NZAP_TEST_ID);

	const registration = new TournamentAdminRegistrationPage(page);
	await registration.gotoEdit(tournament.id, team.id);
	await expect(registration.locators.editHeading).toBeVisible();

	const picked = await registration.pickCounterpickMaps();
	await registration.save();

	const admin = new TournamentAdminPage(page);
	await expect(admin.locators.searchInput).toBeVisible();

	const teamPage = new TournamentTeamPage(page);
	await teamPage.goto(tournament.id, team.id);
	for (const { mode, stageId } of picked) {
		await expect(teamPage.mapPoolStage(mode, stageId)).toBeVisible();
	}

	// a pool left incomplete is rejected instead of overwriting the saved one
	const [replaced] = picked;
	await registration.gotoEdit(tournament.id, team.id);
	await expect(
		registration.pickedCounterpickMap(replaced.mode, replaced.stageId),
	).toBeVisible();
	await registration.unpickCounterpickMap(replaced.mode, replaced.stageId);
	await registration.save();
	await expect(registration.locators.invalidMapPoolError).toBeVisible();

	await teamPage.goto(tournament.id, team.id);
	await expect(
		teamPage.mapPoolStage(replaced.mode, replaced.stageId),
	).toBeVisible();

	// swapping one of the picked maps for another is reflected on the team page
	await registration.gotoEdit(tournament.id, team.id);
	await registration.unpickCounterpickMap(replaced.mode, replaced.stageId);
	await registration.pickCounterpickMap(replaced.mode, REPLACEMENT_STAGE_ID);
	await registration.save();

	await expect(admin.locators.searchInput).toBeVisible();

	await teamPage.goto(tournament.id, team.id);
	await expect(
		teamPage.mapPoolStage(replaced.mode, REPLACEMENT_STAGE_ID),
	).toBeVisible();
	await expect(
		teamPage.mapPoolStage(replaced.mode, replaced.stageId),
	).toHaveCount(0);
})

exports the team list

Renders/to/:id/to/:id/admin/to/:id/admin

Setup
Arrange
  1. Create tournament → tournament
    const tournament = await createTournament(factories);
  2. Create 4 × user → roster
    const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
  3. Create tournament team tournament id tournament.id team pickUpTeam("Alpha Squad") member user ids roster.map((user) => user.id)
    await factories.TournamentTeamFactory.create({
    	tournamentId: tournament.id,
    	team: pickUpTeam("Alpha Squad"),
    	memberUserIds: roster.map((user) => user.id),
    });
NN-ZAP
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. Open export dialog
    await admin.openExportDialog();
Assert
  1. export dialog heading is visible
    await expect(admin.locators.exportDialogHeading).toBeVisible();
Act
  1. Download export → download
    const download = await admin.downloadExport();
Assert
  1. download.suggestedFilename() is “participants.txt”
    expect(download.suggestedFilename()).toBe("participants.txt");
Act
  1. Read file download.path(), “utf-8” → content
    const content = await fs.readFile(await download.path(), "utf-8");
Assert
  1. content contains “Alpha Squad”
    expect(content).toContain("Alpha Squad");
Source
test("exports the team list", async ({ page, factories }) => {
	const tournament = await createTournament(factories);
	const roster = await factories.UserFactory.createMany(ROSTER_SIZE);
	await factories.TournamentTeamFactory.create({
		tournamentId: tournament.id,
		team: pickUpTeam("Alpha Squad"),
		memberUserIds: roster.map((user) => user.id),
	});

	await impersonate(page, NZAP_TEST_ID);

	const admin = new TournamentAdminPage(page);
	await admin.goto(tournament.id);

	await admin.openExportDialog();
	await expect(admin.locators.exportDialogHeading).toBeVisible();

	const download = await admin.downloadExport();

	expect(download.suggestedFilename()).toBe("participants.txt");

	const content = await fs.readFile(await download.path(), "utf-8");
	expect(content).toContain("Alpha Squad");
})

adds a sub post on behalf of a user

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/looking

Setup
Arrange
  1. registration is closed (start time in the past) so the subs view is shown on the looking page

    Create tournament author N-ZAP start times dateToDatabaseTimestamp(subDays(new Date(), 1)) → tournament
    const tournament = await factories.TournamentFactory.create({
    	authorId: NZAP_TEST_ID,
    	startTimes: [dateToDatabaseTimestamp(subDays(new Date(), 1))],
    });
  2. Create user discord name “Subby Sam”
    await factories.UserFactory.create({
    	discordName: "Subby Sam",
    });
NN-ZAP
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. Open add sub dialog
    await admin.openAddSubDialog();
Assert
  1. add sub dialog heading is visible
    await expect(admin.locators.addSubDialogHeading).toBeVisible();
Act
  1. Select user User “Subby Sam”
    await admin.addSubForm.selectUser("userId", "Subby Sam");
  2. Fill message with “Can play backline”
    await admin.addSubForm.fill("message", "Can play backline");
  3. Submit
    await admin.addSubForm.submit();
Assert
  1. add sub dialog heading count is 0
    await expect(admin.locators.addSubDialogHeading).toHaveCount(0);
Tournament subs page
Act
  1. Open the tournament subs page tournament.id
    await subs.goto(tournament.id);
Assert
  1. sub post text “Subby Sam” is visible
    await expect(subs.subPostText("Subby Sam")).toBeVisible();
  2. sub post text “Can play backline” is visible
    await expect(subs.subPostText("Can play backline")).toBeVisible();
Source
test("adds a sub post on behalf of a user", async ({ page, factories }) => {
	// registration is closed (start time in the past) so the subs view is shown on the looking page
	const tournament = await factories.TournamentFactory.create({
		authorId: NZAP_TEST_ID,
		startTimes: [dateToDatabaseTimestamp(subDays(new Date(), 1))],
	});
	await factories.UserFactory.create({
		discordName: "Subby Sam",
	});

	await impersonate(page, NZAP_TEST_ID);

	const admin = new TournamentAdminPage(page);
	await admin.goto(tournament.id);

	await admin.openAddSubDialog();
	await expect(admin.locators.addSubDialogHeading).toBeVisible();

	await admin.addSubForm.selectUser("userId", "Subby Sam");
	await admin.addSubForm.fill("message", "Can play backline");
	await admin.addSubForm.submit();

	await expect(admin.locators.addSubDialogHeading).toHaveCount(0);

	const subs = new TournamentSubsPage(page);
	await subs.goto(tournament.id);
	await expect(subs.subPostText("Subby Sam")).toBeVisible();
	await expect(subs.subPostText("Can play backline")).toBeVisible();
})

adds a sub post on behalf of a user whose team dropped out

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/looking

Setup
Arrange
  1. Create tournament author N-ZAP start times startedTournamentTimes() bracket progression DOUBLE_ELIMINATION → tournament
    const tournament = await factories.TournamentFactory.create({
    	authorId: NZAP_TEST_ID,
    	startTimes: startedTournamentTimes(),
    	bracketProgression: DOUBLE_ELIMINATION,
    });
  2. Create user discord name “Dropout Dana” → dropout
    const dropout = await factories.UserFactory.create({
    	discordName: "Dropout Dana",
    });
  3. Create ROSTER_SIZE - 1 × user → droppingRest
    const droppingRest = await factories.UserFactory.createMany(
    	ROSTER_SIZE - 1,
    );
  4. Create tournament team tournament id tournament.id team pickUpTeam("Recruiting Rays") member user ids dropout.id, ...droppingRest.map((user) => user.id) is checked in true is looking true
    await factories.TournamentTeamFactory.create(
    	{
    		tournamentId: tournament.id,
    		team: pickUpTeam("Recruiting Rays"),
    		memberUserIds: [dropout.id, ...droppingRest.map((user) => user.id)],
    	},
    	// the team was recruiting on the LFG page before it dropped out
    	{ isCheckedIn: true, isLooking: true },
    );
  5. enough opponents that dropping out one team does not end every match

    Create teams tournament.id, teamSeeds(3)
    await createTeams(factories, tournament.id, teamSeeds(3));
  6. Start bracket tournament tournament.id
    await factories.TournamentFactory.startBracket(tournament.id);
NN-ZAP
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. Search teams “Recruiting Rays”
    await admin.searchTeams("Recruiting Rays");
  3. Drop out team 0
    await admin.dropOutTeam(0);
  4. Open add sub dialog
    await admin.openAddSubDialog();
Assert
  1. add sub dialog heading is visible
    await expect(admin.locators.addSubDialogHeading).toBeVisible();
Act
  1. Select user User “Dropout Dana”
    await admin.addSubForm.selectUser("userId", "Dropout Dana");
  2. Fill message with “Free to sub now”
    await admin.addSubForm.fill("message", "Free to sub now");
  3. Submit
    await admin.addSubForm.submit();
Assert
  1. add sub dialog heading count is 0
    await expect(admin.locators.addSubDialogHeading).toHaveCount(0);
Tournament subs page
Act
  1. Open the tournament subs page tournament.id
    await subs.goto(tournament.id);
Assert
  1. sub post text “Dropout Dana” is visible
    await expect(subs.subPostText("Dropout Dana")).toBeVisible();
  2. sub post text “Free to sub now” is visible
    await expect(subs.subPostText("Free to sub now")).toBeVisible();
Source
test("adds a sub post on behalf of a user whose team dropped out", async ({
	page,
	factories,
}) => {
	const tournament = await factories.TournamentFactory.create({
		authorId: NZAP_TEST_ID,
		startTimes: startedTournamentTimes(),
		bracketProgression: DOUBLE_ELIMINATION,
	});
	const dropout = await factories.UserFactory.create({
		discordName: "Dropout Dana",
	});
	const droppingRest = await factories.UserFactory.createMany(
		ROSTER_SIZE - 1,
	);
	await factories.TournamentTeamFactory.create(
		{
			tournamentId: tournament.id,
			team: pickUpTeam("Recruiting Rays"),
			memberUserIds: [dropout.id, ...droppingRest.map((user) => user.id)],
		},
		// the team was recruiting on the LFG page before it dropped out
		{ isCheckedIn: true, isLooking: true },
	);
	// enough opponents that dropping out one team does not end every match
	await createTeams(factories, tournament.id, teamSeeds(3));
	await factories.TournamentFactory.startBracket(tournament.id);

	await impersonate(page, NZAP_TEST_ID);

	const admin = new TournamentAdminPage(page);
	await admin.goto(tournament.id);

	await admin.searchTeams("Recruiting Rays");
	await admin.dropOutTeam(0);

	await admin.openAddSubDialog();
	await expect(admin.locators.addSubDialogHeading).toBeVisible();

	await admin.addSubForm.selectUser("userId", "Dropout Dana");
	await admin.addSubForm.fill("message", "Free to sub now");
	await admin.addSubForm.submit();

	await expect(admin.locators.addSubDialogHeading).toHaveCount(0);

	const subs = new TournamentSubsPage(page);
	await subs.goto(tournament.id);
	await expect(subs.subPostText("Dropout Dana")).toBeVisible();
	await expect(subs.subPostText("Free to sub now")).toBeVisible();
})

filters the team list by name and by captain Discord id

Renders/to/:id/to/:id/admin/to/:id/admin

Setup
Arrange
  1. Create tournament → tournament
    const tournament = await createTournament(factories);
  2. Create user discord id “1234567890123456789” → captain
    const captain = await factories.UserFactory.create({
    	discordId: CAPTAIN_DISCORD_ID,
    });
  3. Create ROSTER_SIZE - 1 × user → alphaRest
    const alphaRest = await factories.UserFactory.createMany(ROSTER_SIZE - 1);
  4. Create tournament team tournament id tournament.id team pickUpTeam("Alpha Squad") member user ids captain.id, ...alphaRest.map((user) => user.id)
    await factories.TournamentTeamFactory.create({
    	tournamentId: tournament.id,
    	team: pickUpTeam("Alpha Squad"),
    	memberUserIds: [captain.id, ...alphaRest.map((user) => user.id)],
    });
  5. Create 4 × user → bravoRoster
    const bravoRoster = await factories.UserFactory.createMany(ROSTER_SIZE);
  6. Create tournament team tournament id tournament.id team pickUpTeam("Bravo Squad") member user ids bravoRoster.map((user) => user.id)
    await factories.TournamentTeamFactory.create({
    	tournamentId: tournament.id,
    	team: pickUpTeam("Bravo Squad"),
    	memberUserIds: bravoRoster.map((user) => user.id),
    });
NN-ZAP
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. the captain is on no other team, so their team name + discord id both single it out

    Search teams “Alpha Squad”
    await admin.searchTeams("Alpha Squad");
Assert
  1. team rows count is 1
    await expect(admin.locators.teamRows).toHaveCount(1);
  2. first team names reads “Alpha Squad”
    await expect(admin.locators.teamNames.first()).toHaveText("Alpha Squad");
Act
  1. Search teams “1234567890123456789”
    await admin.searchTeams(CAPTAIN_DISCORD_ID);
Assert
  1. team rows count is 1
    await expect(admin.locators.teamRows).toHaveCount(1);
  2. first team names reads “Alpha Squad”
    await expect(admin.locators.teamNames.first()).toHaveText("Alpha Squad");
Act
  1. Search teams “zzz-no-such-team-zzz”
    await admin.searchTeams("zzz-no-such-team-zzz");
Assert
  1. no search results text is visible
    await expect(admin.locators.noSearchResultsText).toBeVisible();
Source
test("filters the team list by name and by captain Discord id", async ({
	page,
	factories,
}) => {
	const tournament = await createTournament(factories);
	const captain = await factories.UserFactory.create({
		discordId: CAPTAIN_DISCORD_ID,
	});
	const alphaRest = await factories.UserFactory.createMany(ROSTER_SIZE - 1);
	await factories.TournamentTeamFactory.create({
		tournamentId: tournament.id,
		team: pickUpTeam("Alpha Squad"),
		memberUserIds: [captain.id, ...alphaRest.map((user) => user.id)],
	});
	const bravoRoster = await factories.UserFactory.createMany(ROSTER_SIZE);
	await factories.TournamentTeamFactory.create({
		tournamentId: tournament.id,
		team: pickUpTeam("Bravo Squad"),
		memberUserIds: bravoRoster.map((user) => user.id),
	});

	await impersonate(page, NZAP_TEST_ID);

	const admin = new TournamentAdminPage(page);
	await admin.goto(tournament.id);

	// the captain is on no other team, so their team name + discord id both single it out
	await admin.searchTeams("Alpha Squad");
	await expect(admin.locators.teamRows).toHaveCount(1);
	await expect(admin.locators.teamNames.first()).toHaveText("Alpha Squad");

	await admin.searchTeams(CAPTAIN_DISCORD_ID);
	await expect(admin.locators.teamRows).toHaveCount(1);
	await expect(admin.locators.teamNames.first()).toHaveText("Alpha Squad");

	await admin.searchTeams("zzz-no-such-team-zzz");
	await expect(admin.locators.noSearchResultsText).toBeVisible();
})

Tournament admin bracket progression editing

edits an unstarted follow-up bracket while the started bracket stays locked

Renders/to/:id/to/:id/admin/to/:id/admin/to/:id/admin/brackets

Setup
Arrange
  1. Create tournament author N-ZAP start times startedTournamentTimes() bracket progression RR_TO_SE → tournament
    const tournament = await factories.TournamentFactory.create({
    	authorId: NZAP_TEST_ID,
    	startTimes: startedTournamentTimes(),
    	bracketProgression: RR_TO_SE,
    });
  2. Create teams tournament.id, teamSeeds(4)
    await createTeams(factories, tournament.id, teamSeeds(4));
  3. Start bracket tournament tournament.id
    await factories.TournamentFactory.startBracket(tournament.id);
NN-ZAP
Tournament admin page
Act
  1. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  2. Open brackets
    await admin.openBrackets();
Assert
  1. the started groups stage is locked and can not be removed

    first bracket name inputs is disabled
    await expect(admin.locators.bracketNameInputs.first()).toBeDisabled();
  2. bracket name inputs #1 is enabled
    await expect(admin.locators.bracketNameInputs.nth(1)).toBeEnabled();
  3. first remove bracket buttons is disabled
    await expect(admin.locators.removeBracketButtons.first()).toBeDisabled();
  4. remove bracket buttons #1 is enabled
    await expect(admin.locators.removeBracketButtons.nth(1)).toBeEnabled();
Act
  1. Rename bracket 1, “Top Cut”
    await admin.renameBracket(1, "Top Cut");
  2. Save progression
    await admin.saveProgression();
  3. Open the tournament admin page tournament.id
    await admin.goto(tournament.id);
  4. Open brackets
    await admin.openBrackets();
Assert
  1. bracket name inputs #1 has value “Top Cut”
    await expect(admin.locators.bracketNameInputs.nth(1)).toHaveValue(
    	"Top Cut",
    );
Source
test("edits an unstarted follow-up bracket while the started bracket stays locked", async ({
	page,
	factories,
}) => {
	const tournament = await factories.TournamentFactory.create({
		authorId: NZAP_TEST_ID,
		startTimes: startedTournamentTimes(),
		bracketProgression: RR_TO_SE,
	});
	await createTeams(factories, tournament.id, teamSeeds(4));
	await factories.TournamentFactory.startBracket(tournament.id);

	await impersonate(page, NZAP_TEST_ID);

	const admin = new TournamentAdminPage(page);
	await admin.goto(tournament.id);
	await admin.openBrackets();

	// the started groups stage is locked and can not be removed
	await expect(admin.locators.bracketNameInputs.first()).toBeDisabled();
	await expect(admin.locators.bracketNameInputs.nth(1)).toBeEnabled();
	await expect(admin.locators.removeBracketButtons.first()).toBeDisabled();
	await expect(admin.locators.removeBracketButtons.nth(1)).toBeEnabled();

	await admin.renameBracket(1, "Top Cut");
	await admin.saveProgression();

	await admin.goto(tournament.id);
	await admin.openBrackets();
	await expect(admin.locators.bracketNameInputs.nth(1)).toHaveValue(
		"Top Cut",
	);
})