Requests a sent request shows as pending to both users Renders/friends
Sendou sends N-ZAP a friend request N-ZAP is notified and can accept it 1 Sendou sends N-ZAP a friend request
Friends page Act
Open the friends page
await friends.goto ();Send request “N-ZAP”
await friends.sendRequest ("N-ZAP" );Assert
cancel request button is visible
await expect (friends.locators.cancelRequestButton).toBeVisible ();2 N-ZAP is notified and can accept it
Friends page Act
Open the friends page
await friends.goto ();Notification popover Assert
bell dot is visible
await expect (notifications.locators.bellDot).toBeVisible ();Friends page Assert
accept button is visible
await expect (friends.locators.acceptButton).toBeVisible ();Source test ("a sent request shows as pending to both users" , async ({ page }) => {
const friends = new FriendsPage (page);
const notifications = new NotificationPopover (page);
await test.step ("Sendou sends N-ZAP a friend request" , async () => {
await impersonate (page);
await friends.goto ();
await friends.sendRequest ("N-ZAP" );
await expect (friends.locators.cancelRequestButton).toBeVisible ();
});
await test.step ("N-ZAP is notified and can accept it" , async () => {
await impersonate (page, NZAP_TEST_ID );
await friends.goto ();
await expect (notifications.locators.bellDot).toBeVisible ();
await expect (friends.locators.acceptButton).toBeVisible ();
});
})accepting a request marks its notification read without opening the bell Renders/friends
Friends page Notification popover Friends page Notification popover Arrange
Request friendship from Sendou to N-ZAP
await requestFriendship (factories, { from : ADMIN_ID , to: NZAP_TEST_ID });Friends page Act
Open the friends page
await friends.goto ();Notification popover Assert
bell dot is visible
await expect (notifications.locators.bellDot).toBeVisible ();Friends page Act
Accept request
await friends.acceptRequest ();Notification popover Assert
bell dot is hidden
await expect (notifications.locators.bellDot).toBeHidden ();Act
Open
await notifications.open ();Assert
notification “Sendou sent you a friend request” is visible
await expect (
notifications.notification ("Sendou sent you a friend request" ),
).toBeVisible ();Source test ("accepting a request marks its notification read without opening the bell" , async ({
page,
factories,
}) => {
await requestFriendship (factories, { from : ADMIN_ID , to: NZAP_TEST_ID });
await impersonate (page, NZAP_TEST_ID );
const friends = new FriendsPage (page);
await friends.goto ();
const notifications = new NotificationPopover (page);
await expect (notifications.locators.bellDot).toBeVisible ();
await friends.acceptRequest ();
await expect (notifications.locators.bellDot).toBeHidden ();
await notifications.open ();
await expect (
notifications.notification ("Sendou sent you a friend request" ),
).toBeVisible ();
})deleting a friend removes them from the list Renders/friends
Friends page Arrange
Befriend Sendou
await befriend (factories, [ADMIN_ID ]);Friends page Act
Open the friends page
await friends.goto ();Delete friend
await friends.friend ("Sendou" ).deleteFriend ();Assert
no friends text is visible
await expect (friends.locators.noFriendsText).toBeVisible ();Source test ("deleting a friend removes them from the list" , async ({
page,
factories,
}) => {
await befriend (factories, [ADMIN_ID ]);
await impersonate (page, NZAP_TEST_ID );
const friends = new FriendsPage (page);
await friends.goto ();
await friends.friend ("Sendou" ).deleteFriend ();
await expect (friends.locators.noFriendsText).toBeVisible ();
})Weekly schedule friends who shared a schedule sort above those who didn't Renders/friends
Friends page Arrange
Friends with schedules → { scheduled, unscheduled, queueing }
const { scheduled, unscheduled, queueing } =
await friendsWithSchedules (factories);Friends page Act
Open the friends page
await friends.goto ();Assert
friends.row (queueing.id), friends.row (scheduled.id), friends.row (unscheduled.id) top to bottom
await expectTopToBottom ([
friends.row (queueing.id),
friends.row (scheduled.id),
friends.row (unscheduled.id),
]);schedule button unscheduled.id is not visible
await isNotVisible (friends.scheduleButton (unscheduled.id));Source test ("friends who shared a schedule sort above those who didn't" , async ({
page,
factories,
}) => {
const { scheduled, unscheduled, queueing } =
await friendsWithSchedules (factories);
await impersonate (page, ADMIN_ID );
const friends = new FriendsPage (page);
await friends.goto ();
await expectTopToBottom ([
friends.row (queueing.id),
friends.row (scheduled.id),
friends.row (unscheduled.id),
]);
await isNotVisible (friends.scheduleButton (unscheduled.id));
})a friend's week shows when they are free, not what they are doing Renders/friends
This week shows their free time but not the team event taking part of it Next week is empty as they only filled in this week Arrange
Friends with schedules → { scheduled }
const { scheduled } = await friendsWithSchedules (factories);Friends page Act
Open the friends page
await friends.goto ();Click schedule button scheduled.id
await friends.scheduleButton (scheduled.id).click ();1 This week shows their free time but not the team event taking part of it
Friends page Assert
schedule ranges count is 1
await expect (friends.locators.scheduleRanges).toHaveCount (1 );day 2 contains “6:00”
await expect (friends.day (WEDNESDAY )).toContainText ("6:00" );day 2 does not contain “VoD review”
await expect (friends.day (WEDNESDAY )).not.toContainText ("VoD review" );2 Next week is empty as they only filled in this week
Friends page Act
Click next week toggle
await friends.locators.nextWeekToggle.click ();Assert
no schedule text is visible
await expect (friends.locators.noScheduleText).toBeVisible ();Source test ("a friend's week shows when they are free, not what they are doing" , async ({
page,
factories,
}) => {
const { scheduled } = await friendsWithSchedules (factories);
await impersonate (page, ADMIN_ID );
await setTimezoneCookie (page);
const friends = new FriendsPage (page);
await friends.goto ();
await friends.scheduleButton (scheduled.id).click ();
await test.step ("This week shows their free time but not the team event taking part of it" , async () => {
await expect (friends.locators.scheduleRanges).toHaveCount (1 );
await expect (friends.day (WEDNESDAY )).toContainText ("6:00" );
await expect (friends.day (WEDNESDAY )).not.toContainText ("VoD review" );
});
await test.step ("Next week is empty as they only filled in this week" , async () => {
await friends.locators.nextWeekToggle.click ();
await expect (friends.locators.noScheduleText).toBeVisible ();
});
})Pinning pinned friends sort to the top of their section, on the page and in the sidebar Pins only reorder within a section: a pinned idle friend stays below everyone queueing.
Renders/friends
Sendou pins an idle and a queueing friend The page lists pinned friends first within each section The sidebar reorders too, without navigating Unpinning a friend turns their pin off again Arrange
Create named users “PinQueue” , “PlainQueue” , “PinIdle” , “PlainIdle” → [pinnedQueueing, queueing, pinnedIdle, idle]
const [pinnedQueueing, queueing, pinnedIdle, idle] =
await createNamedUsers (factories, [
"PinQueue" ,
"PlainQueue" ,
"PinIdle" ,
"PlainIdle" ,
]);Befriend idle.id, pinnedIdle.id, queueing.id, pinnedQueueing.id, Sendou
await befriend (
factories,
[idle.id, pinnedIdle.id, queueing.id, pinnedQueueing.id],
ADMIN_ID ,
);Create SQ group member user ids queueing.id
await factories.SQGroupFactory .create ({ memberUserIds: [queueing.id] });Create SQ group member user ids pinnedQueueing.id
await factories.SQGroupFactory .create ({
memberUserIds: [pinnedQueueing.id],
});Friends page Act
Open the friends page
await friends.goto ();1 Sendou pins an idle and a queueing friend
Friends page Act
Toggle pin pinnedIdle.id
await friends.togglePin (pinnedIdle.id);Toggle pin pinnedQueueing.id
await friends.togglePin (pinnedQueueing.id);Assert
pin button pinnedIdle.id has “aria-pressed” = “true”
await expect (friends.pinButton (pinnedIdle.id)).toHaveAttribute (
"aria-pressed" ,
"true" ,
);pin button idle.id has “aria-pressed” = “false”
await expect (friends.pinButton (idle.id)).toHaveAttribute (
"aria-pressed" ,
"false" ,
);2 The page lists pinned friends first within each section
Assert
friends.row (pinnedQueueing.id), friends.row (queueing.id), friends.row (pinnedIdle.id), friends.row (idle.id) top to bottom
await expectTopToBottom ([
friends.row (pinnedQueueing.id),
friends.row (queueing.id),
friends.row (pinnedIdle.id),
friends.row (idle.id),
]);3 The sidebar reorders too, without navigating
Assert
sideNav.friend ("PinQueue" ).trigger, sideNav.friend ("PlainQueue" ).trigger, sideNav.friend ("PinIdle" ).trigger, sideNav.friend ("PlainIdle" ).trigger top to bottom
await expectTopToBottom ([
sideNav.friend ("PinQueue" ).trigger,
sideNav.friend ("PlainQueue" ).trigger,
sideNav.friend ("PinIdle" ).trigger,
sideNav.friend ("PlainIdle" ).trigger,
]);4 Unpinning a friend turns their pin off again
Friends page Act
Open the friends page
await friends.goto ();Toggle pin pinnedIdle.id
await friends.togglePin (pinnedIdle.id);Assert
pin button pinnedIdle.id has “aria-pressed” = “false”
await expect (friends.pinButton (pinnedIdle.id)).toHaveAttribute (
"aria-pressed" ,
"false" ,
);Source test ("pinned friends sort to the top of their section, on the page and in the sidebar" , async ({
page,
factories,
}) => {
const [pinnedQueueing, queueing, pinnedIdle, idle] =
await createNamedUsers (factories, [
"PinQueue" ,
"PlainQueue" ,
"PinIdle" ,
"PlainIdle" ,
]);
await befriend (
factories,
[idle.id, pinnedIdle.id, queueing.id, pinnedQueueing.id],
ADMIN_ID ,
);
await factories.SQGroupFactory .create ({ memberUserIds: [queueing.id] });
await factories.SQGroupFactory .create ({
memberUserIds: [pinnedQueueing.id],
});
await impersonate (page, ADMIN_ID );
const friends = new FriendsPage (page);
await friends.goto ();
await test.step ("Sendou pins an idle and a queueing friend" , async () => {
await friends.togglePin (pinnedIdle.id);
await friends.togglePin (pinnedQueueing.id);
await expect (friends.pinButton (pinnedIdle.id)).toHaveAttribute (
"aria-pressed" ,
"true" ,
);
await expect (friends.pinButton (idle.id)).toHaveAttribute (
"aria-pressed" ,
"false" ,
);
});
await test.step ("The page lists pinned friends first within each section" , async () => {
await expectTopToBottom ([
friends.row (pinnedQueueing.id),
friends.row (queueing.id),
friends.row (pinnedIdle.id),
friends.row (idle.id),
]);
});
await test.step ("The sidebar reorders too, without navigating" , async () => {
const sideNav = new SideNav (page);
await expectTopToBottom ([
sideNav.friend ("PinQueue" ).trigger,
sideNav.friend ("PlainQueue" ).trigger,
sideNav.friend ("PinIdle" ).trigger,
sideNav.friend ("PlainIdle" ).trigger,
]);
});
await test.step ("Unpinning a friend turns their pin off again" , async () => {
await friends.goto ();
await friends.togglePin (pinnedIdle.id);
await expect (friends.pinButton (pinnedIdle.id)).toHaveAttribute (
"aria-pressed" ,
"false" ,
);
});
})FriendsPage.gotoe2e/pages/friends/friends-page.ts:35
No JSDoc
async goto () {
await navigate ({ page: this .page, url: FRIENDS_PAGE });
}FriendsPage.sendRequeste2e/pages/friends/friends-page.ts:39
No JSDoc
async sendRequest (userName: string ) {
await selectUser ({
page: this .page,
userName,
labelName: this .form.getLabel ("userId" ),
});
await submit (this .page);
}FriendsPage.locators.cancelRequestButtone2e/pages/friends/friends-page.ts:23
No JSDoc
this .page.getByRole ("button" , { name: "Cancel" })NotificationPopover.locators.bellDote2e/pages/layout/notification-popover.ts:29
this .page.getByTestId ("notifications-bell-dot" )FriendsPage.locators.acceptButtone2e/pages/friends/friends-page.ts:22
No JSDoc
this .page.getByRole ("button" , { name: "Accept" })FriendsPagee2e/pages/friends/friends-page.ts:13
NotificationPopovere2e/pages/layout/notification-popover.ts:6
requestFriendshipe2e/friends.spec.ts:204
async function requestFriendship (
factories: Factories ,
{ from , to }: { from : number ; to: number },
) {
await factories.FriendRequestFactory .create ({
senderId: from ,
receiverId: to,
});
await factories.NotificationFactory .create ({
notification: {
type : "FRIEND_REQUEST_RECEIVED" ,
meta: { senderId: from , senderUsername: "Sendou" },
},
users: [{ userId: to, seen: 0 }],
// … FriendsPage.acceptRequeste2e/pages/friends/friends-page.ts:49
async acceptRequest () {
await waitForPOSTResponse (this .page, () =>
this .locators.acceptButton.first ().click (),
);
}NotificationPopover.opene2e/pages/layout/notification-popover.ts:37
No JSDoc
async open () {
// the popover is closed on navigation, which would also close one opened
// after a click on a notification navigated but before the page rendered
await waitForNavigationRendered (this .page);
await expect (this .locators.shownPopover).toHaveCount (0 );
await this .locators.openButton.click ();
await expect (this .locators.shownPopover).toHaveCount (1 );
}NotificationPopover.notificatione2e/pages/layout/notification-popover.ts:50
No JSDoc
notification (text: string ) {
return this .locators.items.filter ({ hasText: text });
}befriende2e/helpers/sidebar.ts:46
No JSDoc
export function befriend (
factories: Factories ,
friendIds: number [],
userId = NZAP_TEST_ID ,
) {
return Promise .all (
friendIds.map ((friendId) =>
factories.FriendshipFactory .create ({
userOneId: userId,
userTwoId: friendId,
}),
),
);
}FriendsPage.locators.noFriendsTexte2e/pages/friends/friends-page.ts:24
No JSDoc
this .page.getByText ("No friends yet" )friendsWithSchedulese2e/friends.spec.ts:222
async function friendsWithSchedules (factories: Factories ) {
const [scheduled, unscheduled, queueing] = await createNamedUsers (factories, [
"ScheduleFriend" ,
"NoScheduleFriend" ,
"QueueFriend" ,
]);
await befriend (
factories,
[unscheduled.id, scheduled.id, queueing.id],
ADMIN_ID ,
);
await factories.SQGroupFactory .create ({ memberUserIds: [queueing.id] });
await factories.AvailabilityWeekFactory .create ({
// … expectTopToBottome2e/helpers/sidebar.ts:62
export async function expectTopToBottom (rows: Locator []) {
const tops: number [] = [];
for (const row of rows) {
await expect (row).toBeVisible ();
const box = await row.boundingBox ();
invariant (box, "Visible row has no bounding box" );
tops.push (box.y);
}
expect (tops).toEqual ([...tops].sort ((a, b) => a - b));
}FriendsPage.scheduleButtone2e/pages/friends/friends-page.ts:70
No JSDoc
scheduleButton (userId: number ) {
return this .page.getByTestId (`friend-schedule-button-${userId}` );
}isNotVisiblee2e/helpers/playwright.ts:745
No JSDoc
export function isNotVisible (locator: Locator ) {
return expect (locator).toHaveCount (0 );
}FriendsPage.locators.scheduleRangese2e/pages/friends/friends-page.ts:26
No JSDoc
this .page.getByTestId ("schedule-range" )FriendsPage.daye2e/pages/friends/friends-page.ts:83
day (dayIndex: number ) {
return this .locators.scheduleDays.getByRole ("listitem" ).nth (dayIndex);
}FriendsPage.locators.nextWeekTogglee2e/pages/friends/friends-page.ts:29
No JSDoc
this .page.locator (
'label[for="chip-radio-friend-schedule-week-next"]' ,
)FriendsPage.locators.noScheduleTexte2e/pages/friends/friends-page.ts:27
No JSDoc
this .page.getByTestId ("schedule-no-week" )createNamedUserse2e/helpers/sidebar.ts:29
export function createNamedUsers (
factories: Factories ,
names: string [],
overrides?: { twitch?: string },
) {
return factories.UserFactory .createMany (names.length, (index) => ({
...overrides,
discordName: names[index],
}));
}FriendsPage.togglePine2e/pages/friends/friends-page.ts:78
No JSDoc
async togglePin (userId: number ) {
await waitForPOSTResponse (this .page, () => this .pinButton (userId).click ());
}FriendsPage.pinButtone2e/pages/friends/friends-page.ts:74
No JSDoc
pinButton (userId: number ) {
return this .page.getByTestId (`friend-pin-button-${userId}` );
}