Skip to content

Scrims · E2E tests

2 modules · 22 exports · 4 repositories · 12 e2e tests · 3 routes

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

Scrims

Posting

A post is made as a team or as a pick-up of users the author picks, who are saved for later posts. Its card shows the start window and the tournament whose maps are played, and marks a post only an association can see.

a post limited to an association is marked as such and its owner can delete it

Renders//scrims/scrims/new

Setup
Arrange
  1. Create named users “Pickup One”, “Pickup Two”, “Pickup Three”
    await createNamedUsers(factories, PICKUP_NAMES);
  2. Create association user N-ZAP name “Inkling Alliance”
    await factories.AssociationFactory.create({
    	userId: NZAP_TEST_ID,
    	name: ASSOCIATION_NAME,
    });
NN-ZAP
Act
  1. Go to “/”
    await navigate({ page, url: "/" });
Anything adder
Act
  1. Add “scrimPost”
    await new AnythingAdder(page).add("scrimPost");
New scrim post page
Act
  1. Select pickup users “Pickup One”, “Pickup Two”, “Pickup Three”
    await newPost.selectPickupUsers(PICKUP_NAMES);
  2. Select visibility “Inkling Alliance”
    await newPost.selectVisibility(ASSOCIATION_NAME);
  3. Fill Text with “Test scrim”
    await newPost.form.fill("postText", "Test scrim");
  4. Save
    await newPost.save();
Scrims page
Assert
  1. limited visibility popover is visible
    await expect(scrims.locators.limitedVisibilityPopover).toBeVisible();
Act
  1. Delete first post
    await scrims.deleteFirstPost();
Assert
  1. delete buttons count is 0
    await expect(scrims.locators.deleteButtons).toHaveCount(0);
Source
test("a post limited to an association is marked as such and its owner can delete it", async ({
	page,
	factories,
}) => {
	await createNamedUsers(factories, PICKUP_NAMES);
	await factories.AssociationFactory.create({
		userId: NZAP_TEST_ID,
		name: ASSOCIATION_NAME,
	});

	await impersonate(page, NZAP_TEST_ID);
	await navigate({ page, url: "/" });

	await new AnythingAdder(page).add("scrimPost");

	const newPost = new NewScrimPostPage(page);
	await newPost.selectPickupUsers(PICKUP_NAMES);
	await newPost.selectVisibility(ASSOCIATION_NAME);
	await newPost.form.fill("postText", "Test scrim");
	await newPost.save();

	const scrims = new ScrimsPage(page);
	await expect(scrims.locators.limitedVisibilityPopover).toBeVisible();

	await scrims.deleteFirstPost();

	await expect(scrims.locators.deleteButtons).toHaveCount(0);
})

reuses a pick-up saved from an earlier scrim post

Renders/scrims/scrims/new

Setup
Arrange
  1. Create named users “Pickup One”, “Pickup Two”, “Pickup Three”
    await createNamedUsers(factories, PICKUP_NAMES);
NN-ZAP
New scrim post page
Act
  1. Open the new scrim post page
    await newPost.goto();
  2. Select pickup users “Pickup One”, “Pickup Two”, “Pickup Three”
    await newPost.selectPickupUsers(PICKUP_NAMES);
  3. Save
    await newPost.save();
  4. Open the new scrim post page
    await newPost.goto();
  5. Select saved pickup “Pickup One”, “Pickup Two”, “Pickup Three”
    await newPost.selectSavedPickup(PICKUP_NAMES);
Assert
  1. For each [index, userName] of PICKUP_NAMES.entries()
    for (const [index, userName] of PICKUP_NAMES.entries()) {
    	await expect(newPost.pickupUser(index + 2)).toContainText(userName);
    }
    1. pickup user index + 2 contains userName
      await expect(newPost.pickupUser(index + 2)).toContainText(userName);
Act
  1. Save
    await newPost.save();
Scrims page
Assert
  1. delete buttons count is 2
    await expect(scrims.locators.deleteButtons).toHaveCount(2);
Source
test("reuses a pick-up saved from an earlier scrim post", async ({
	page,
	factories,
}) => {
	await createNamedUsers(factories, PICKUP_NAMES);

	await impersonate(page, NZAP_TEST_ID);

	const newPost = new NewScrimPostPage(page);
	await newPost.goto();
	await newPost.selectPickupUsers(PICKUP_NAMES);
	await newPost.save();

	await newPost.goto();
	await newPost.selectSavedPickup(PICKUP_NAMES);

	for (const [index, userName] of PICKUP_NAMES.entries()) {
		await expect(newPost.pickupUser(index + 2)).toContainText(userName);
	}

	await newPost.save();

	const scrims = new ScrimsPage(page);
	await expect(scrims.locators.deleteButtons).toHaveCount(2);
})

a post's start window and tournament maps show on its card

Renders/scrims/scrims/new

Setup
Arrange
  1. Create team for Sendou
    await createTeamFor(factories, ADMIN_ID);
  2. Create tournament
    await createTournament(factories);
SSendou
New scrim post page
Act
  1. Open the new scrim post page
    await newPost.goto();
  2. Set Start to tomorrowEvening()
    await newPost.form.setDateTime("at", tomorrowEvening());
  3. Select “+2 hours” for Start time flexibility
    await newPost.form.select("rangeEnd", "+2hours");
  4. Select tournament maps “Swim or Sink”
    await newPost.selectTournamentMaps(TOURNAMENT_NAME);
  5. Save
    await newPost.save();
Scrims page
Assert
  1. post “+2h” is visible
    await expect(scrims.post("+2h")).toBeVisible();
  2. tournament popover is visible
    await expect(scrims.locators.tournamentPopover).toBeVisible();
Source
test("a post's start window and tournament maps show on its card", async ({
	page,
	factories,
}) => {
	await createTeamFor(factories, ADMIN_ID);
	await createTournament(factories);

	await impersonate(page, ADMIN_ID);

	const newPost = new NewScrimPostPage(page);
	await newPost.goto();
	await newPost.form.setDateTime("at", tomorrowEvening());
	await newPost.form.select("rangeEnd", "+2hours");
	await newPost.selectTournamentMaps(TOURNAMENT_NAME);
	await newPost.save();

	const scrims = new ScrimsPage(page);
	await expect(scrims.post("+2h")).toBeVisible();
	await expect(scrims.locators.tournamentPopover).toBeVisible();
})

Requests

Other teams request a post from the Available tab, picking the start when the post has a start window, and can withdraw the request while it is pending. The post's owner is notified of each request.

requests a post and withdraws the request

Renders/scrims

Setup
Arrange
  1. Repeat for (let postNth = 0; postNth < 2; postNth++)
    for (let postNth = 0; postNth < 2; postNth++) {
    	await factories.ScrimPostFactory.create({
    		users: await createGroup(factories),
    	});
    }
    1. Create scrim post users createGroup(factories)
      await factories.ScrimPostFactory.create({
      	users: await createGroup(factories),
      });
  2. Create team for N-ZAP
    await createTeamFor(factories, NZAP_TEST_ID);
NN-ZAP
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Open tab “available”
    await scrims.openTab("available");
Assert
  1. request buttons count is 2
    await expect(scrims.locators.requestButtons).toHaveCount(2);
Act
  1. Request first → request
    const request = await scrims.requestFirst();
Scrim request modal
Act
  1. Send
    await request.send();
Scrims page
Assert
  1. request buttons count is 1
    await expect(scrims.locators.requestButtons).toHaveCount(1);
Act
  1. Show pending requests
    await scrims.showPendingRequests();
  2. Cancel pending request
    await scrims.cancelPendingRequest();
Assert
  1. request buttons count is 2
    await expect(scrims.locators.requestButtons).toHaveCount(2);
Source
test("requests a post and withdraws the request", async ({
	page,
	factories,
}) => {
	for (let postNth = 0; postNth < 2; postNth++) {
		await factories.ScrimPostFactory.create({
			users: await createGroup(factories),
		});
	}
	await createTeamFor(factories, NZAP_TEST_ID);

	await impersonate(page, NZAP_TEST_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();
	await scrims.openTab("available");

	await expect(scrims.locators.requestButtons).toHaveCount(2);

	const request = await scrims.requestFirst();
	await request.send();

	await expect(scrims.locators.requestButtons).toHaveCount(1);

	await scrims.showPendingRequests();
	await scrims.cancelPendingRequest();

	await expect(scrims.locators.requestButtons).toHaveCount(2);
})

a request to a post with a start window picks the start and can carry a message

Renders/scrims

Setup
Arrange
  1. at = tomorrowEvening()
    const at = tomorrowEvening();
  2. Create scrim post users createGroup(factories, ADMIN_ID) starts at dateToDatabaseTimestamp(at) range ends at dateToDatabaseTimestamp(addHours(at, 2)) is scheduled for future true
    await factories.ScrimPostFactory.create({
    	users: await createGroup(factories, ADMIN_ID),
    	startsAt: dateToDatabaseTimestamp(at),
    	rangeEndsAt: dateToDatabaseTimestamp(addHours(at, 2)),
    	isScheduledForFuture: true,
    });
  3. Create named users “Pickup One”, “Pickup Two”, “Pickup Three”
    await createNamedUsers(factories, PICKUP_NAMES);

1N-ZAP requests it with a pick-up, a start and a message

NN-ZAP
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Open tab “available”
    await scrims.openTab("available");
  3. Request first → request
    const request = await scrims.requestFirst();
Scrim request modal
Act
  1. Select pickup users “Pickup One”, “Pickup Two”, “Pickup Three”
    await request.selectPickupUsers(PICKUP_NAMES);
  2. Select start time 1
    await request.selectStartTime(1);
  3. Fill Message with “Ready to scrim! Let's do this.”
    await request.form.fill("message", REQUEST_MESSAGE);
  4. Send
    await request.send();

2Sendou is notified and sees the message

SSendou
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
Notification popover
Act
  1. Open
    await notifications.open();
Assert
  1. notification “N-ZAP requested a scrim” is visible
    await expect(
    	notifications.notification("N-ZAP requested a scrim"),
    ).toBeVisible();
Act
  1. Close
    await notifications.close();
Scrims page
Assert
  1. post “Ready to scrim! Let's do this.” is visible
    await expect(scrims.post(REQUEST_MESSAGE)).toBeVisible();
Source
test("a request to a post with a start window picks the start and can carry a message", async ({
	page,
	factories,
}) => {
	const at = tomorrowEvening();
	await factories.ScrimPostFactory.create({
		users: await createGroup(factories, ADMIN_ID),
		startsAt: dateToDatabaseTimestamp(at),
		rangeEndsAt: dateToDatabaseTimestamp(addHours(at, 2)),
		isScheduledForFuture: true,
	});
	await createNamedUsers(factories, PICKUP_NAMES);

	await test.step("N-ZAP requests it with a pick-up, a start and a message", async () => {
		await impersonate(page, NZAP_TEST_ID);

		const scrims = new ScrimsPage(page);
		await scrims.goto();
		await scrims.openTab("available");

		const request = await scrims.requestFirst();
		await request.selectPickupUsers(PICKUP_NAMES);
		await request.selectStartTime(1);
		await request.form.fill("message", REQUEST_MESSAGE);
		await request.send();
	});

	await test.step("Sendou is notified and sees the message", async () => {
		await impersonate(page, ADMIN_ID);

		const scrims = new ScrimsPage(page);
		await scrims.goto();

		const notifications = new NotificationPopover(page);
		await notifications.open();

		await expect(
			notifications.notification("N-ZAP requested a scrim"),
		).toBeVisible();

		await notifications.close();

		await expect(scrims.post(REQUEST_MESSAGE)).toBeVisible();
	});
})

Accepting

Accepting a request books the scrim: it moves to the Booked tab with a page of its own, the requester is notified and the owner's other pending posts starting within an hour of it are deleted.

accepting resolves the request notification and notifies the requester

Renders//scrims/scrims/:id

Setup
Arrange
  1. Create post with request owner Sendou requester N-ZAP → post
    const post = await createPostWithRequest(factories, {
    	ownerUserId: ADMIN_ID,
    	requesterUserId: NZAP_TEST_ID,
    });
  2. Create notification notification type “SCRIM_NEW_REQUEST” meta from N-ZAP from username “N-ZAP” scrim post id post.id users user Sendou
    await factories.NotificationFactory.create({
    	notification: {
    		type: "SCRIM_NEW_REQUEST",
    		meta: {
    			fromUserId: NZAP_TEST_ID,
    			fromUsername: "N-ZAP",
    			scrimPostId: post.id,
    		},
    	},
    	users: [{ userId: ADMIN_ID }],
    });
SSendou
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
Notification popover
Assert
  1. bell dot is visible
    await expect(notifications.locators.bellDot).toBeVisible();
Scrims page
Act
  1. Accept first request
    await scrims.acceptFirstRequest();
Notification popover
Assert
  1. accepting settled the post, resolving the request notification without the bell having been opened

    bell dot is hidden
    await expect(notifications.locators.bellDot).toBeHidden();
Scrims page
Act
  1. Open tab “booked”
    await scrims.openTab("booked");
Assert
  1. contact links count is 1
    await expect(scrims.locators.contactLinks).toHaveCount(1);
Act
  1. Open first booked scrim → scrim
    const scrim = await scrims.openFirstBookedScrim();
Scrim page
Assert
  1. subtitle is visible
    await expect(scrim.locators.subtitle).toBeVisible();
NN-ZAP
Act
  1. Go to “/”
    await navigate({ page, url: "/" });
Notification popover
Act
  1. Open
    await notifications.open();
  2. Open notification “New scrim scheduled vs.”
    await notifications.openNotification("New scrim scheduled vs.");
Assert
  1. the page URL is scrimPage(post.id)
    await expect(page).toHaveURL(scrimPage(post.id));
Source
test("accepting resolves the request notification and notifies the requester", async ({
	page,
	factories,
}) => {
	const post = await createPostWithRequest(factories, {
		ownerUserId: ADMIN_ID,
		requesterUserId: NZAP_TEST_ID,
	});
	await factories.NotificationFactory.create({
		notification: {
			type: "SCRIM_NEW_REQUEST",
			meta: {
				fromUserId: NZAP_TEST_ID,
				fromUsername: "N-ZAP",
				scrimPostId: post.id,
			},
		},
		users: [{ userId: ADMIN_ID }],
	});

	await impersonate(page, ADMIN_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();

	const notifications = new NotificationPopover(page);
	await expect(notifications.locators.bellDot).toBeVisible();

	await scrims.acceptFirstRequest();

	// accepting settled the post, resolving the request notification without
	// the bell having been opened
	await expect(notifications.locators.bellDot).toBeHidden();

	await scrims.openTab("booked");
	await expect(scrims.locators.contactLinks).toHaveCount(1);

	const scrim = await scrims.openFirstBookedScrim();

	await expect(scrim.locators.subtitle).toBeVisible();

	// the requester got notified of the scheduled scrim, linking to its page
	await impersonate(page, NZAP_TEST_ID);
	await navigate({ page, url: "/" });

	await notifications.open();
	await notifications.openNotification("New scrim scheduled vs.");

	await expect(page).toHaveURL(scrimPage(post.id));
})

auto-cancels overlapping pending scrims when a scrim is booked

Renders/scrims/scrims/new

Setup
Arrange
  1. Create team for Sendou
    await createTeamFor(factories, ADMIN_ID);
  2. Create team for N-ZAP
    await createTeamFor(factories, NZAP_TEST_ID);
  3. bookedAt = tomorrowEvening()
    const bookedAt = tomorrowEvening();
  4. within ±1h of the booked time

    overlappingAt = setMinutes(bookedAt, 30)
    const overlappingAt = setMinutes(bookedAt, 30);
  5. outside the ±1h window

    farAwayAt = setHours(bookedAt, 22)
    const farAwayAt = setHours(bookedAt, 22);
SSendou
Act
  1. For each [at, text] of bookedAt, “Booked post”, overlappingAt, “Overlapping post”, farAwayAt, “Far away post”
    for (const [at, text] of [
    	[bookedAt, "Booked post"],
    	[overlappingAt, "Overlapping post"],
    	[farAwayAt, "Far away post"],
    ] as const) {
    	await newPost.goto();
    	await newPost.form.setDateTime("at", at);
    	await newPost.form.fill("postText", text);
    	await newPost.save();
    }
    1. Open the new scrim post page
      await newPost.goto();
    2. Set Start to at
      await newPost.form.setDateTime("at", at);
    3. Fill Text with text
      await newPost.form.fill("postText", text);
    4. Save
      await newPost.save();
NN-ZAP
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Open tab “available”
    await scrims.openTab("available");
  3. Request first → request
    const request = await scrims.requestFirst();
Scrim request modal
Act
  1. Send
    await request.send();
SSendou
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Accept first request
    await scrims.acceptFirstRequest();
  3. Open tab “owned”
    await scrims.openTab("owned");
Assert
  1. post “Far away post” is visible
    await expect(scrims.post("Far away post")).toBeVisible();
  2. post “Overlapping post” is not visible
    await isNotVisible(scrims.post("Overlapping post"));
Source
test("auto-cancels overlapping pending scrims when a scrim is booked", async ({
	page,
	factories,
}) => {
	await createTeamFor(factories, ADMIN_ID);
	await createTeamFor(factories, NZAP_TEST_ID);

	const bookedAt = tomorrowEvening();
	// within ±1h of the booked time
	const overlappingAt = setMinutes(bookedAt, 30);
	// outside the ±1h window
	const farAwayAt = setHours(bookedAt, 22);

	await impersonate(page, ADMIN_ID);

	const newPost = new NewScrimPostPage(page);
	for (const [at, text] of [
		[bookedAt, "Booked post"],
		[overlappingAt, "Overlapping post"],
		[farAwayAt, "Far away post"],
	] as const) {
		await newPost.goto();
		await newPost.form.setDateTime("at", at);
		await newPost.form.fill("postText", text);
		await newPost.save();
	}

	// N-ZAP requests the earliest (soon-to-be-booked) post
	await impersonate(page, NZAP_TEST_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();
	await scrims.openTab("available");

	const request = await scrims.requestFirst();
	await request.send();

	// the author accepts the request, booking the scrim
	await impersonate(page, ADMIN_ID);
	await scrims.goto();
	await scrims.acceptFirstRequest();

	await scrims.openTab("owned");

	await expect(scrims.post("Far away post")).toBeVisible();
	await isNotVisible(scrims.post("Overlapping post"));
})

Canceling

A booked scrim is canceled from its page with a reason, and is then shown as canceled.

cancels a scrim and shows canceled status

Renders/scrims/scrims/:id

Setup
Arrange
  1. Create post with request owner Sendou at addHours(new Date(), 4)
    await createPostWithRequest(factories, {
    	ownerUserId: ADMIN_ID,
    	at: addHours(new Date(), 4),
    });
SSendou
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. accepting is what makes the scrim's own page accessible

    Accept first request
    await scrims.acceptFirstRequest();
  3. Open tab “booked”
    await scrims.openTab("booked");
  4. Open first booked scrim → scrim
    const scrim = await scrims.openFirstBookedScrim();
Scrim page
Act
  1. Cancel “Oops something came up”
    await scrim.cancel("Oops something came up");
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
Assert
  1. canceled label is visible
    await expect(scrims.locators.canceledLabel).toBeVisible();
Source
test("cancels a scrim and shows canceled status", async ({
	page,
	factories,
}) => {
	await createPostWithRequest(factories, {
		ownerUserId: ADMIN_ID,
		at: addHours(new Date(), 4),
	});

	await impersonate(page, ADMIN_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();

	// accepting is what makes the scrim's own page accessible
	await scrims.acceptFirstRequest();
	await scrims.openTab("booked");

	const scrim = await scrims.openFirstBookedScrim();
	await scrim.cancel("Oops something came up");

	await scrims.goto();

	await expect(scrims.locators.canceledLabel).toBeVisible();
})

Filters

Filters (here LUTI divisions) narrow the Available tab and can be saved as the user's default. Once the user edits the filters the page's own wins over the saved default.

filters by div and sets the filter as default

Renders/scrims

Setup
Arrange
  1. Create scrim post users createGroup(factories) max div serializeLutiDiv("1") min div serializeLutiDiv("2")
    await factories.ScrimPostFactory.create({
    	users: await createGroup(factories),
    	maxDiv: serializeLutiDiv("1"),
    	minDiv: serializeLutiDiv("2"),
    });
NN-ZAP
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Open tab “available”
    await scrims.openTab("available");
Assert
  1. request buttons count is 1
    await expect(scrims.locators.requestButtons).toHaveCount(1);
Act
  1. a div range the post's own range falls outside of

    Filter by divs max “5” min “6”
    await scrims.filterByDivs({ max: "5", min: "6" });
Assert
  1. request buttons count is 0
    await expect(scrims.locators.requestButtons).toHaveCount(0);
Act
  1. Save filters as default
    await scrims.saveFiltersAsDefault();
  2. Open the scrims page
    await scrims.goto();
  3. Open tab “available”
    await scrims.openTab("available");
Assert
  1. remembers selection via user preferences

    request buttons count is 0
    await expect(scrims.locators.requestButtons).toHaveCount(0);
Act
  1. Remove divs filter
    await scrims.removeDivsFilter();
Assert
  1. removing the filter sticks instead of falling back to the saved default

    request buttons count is 1
    await expect(scrims.locators.requestButtons).toHaveCount(1);
Act
  1. Reload
    await scrims.reload();
Assert
  1. request buttons count is 1
    await expect(scrims.locators.requestButtons).toHaveCount(1);
Source
test("filters by div and sets the filter as default", async ({
	page,
	factories,
}) => {
	await factories.ScrimPostFactory.create({
		users: await createGroup(factories),
		maxDiv: serializeLutiDiv("1"),
		minDiv: serializeLutiDiv("2"),
	});

	await impersonate(page, NZAP_TEST_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();
	await scrims.openTab("available");

	await expect(scrims.locators.requestButtons).toHaveCount(1);

	// a div range the post's own range falls outside of
	await scrims.filterByDivs({ max: "5", min: "6" });

	await expect(scrims.locators.requestButtons).toHaveCount(0);

	await scrims.saveFiltersAsDefault();
	await scrims.goto();
	await scrims.openTab("available");

	// remembers selection via user preferences
	await expect(scrims.locators.requestButtons).toHaveCount(0);

	await scrims.removeDivsFilter();

	// removing the filter sticks instead of falling back to the saved default
	await expect(scrims.locators.requestButtons).toHaveCount(1);

	await scrims.reload();

	await expect(scrims.locators.requestButtons).toHaveCount(1);
})

Map-by-map

Once booked, each side submits a map list (the post's tournament or a map pool) and maps are generated one at a time from the lists, each reported map generating the next. The latest report can be undone and the current map swapped for a replay of the previous one. Stats count the reported maps from the viewer's side.

plays a scrim map by map

Renders/scrims/:id

Setup
Arrange
  1. Create tournament → tournament
    const tournament = await createTournament(factories);
  2. Create post with request owner Sendou requester N-ZAP maps tournament id tournament.id is accepted true → post
    const post = await createPostWithRequest(factories, {
    	ownerUserId: ADMIN_ID,
    	requesterUserId: NZAP_TEST_ID,
    	mapsTournamentId: tournament.id,
    	isAccepted: true,
    });

1Sendou submits the post's tournament as Alpha's list, generating the first map

SSendou
Scrim page
Act
  1. Open the scrim page post.id
    await scrim.goto(post.id);
  2. Open tab “action”
    await scrim.openTab("action");
Assert
  1. map list form is visible
    await expect(scrim.locators.mapListForm).toBeVisible();
Act
  1. the post's tournament is the author's default source, so no tournament search is needed; the first map list submitted collapses the map-list manager

    Submit map list
    await scrim.submitMapList();
Assert
  1. report score button is visible
    await expect(scrim.locators.reportScoreButton).toBeVisible();
Act
  1. Open map list manager
    await scrim.openMapListManager();
Assert
  1. map list row “ALPHA” contains “Swim or Sink”
    await expect(scrim.mapListRow("ALPHA")).toContainText(TOURNAMENT_NAME);

2N-ZAP adds a map pool as Bravo's list

NN-ZAP
Scrim page
Act
  1. Open the scrim page post.id
    await scrim.goto(post.id);
  2. Open tab “action”
    await scrim.openTab("action");
  3. Submit pool map list “sz:3a14000;tc:2c98000”
    await scrim.submitPoolMapList(TEST_POOL_SERIALIZED);
Assert
  1. report score button is visible
    await expect(scrim.locators.reportScoreButton).toBeVisible();
  2. map list row “BRAVO” contains “Pool”
    await expect(scrim.mapListRow("BRAVO")).toContainText("Pool");

3Each reported winner generates the next map

NN-ZAP
Scrim page
Act
  1. Report map winner “ALPHA”
    await scrim.reportMapWinner("ALPHA");
Assert
  1. report score button is visible
    await expect(scrim.locators.reportScoreButton).toBeVisible();
Act
  1. Report map winner “BRAVO”
    await scrim.reportMapWinner("BRAVO");
Assert
  1. report score button is visible
    await expect(scrim.locators.reportScoreButton).toBeVisible();

4Undo un-reports the latest map and removes the one it generated

NN-ZAP
Scrim page
Act
  1. Report map winner “ALPHA”
    await scrim.reportMapWinner("ALPHA");
Assert
  1. undo map button is visible
    await expect(scrim.locators.undoMapButton).toBeVisible();
Act
  1. Undo map
    await scrim.undoMap();
Assert
  1. report score button is visible
    await expect(scrim.locators.reportScoreButton).toBeVisible();

5Replay swaps the current map for the previous one

NN-ZAP
Scrim page
Act
  1. Report map winner “BRAVO”
    await scrim.reportMapWinner("BRAVO");
Assert
  1. replay map button is visible
    await expect(scrim.locators.replayMapButton).toBeVisible();
Act
  1. Replay map
    await scrim.replayMap();
  2. Report map winner “ALPHA”
    await scrim.reportMapWinner("ALPHA");

6Sendou swaps Alpha's list for a map pool

SSendou
Scrim page
Act
  1. Open the scrim page post.id
    await scrim.goto(post.id);
  2. Open tab “action”
    await scrim.openTab("action");
  3. Open map list manager
    await scrim.openMapListManager();
  4. Remove own map list “ALPHA”
    await scrim.removeOwnMapList("ALPHA");
Assert
  1. map list form is visible
    await expect(scrim.locators.mapListForm).toBeVisible();
Act
  1. Submit pool map list “sz:3a14000;tc:2c98000”
    await scrim.submitPoolMapList(TEST_POOL_SERIALIZED);
Assert
  1. map list row “ALPHA” contains “Pool”
    await expect(scrim.mapListRow("ALPHA")).toContainText("Pool");

7Stats count the four reported maps

SSendou
Scrim page
Act
  1. Open tab “stats”
    await scrim.openTab("stats");
Assert
  1. stats root is visible
    await expect(scrim.locators.statsRoot).toBeVisible();
Act
  1. 2 wins & 2 losses from Sendou's point of view

    Show mode stats of all maps
    await scrim.showModeStatsOfAllMaps();
Assert
  1. reported map count is 4
    expect(await scrim.reportedMapCount()).toBe(4);
Source
test("plays a scrim map by map", async ({ page, factories }) => {
	const tournament = await createTournament(factories);
	const post = await createPostWithRequest(factories, {
		ownerUserId: ADMIN_ID,
		requesterUserId: NZAP_TEST_ID,
		mapsTournamentId: tournament.id,
		isAccepted: true,
	});
	const scrim = new ScrimPage(page);

	await test.step("Sendou submits the post's tournament as Alpha's list, generating the first map", async () => {
		await impersonate(page, ADMIN_ID);
		await scrim.goto(post.id);
		await scrim.openTab("action");

		await expect(scrim.locators.mapListForm).toBeVisible();

		// the post's tournament is the author's default source, so no tournament search is
		// needed; the first map list submitted collapses the map-list manager
		await scrim.submitMapList();

		await expect(scrim.locators.reportScoreButton).toBeVisible();

		await scrim.openMapListManager();

		await expect(scrim.mapListRow("ALPHA")).toContainText(TOURNAMENT_NAME);
	});

	await test.step("N-ZAP adds a map pool as Bravo's list", async () => {
		// N-ZAP has no list yet, so the map-list manager is already expanded on mount
		await impersonate(page, NZAP_TEST_ID);
		await scrim.goto(post.id);
		await scrim.openTab("action");
		await scrim.submitPoolMapList(TEST_POOL_SERIALIZED);

		await expect(scrim.locators.reportScoreButton).toBeVisible();
		await expect(scrim.mapListRow("BRAVO")).toContainText("Pool");
	});

	await test.step("Each reported winner generates the next map", async () => {
		await scrim.reportMapWinner("ALPHA");
		await expect(scrim.locators.reportScoreButton).toBeVisible();

		await scrim.reportMapWinner("BRAVO");
		await expect(scrim.locators.reportScoreButton).toBeVisible();
	});

	await test.step("Undo un-reports the latest map and removes the one it generated", async () => {
		await scrim.reportMapWinner("ALPHA");
		await expect(scrim.locators.undoMapButton).toBeVisible();
		await scrim.undoMap();
		await expect(scrim.locators.reportScoreButton).toBeVisible();
	});

	await test.step("Replay swaps the current map for the previous one", async () => {
		await scrim.reportMapWinner("BRAVO");

		await expect(scrim.locators.replayMapButton).toBeVisible();
		await scrim.replayMap();
		await scrim.reportMapWinner("ALPHA");
	});

	await test.step("Sendou swaps Alpha's list for a map pool", async () => {
		await impersonate(page, ADMIN_ID);
		await scrim.goto(post.id);
		await scrim.openTab("action");
		await scrim.openMapListManager();

		await scrim.removeOwnMapList("ALPHA");

		await expect(scrim.locators.mapListForm).toBeVisible();

		await scrim.submitPoolMapList(TEST_POOL_SERIALIZED);

		await expect(scrim.mapListRow("ALPHA")).toContainText("Pool");
	});

	await test.step("Stats count the four reported maps", async () => {
		await scrim.openTab("stats");

		await expect(scrim.locators.statsRoot).toBeVisible();

		// 2 wins & 2 losses from Sendou's point of view
		await scrim.showModeStatsOfAllMaps();

		expect(await scrim.reportedMapCount()).toBe(4);
	});
})

Availability

The members' weekly availability suggests starts when posting and shows how much of the viewer's roster could play each post. A slot one player short of a full team is still offered, as one to grab a sub for.

picks a start and its flexibility from the roster's shared free time

Renders/scrims/new

Setup
Arrange
  1. Create team for N-ZAP → { memberUserIds }
    const { memberUserIds } = await createTeamFor(factories, NZAP_TEST_ID);
  2. evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00")
    const evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00");
  3. For each userId of memberUserIds.slice(0, memberUserIds.length - 1)
    for (const userId of memberUserIds.slice(0, memberUserIds.length - 1)) {
    	await factories.AvailabilityWeekFactory.create({
    		userId,
    		weekStartsAt: nextWeek().startsAt,
    		timezone: MACHINE_TIMEZONE,
    		slots: [evening],
    	});
    }
    1. Create availability week userId week starts at nextWeek().startsAt timezone MACHINE_TIMEZONE slots evening
      await factories.AvailabilityWeekFactory.create({
      	userId,
      	weekStartsAt: nextWeek().startsAt,
      	timezone: MACHINE_TIMEZONE,
      	slots: [evening],
      });
NN-ZAP
New scrim post page
Act
  1. Open the new scrim post page
    await newPost.goto();
  2. Click next week toggle
    await newPost.locators.nextWeekToggle.click();
Assert
  1. the roster's last member never filled the week in, so the shared evening is one player short of a full team

    schedule unknown is visible
    await expect(newPost.locators.scheduleUnknown).toBeVisible();
  2. slot = schedule slots
    const slot = newPost.locators.scheduleSlots;
  3. slot count is 1
    await expect(slot).toHaveCount(1);
  4. slot has “data-tier” = “ONE_SHORT”
    await expect(slot).toHaveAttribute("data-tier", "ONE_SHORT");
Act
  1. Click slot
    await slot.click();
Assert
  1. 18:00, with the flexibility that still leaves an hour of the window to play whichever start is settled on, capped at the longest option

    start input has value /T18:00$/
    await expect(newPost.startInput).toHaveValue(/T18:00$/);
  2. flexibility has value “+3hours”
    await expect(newPost.locators.flexibility).toHaveValue("+3hours");
  3. slot has “data-picked” = “true”
    await expect(slot).toHaveAttribute("data-picked", "true");
Source
test("picks a start and its flexibility from the roster's shared free time", async ({
	page,
	factories,
}) => {
	const { memberUserIds } = await createTeamFor(factories, NZAP_TEST_ID);
	const evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00");

	for (const userId of memberUserIds.slice(0, memberUserIds.length - 1)) {
		await factories.AvailabilityWeekFactory.create({
			userId,
			weekStartsAt: nextWeek().startsAt,
			timezone: MACHINE_TIMEZONE,
			slots: [evening],
		});
	}

	await impersonate(page, NZAP_TEST_ID);
	await setTimezoneCookie(page);

	const newPost = new NewScrimPostPage(page);
	await newPost.goto();
	await newPost.locators.nextWeekToggle.click();

	// the roster's last member never filled the week in, so the shared
	// evening is one player short of a full team
	await expect(newPost.locators.scheduleUnknown).toBeVisible();
	const slot = newPost.locators.scheduleSlots;
	await expect(slot).toHaveCount(1);
	await expect(slot).toHaveAttribute("data-tier", "ONE_SHORT");

	await slot.click();

	// 18:00, with the flexibility that still leaves an hour of the window
	// to play whichever start is settled on, capped at the longest option
	await expect(newPost.startInput).toHaveValue(/T18:00$/);
	await expect(newPost.locators.flexibility).toHaveValue("+3hours");
	await expect(slot).toHaveAttribute("data-picked", "true");
})

shows how much of the viewer's roster could play a post

Renders/scrims

Setup
Arrange
  1. Create team for N-ZAP → { memberUserIds }
    const { memberUserIds } = await createTeamFor(factories, NZAP_TEST_ID);
  2. evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00")
    const evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00");
  3. withoutSchedule = memberUserIds[memberUserIds.length - 1]
    const withoutSchedule = memberUserIds[memberUserIds.length - 1];
  4. For each userId of memberUserIds.filter( (candidate) => candidate !== withoutSchedule, )
    for (const userId of memberUserIds.filter(
    	(candidate) => candidate !== withoutSchedule,
    )) {
    	await factories.AvailabilityWeekFactory.create({
    		userId,
    		weekStartsAt: nextWeek().startsAt,
    		timezone: MACHINE_TIMEZONE,
    		slots: [evening],
    	});
    }
    1. Create availability week userId week starts at nextWeek().startsAt timezone MACHINE_TIMEZONE slots evening
      await factories.AvailabilityWeekFactory.create({
      	userId,
      	weekStartsAt: nextWeek().startsAt,
      	timezone: MACHINE_TIMEZONE,
      	slots: [evening],
      });
  5. Create scrim post users createGroup(factories) starts at evening.startsAt is scheduled for future true
    await factories.ScrimPostFactory.create({
    	users: await createGroup(factories),
    	startsAt: evening.startsAt,
    	isScheduledForFuture: true,
    });
NN-ZAP
Scrims page
Act
  1. Open the scrims page
    await scrims.goto();
  2. Open tab “available”
    await scrims.openTab("available");
Assert
  1. fit indicator contains “3/4 available”
    await expect(scrims.locators.fitIndicator).toContainText("3/4 available");
Act
  1. Click fit indicator
    await scrims.locators.fitIndicator.click();
Assert
  1. availability row N-ZAP has “data-status” = “available”
    await expect(scrims.availabilityRow(NZAP_TEST_ID)).toHaveAttribute(
    	"data-status",
    	"available",
    );
  2. availability row withoutSchedule has “data-status” = “unknown”
    await expect(scrims.availabilityRow(withoutSchedule)).toHaveAttribute(
    	"data-status",
    	"unknown",
    );
Act
  1. Press Escape
    await page.keyboard.press("Escape");
  2. Request first
    await scrims.requestFirst();
Assert
  1. the same breakdown, for the slot the request would be made for

    availability row N-ZAP has “data-status” = “available”
    await expect(scrims.availabilityRow(NZAP_TEST_ID)).toHaveAttribute(
    	"data-status",
    	"available",
    );
Source
test("shows how much of the viewer's roster could play a post", async ({
	page,
	factories,
}) => {
	const { memberUserIds } = await createTeamFor(factories, NZAP_TEST_ID);
	const evening = nextWeekSlot(WEDNESDAY, "18:00", "23:00");
	const withoutSchedule = memberUserIds[memberUserIds.length - 1];

	for (const userId of memberUserIds.filter(
		(candidate) => candidate !== withoutSchedule,
	)) {
		await factories.AvailabilityWeekFactory.create({
			userId,
			weekStartsAt: nextWeek().startsAt,
			timezone: MACHINE_TIMEZONE,
			slots: [evening],
		});
	}

	await factories.ScrimPostFactory.create({
		users: await createGroup(factories),
		startsAt: evening.startsAt,
		isScheduledForFuture: true,
	});

	await impersonate(page, NZAP_TEST_ID);

	const scrims = new ScrimsPage(page);
	await scrims.goto();
	await scrims.openTab("available");

	await expect(scrims.locators.fitIndicator).toContainText("3/4 available");

	await scrims.locators.fitIndicator.click();

	await expect(scrims.availabilityRow(NZAP_TEST_ID)).toHaveAttribute(
		"data-status",
		"available",
	);
	await expect(scrims.availabilityRow(withoutSchedule)).toHaveAttribute(
		"data-status",
		"unknown",
	);

	await page.keyboard.press("Escape");
	await scrims.requestFirst();

	// the same breakdown, for the slot the request would be made for
	await expect(scrims.availabilityRow(NZAP_TEST_ID)).toHaveAttribute(
		"data-status",
		"available",
	);
})