-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Room list: add default sections #32785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
0233ae0
feat: add sections to RLSV3
florianduros ef3e2ec
feat: add sections in vms
florianduros 4eaf924
feat: add room list section labs flag
florianduros 5a53aaa
fix: wrong margin for room list item when in sections
florianduros a89ccc7
feat: hide favourites and low priority filters
florianduros 6269d61
fix: crash when changing filter
florianduros 7d20f10
feat: support sticky room in sections
florianduros 690c501
test: update SC snapshot
florianduros 7b9de65
test: update SC screenshot
florianduros 39eabe1
test: update RLS tests
florianduros 41e7b21
test: add tests to RoomListSectionHeaderViewModel
florianduros 7c6c367
test: fix existing test in RoomListViewModel
florianduros 2fcd24a
test: add sections tests for RoomListViewModel
florianduros bff189e
test: add e2e tests for sections
florianduros 726481e
fix: incorrect selected room when expanding/collasping a section
florianduros ccbfbec
Merge branch 'develop' into florianduros/default-sections
florianduros a85bb86
fix: typo in `roomSkipList`
florianduros aa76629
feat: use one skip list with all filters instead of one list by tag
florianduros 9448318
chore: put back comment about `roomIndexInSection`
florianduros d76160f
chore: add missing `readonly`
florianduros 489f257
chore: add doc about possible undefined value for room item vm
florianduros 3fcf85f
Merge branch 'develop' into florianduros/default-sections
florianduros File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
260 changes: 260 additions & 0 deletions
260
apps/web/playwright/e2e/left-panel/room-list-panel/room-list-sections.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| /* | ||
| * Copyright 2025 New Vector Ltd. | ||
| * | ||
| * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| * Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import { type Locator, type Page } from "@playwright/test"; | ||
|
|
||
| import { expect, test } from "../../../element-web-test"; | ||
|
|
||
| test.describe("Room list sections", () => { | ||
| test.use({ | ||
| displayName: "Alice", | ||
| labsFlags: ["feature_new_room_list", "feature_room_list_sections"], | ||
| botCreateOpts: { | ||
| displayName: "BotBob", | ||
| autoAcceptInvites: true, | ||
| }, | ||
| }); | ||
|
|
||
| /** | ||
| * Get the room list | ||
| * @param page | ||
| */ | ||
| function getRoomList(page: Page): Locator { | ||
| return page.getByTestId("room-list"); | ||
| } | ||
|
|
||
| /** | ||
| * Get the primary filters | ||
| * @param page | ||
| */ | ||
| function getPrimaryFilters(page: Page): Locator { | ||
| return page.getByTestId("primary-filters"); | ||
| } | ||
|
|
||
| /** | ||
| * Get a section header toggle button by section name | ||
| * @param page | ||
| * @param sectionName The display name of the section (e.g. "Favourites", "Chats", "Low Priority") | ||
| */ | ||
| function getSectionHeader(page: Page, sectionName: string): Locator { | ||
| return getRoomList(page).getByRole("gridcell", { name: `Toggle ${sectionName} section` }); | ||
| } | ||
|
|
||
| test.beforeEach(async ({ page, app, user }) => { | ||
| // The notification toast is displayed above the search section | ||
| await app.closeNotificationToast(); | ||
|
|
||
| // focus the user menu to avoid to have hover decoration | ||
| await page.getByRole("button", { name: "User menu" }).focus(); | ||
| }); | ||
|
|
||
| test.describe("Section rendering", () => { | ||
| test.beforeEach(async ({ app, user }) => { | ||
| // Create regular rooms | ||
| for (let i = 0; i < 3; i++) { | ||
| await app.client.createRoom({ name: `room${i}` }); | ||
| } | ||
| }); | ||
|
|
||
| test("should render sections with correct rooms in each", { tag: "@screenshot" }, async ({ page, app }) => { | ||
| // Create a favourite room | ||
| const favouriteId = await app.client.createRoom({ name: "favourite room" }); | ||
| await app.client.evaluate(async (client, roomId) => { | ||
| await client.setRoomTag(roomId, "m.favourite"); | ||
| }, favouriteId); | ||
|
|
||
| // Create a low priority room | ||
| const lowPrioId = await app.client.createRoom({ name: "low prio room" }); | ||
| await app.client.evaluate(async (client, roomId) => { | ||
| await client.setRoomTag(roomId, "m.lowpriority"); | ||
| }, lowPrioId); | ||
|
|
||
| const roomList = getRoomList(page); | ||
|
|
||
| // All three section headers should be visible | ||
| await expect(getSectionHeader(page, "Favourites")).toBeVisible(); | ||
| await expect(getSectionHeader(page, "Chats")).toBeVisible(); | ||
| await expect(getSectionHeader(page, "Low Priority")).toBeVisible(); | ||
|
|
||
| // Ensure all rooms are visible | ||
| await expect(roomList.getByRole("row", { name: "Open room favourite room" })).toBeVisible(); | ||
| await expect(roomList.getByRole("row", { name: "Open room low prio room" })).toBeVisible(); | ||
| await expect(roomList.getByRole("row", { name: "Open room room0" })).toBeVisible(); | ||
|
|
||
| await expect(roomList).toMatchScreenshot("room-list-sections.png"); | ||
| }); | ||
|
|
||
| test("should only show non-empty sections", async ({ page, app }) => { | ||
| // No low priority rooms created, only regular and favourite rooms | ||
| const favouriteId = await app.client.createRoom({ name: "favourite room" }); | ||
| await app.client.evaluate(async (client, roomId) => { | ||
| await client.setRoomTag(roomId, "m.favourite"); | ||
| }, favouriteId); | ||
|
|
||
| // Chats and Favourites sections should still be visible | ||
| await expect(getSectionHeader(page, "Chats")).toBeVisible(); | ||
| await expect(getSectionHeader(page, "Favourites")).toBeVisible(); | ||
| // Low Priority sections should not be visible | ||
| await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible(); | ||
| }); | ||
|
|
||
| test("should render a flat list when there is only rooms in Chats section", async ({ page, app }) => { | ||
| // All sections should not be visible | ||
| await expect(getSectionHeader(page, "Chats")).not.toBeVisible(); | ||
| await expect(getSectionHeader(page, "Favourites")).not.toBeVisible(); | ||
| await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible(); | ||
| // It should be a flat list (using listbox a11y role) | ||
| await expect(page.getByRole("listbox", { name: "Room list", exact: true })).toBeVisible(); | ||
| await expect(getRoomList(page).getByRole("option", { name: "Open room room0" })).toBeVisible(); | ||
| }); | ||
| }); | ||
|
|
||
| test.describe("Section collapse and expand", () => { | ||
| [ | ||
| { section: "Favourites", roomName: "favourite room", tag: "m.favourite" }, | ||
| { section: "Low Priority", roomName: "low prio room", tag: "m.lowpriority" }, | ||
| ].forEach(({ section, roomName, tag }) => { | ||
| test(`should collapse and expand the ${section} section`, async ({ page, app }) => { | ||
| const roomId = await app.client.createRoom({ name: roomName }); | ||
| if (tag) { | ||
| await app.client.evaluate( | ||
| async (client, { roomId, tag }) => { | ||
| await client.setRoomTag(roomId, tag); | ||
| }, | ||
| { roomId, tag }, | ||
| ); | ||
| } | ||
|
|
||
| const roomList = getRoomList(page); | ||
| const sectionHeader = getSectionHeader(page, section); | ||
|
|
||
| // The room should be visible | ||
| await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible(); | ||
|
|
||
| // Collapse the section | ||
| await sectionHeader.click(); | ||
|
|
||
| // The room should no longer be visible | ||
| await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).not.toBeVisible(); | ||
|
|
||
| // The section header should still be visible | ||
| await expect(sectionHeader).toBeVisible(); | ||
|
|
||
| // Expand the section again | ||
| await sectionHeader.click(); | ||
|
|
||
| // The room should be visible again | ||
| await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible(); | ||
| }); | ||
| }); | ||
|
|
||
| test("should render collapsed section", { tag: "@screenshot" }, async ({ page, app }) => { | ||
| const favouriteId = await app.client.createRoom({ name: "favourite room" }); | ||
| await app.client.evaluate(async (client, roomId) => { | ||
| await client.setRoomTag(roomId, "m.favourite"); | ||
| }, favouriteId); | ||
|
|
||
| await app.client.createRoom({ name: "regular room" }); | ||
|
|
||
| const roomList = getRoomList(page); | ||
|
|
||
| // Collapse the Favourites section | ||
| await getSectionHeader(page, "Favourites").click(); | ||
|
|
||
| // Verify favourite room is hidden but regular room is still visible | ||
| await expect(roomList.getByRole("row", { name: "Open room favourite room" })).not.toBeVisible(); | ||
| await expect(roomList.getByRole("row", { name: "Open room regular room" })).toBeVisible(); | ||
|
|
||
| await expect(roomList).toMatchScreenshot("room-list-sections-collapsed.png"); | ||
| }); | ||
| }); | ||
|
|
||
| test.describe("Rooms placement in sections", () => { | ||
| test("should move a room between sections when tags change", async ({ page, app }) => { | ||
| await app.client.createRoom({ name: "my room" }); | ||
|
|
||
| const roomList = getRoomList(page); | ||
|
|
||
| // Flat list because there is only rooms in the Chats section | ||
| let roomItem = roomList.getByRole("option", { name: "Open room my room" }); | ||
| await expect(roomItem).toBeVisible(); | ||
|
|
||
| // Favourite the room via context menu | ||
| await roomItem.click({ button: "right" }); | ||
| await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click(); | ||
|
|
||
| // The Favourites section header should now be visible and the room should be under it | ||
| await expect(getSectionHeader(page, "Favourites")).toBeVisible(); | ||
| roomItem = roomList.getByRole("row", { name: "Open room my room" }); | ||
| await expect(roomItem).toBeVisible(); | ||
|
|
||
| // Unfavourite the room | ||
| await roomItem.hover(); | ||
| await roomItem.getByRole("button", { name: "More Options" }).click(); | ||
| await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click(); | ||
|
|
||
| // Mark the room as low priority via context menu | ||
| roomItem = roomList.getByRole("option", { name: "Open room my room" }); | ||
| await roomItem.click({ button: "right" }); | ||
| await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click(); | ||
|
|
||
| // The Low Priority section header should now be visible and the room should be under it | ||
| await expect(getSectionHeader(page, "Low Priority")).toBeVisible(); | ||
| roomItem = roomList.getByRole("row", { name: "Open room my room" }); | ||
| await expect(roomItem).toBeVisible(); | ||
| }); | ||
| }); | ||
|
|
||
| test.describe("Sections and filters interaction", () => { | ||
| test("should not show Favourite and Low Priority filters when sections are enabled", async ({ page, app }) => { | ||
| const primaryFilters = getPrimaryFilters(page); | ||
|
|
||
| // Expand the filter list to see all filters | ||
| const expandButton = primaryFilters.getByRole("button", { name: "Expand filter list" }); | ||
| await expandButton.click(); | ||
|
|
||
| // Favourite and Low Priority filters should NOT be visible since sections handle them | ||
| await expect(primaryFilters.getByRole("option", { name: "Favourite" })).not.toBeVisible(); | ||
|
|
||
| // Other filters should still be present | ||
| await expect(primaryFilters.getByRole("option", { name: "People" })).toBeVisible(); | ||
| await expect(primaryFilters.getByRole("option", { name: "Rooms" })).toBeVisible(); | ||
| await expect(primaryFilters.getByRole("option", { name: "Unread" })).toBeVisible(); | ||
| }); | ||
|
|
||
| test("should maintain sections when a filter is applied", async ({ page, app, bot }) => { | ||
| // Create a favourite room with unread messages | ||
| const favouriteId = await app.client.createRoom({ name: "fav with unread" }); | ||
| await app.client.evaluate(async (client, roomId) => { | ||
| await client.setRoomTag(roomId, "m.favourite"); | ||
| }, favouriteId); | ||
| await app.client.inviteUser(favouriteId, bot.credentials.userId); | ||
| await bot.joinRoom(favouriteId); | ||
| await bot.sendMessage(favouriteId, "Hello from favourite!"); | ||
|
|
||
| // Create a regular room with unread messages | ||
| const regularId = await app.client.createRoom({ name: "regular with unread" }); | ||
| await app.client.inviteUser(regularId, bot.credentials.userId); | ||
| await bot.joinRoom(regularId); | ||
| await bot.sendMessage(regularId, "Hello from regular!"); | ||
|
|
||
| // Create a room without unread | ||
| await app.client.createRoom({ name: "no unread room" }); | ||
|
|
||
| const roomList = getRoomList(page); | ||
| const primaryFilters = getPrimaryFilters(page); | ||
|
|
||
| // Apply the Unread filter | ||
| await primaryFilters.getByRole("option", { name: "Unread" }).click(); | ||
|
|
||
| // Only rooms with unreads should be visible | ||
| await expect(roomList.getByRole("row", { name: "fav with unread" })).toBeVisible(); | ||
| await expect(roomList.getByRole("row", { name: "regular with unread" })).toBeVisible(); | ||
| await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible(); | ||
| }); | ||
| }); | ||
| }); |
Binary file added
BIN
+7.12 KB
...om-list-panel/room-list-sections.spec.ts/room-list-sections-collapsed-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15 KB
...t-panel/room-list-panel/room-list-sections.spec.ts/room-list-sections-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.