Room list: add custom section creation (#33155)
* feat: add creation section dialog * feat: add in skip list a method to change filters * feat: add helper to creation section * feat: add custom sections data to Settings * feat: add custom section to room list store v3 * feat: update header and room list item vms * feat: add toast to room list vm * feat: add new translation * chore: move util functions of room list specs * test: add custom section playwright tests * chore: call loadCustomSections in RoomListStoreV3 ctor
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations 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 Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { getRoomList, getRoomListHeader, getSectionHeader } from "./utils";
|
||||
|
||||
test.describe("Room list custom sections", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
labsFlags: ["feature_new_room_list", "feature_room_list_sections"],
|
||||
botCreateOpts: {
|
||||
displayName: "BotBob",
|
||||
autoAcceptInvites: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a custom section via the header compose menu and dialog.
|
||||
* @param page
|
||||
* @param sectionName The name of the section to create
|
||||
*/
|
||||
async function createCustomSection(page: Page, sectionName: string): Promise<void> {
|
||||
const composeMenu = getRoomListHeader(page).getByRole("button", { name: "New conversation" });
|
||||
await composeMenu.click();
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// Fill in the section name in the dialog
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("textbox", { name: "Section name" }).fill(sectionName);
|
||||
await dialog.getByRole("button", { name: "Create section" }).click();
|
||||
|
||||
// Wait for the dialog to close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
// Focus the user menu to avoid hover decoration
|
||||
await page.getByRole("button", { name: "User menu" }).focus();
|
||||
});
|
||||
|
||||
test.describe("Section creation", () => {
|
||||
test("should create a custom section via the header compose menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
// The custom section header should be visible (even though it is empty)
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
// The Chats section should also be visible
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should show 'Section created' toast after creating a section", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Personal");
|
||||
|
||||
// The "Section created" toast should appear
|
||||
await expect(page.getByText("Section created")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should create a custom section via the room option menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const roomItem = roomList.getByRole("option", { name: "Open room my room" });
|
||||
await expect(roomItem).toBeVisible();
|
||||
|
||||
// Open the More Options menu
|
||||
await roomItem.hover();
|
||||
await roomItem.getByRole("button", { name: "More Options" }).click();
|
||||
|
||||
// Open the "Move to" submenu
|
||||
await page.getByRole("menuitem", { name: "Move to" }).hover();
|
||||
|
||||
// Click on "New section"
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// Fill in the section name in the dialog
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("textbox", { name: "Section name" }).fill("Projects");
|
||||
await dialog.getByRole("button", { name: "Create section" }).click();
|
||||
|
||||
// Wait for the dialog to close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
|
||||
// The custom section should be created
|
||||
await expect(getSectionHeader(page, "Projects")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should cancel section creation when dialog is dismissed", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
const composeMenu = getRoomListHeader(page).getByRole("button", { name: "New conversation" });
|
||||
await composeMenu.click();
|
||||
await page.getByRole("menuitem", { name: "New section" }).click();
|
||||
|
||||
// The dialog should appear
|
||||
const dialog = page.getByRole("dialog", { name: "Create a section" });
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Cancel the dialog
|
||||
await dialog.getByRole("button", { name: "Cancel" }).click();
|
||||
|
||||
// The dialog should close
|
||||
await expect(dialog).not.toBeVisible();
|
||||
|
||||
// No custom section should be created - should remain a flat list
|
||||
await expect(getSectionHeader(page, "Chats")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should create multiple custom sections", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Work");
|
||||
await createCustomSection(page, "Personal");
|
||||
|
||||
// Both custom sections should be visible
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Personal")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Custom section display", () => {
|
||||
test("should show empty custom sections", async ({ page, app }) => {
|
||||
// Create a room so the Chats section has something
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
await createCustomSection(page, "Empty Section");
|
||||
|
||||
// The custom section should be visible even with no rooms
|
||||
await expect(getSectionHeader(page, "Empty Section")).toBeVisible();
|
||||
// The room should still be in the Chats section
|
||||
const roomList = getRoomList(page);
|
||||
await expect(roomList.getByRole("row", { name: "Open room my room" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should display custom sections between Favourites and Chats", 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);
|
||||
|
||||
// Create a regular room
|
||||
await app.client.createRoom({ name: "regular room" });
|
||||
|
||||
// Create a custom section
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
// All section headers should be visible
|
||||
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
// Should be expanded by default
|
||||
await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,10 +6,11 @@
|
||||
*/
|
||||
|
||||
import { type Visibility } from "matrix-js-sdk/src/matrix";
|
||||
import { type Locator, type Page } from "@playwright/test";
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { getFilterCollapseButton, getFilterExpandButton, getPrimaryFilters, getRoomOptionsMenu } from "./utils";
|
||||
|
||||
test.describe("Room list filters and sort", () => {
|
||||
test.use({
|
||||
@@ -21,22 +22,6 @@ test.describe("Room list filters and sort", () => {
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
function getPrimaryFilters(page: Page): Locator {
|
||||
return page.getByTestId("primary-filters");
|
||||
}
|
||||
|
||||
function getRoomOptionsMenu(page: Page): Locator {
|
||||
return page.getByRole("button", { name: "Room Options" });
|
||||
}
|
||||
|
||||
function getFilterExpandButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
|
||||
}
|
||||
|
||||
function getFilterCollapseButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
|
||||
@@ -6,21 +6,13 @@
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import type { Page } from "@playwright/test";
|
||||
import { getHeaderSection } from "./utils";
|
||||
|
||||
test.describe("Header section of the room list", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the header section of the room list
|
||||
* @param page
|
||||
*/
|
||||
function getHeaderSection(page: Page) {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -5,23 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import { getRoomListView } from "./utils";
|
||||
|
||||
test.describe("Room list panel", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the room list view
|
||||
* @param page
|
||||
*/
|
||||
function getRoomListView(page: Page) {
|
||||
return page.getByRole("navigation", { name: "Room list" });
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -5,23 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Page } from "@playwright/test";
|
||||
|
||||
import { test, expect } from "../../../element-web-test";
|
||||
import { getSearchSection } from "./utils";
|
||||
|
||||
test.describe("Search section of the room list", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the search section of the room list
|
||||
* @param page
|
||||
*/
|
||||
function getSearchSection(page: Page) {
|
||||
return page.getByRole("search");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
* 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";
|
||||
import { getPrimaryFilters, getRoomList, getSectionHeader } from "./utils";
|
||||
|
||||
test.describe("Room list sections", () => {
|
||||
test.use({
|
||||
@@ -19,34 +18,6 @@ test.describe("Room list sections", () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 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")
|
||||
* @param isUnread Whether to look for the unread version of the section header
|
||||
*/
|
||||
function getSectionHeader(page: Page, sectionName: string, isUnread = false): Locator {
|
||||
return getRoomList(page).getByRole("gridcell", {
|
||||
name: isUnread ? `Toggle ${sectionName} section with unread room(s)` : `Toggle ${sectionName} section`,
|
||||
});
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -10,6 +10,7 @@ import { type Page } from "@playwright/test";
|
||||
import { expect, test } from "../../../element-web-test";
|
||||
import { type Bot } from "../../../pages/bot";
|
||||
import { type ElementAppPage } from "../../../pages/ElementAppPage";
|
||||
import { getRoomList } from "./utils";
|
||||
|
||||
test.describe("Room list", () => {
|
||||
test.use({
|
||||
@@ -20,14 +21,6 @@ test.describe("Room list", () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
*/
|
||||
function getRoomList(page: Page) {
|
||||
return page.getByTestId("room-list");
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations 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";
|
||||
|
||||
/**
|
||||
* Get the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomList(page: Page): Locator {
|
||||
return page.getByTestId("room-list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomListHeader(page: Page): Locator {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section header toggle button by section name
|
||||
* @param page
|
||||
* @param sectionName The display name of the section
|
||||
* @param isUnread Whether to look for the unread version of the section header
|
||||
*/
|
||||
export function getSectionHeader(page: Page, sectionName: string, isUnread = false): Locator {
|
||||
return getRoomList(page).getByRole("gridcell", {
|
||||
name: isUnread ? `Toggle ${sectionName} section with unread room(s)` : `Toggle ${sectionName} section`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary filters container
|
||||
* @param page
|
||||
*/
|
||||
export function getPrimaryFilters(page: Page): Locator {
|
||||
return page.getByTestId("primary-filters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room options menu button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomOptionsMenu(page: Page): Locator {
|
||||
return page.getByRole("button", { name: "Room Options" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter list expand button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getFilterExpandButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter list collapse button in the room list header
|
||||
* @param page
|
||||
*/
|
||||
export function getFilterCollapseButton(page: Page): Locator {
|
||||
return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header section of the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getHeaderSection(page: Page) {
|
||||
return page.getByTestId("room-list-header");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room list view
|
||||
* @param page
|
||||
*/
|
||||
export function getRoomListView(page: Page) {
|
||||
return page.getByRole("navigation", { name: "Room list" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search section of the room list
|
||||
* @param page
|
||||
*/
|
||||
export function getSearchSection(page: Page) {
|
||||
return page.getByRole("search");
|
||||
}
|
||||
Reference in New Issue
Block a user