Skip to content

Commit 47f4a42

Browse files
committed
Merge branch 'develop' of ssh://github.com/element-hq/element-web into renovate/domutils-4.x
# Conflicts: # pnpm-lock.yaml
2 parents a245c4d + 4315038 commit 47f4a42

108 files changed

Lines changed: 7196 additions & 2944 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/dockerbuild/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31)
22
# with broader compatibility, down to Debian bullseye & Ubuntu focal.
33

4-
FROM rust:bullseye@sha256:16950191527a4cb9e0762d9d48b705a6315158e4035e64f7a93ce8656a1b053c
4+
FROM rust:bullseye@sha256:bc19574c121fe10c1bc68fc2b1ea9b420d87d047a0c50fb1622b282199700cee
55

66
ENV DEBIAN_FRONTEND=noninteractive
77

apps/desktop/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"@babel/core": "^7.18.10",
7272
"@babel/preset-env": "^7.18.10",
7373
"@babel/preset-typescript": "^7.18.6",
74-
"@electron/asar": "4.1.0",
74+
"@electron/asar": "4.1.2",
7575
"@playwright/test": "catalog:",
7676
"@stylistic/eslint-plugin": "^5.0.0",
7777
"@types/auto-launch": "^5.0.1",
@@ -84,7 +84,7 @@
8484
"app-builder-lib": "26.8.2",
8585
"chokidar": "^5.0.0",
8686
"detect-libc": "^2.0.0",
87-
"electron": "41.0.3",
87+
"electron": "41.1.0",
8888
"electron-builder": "26.8.2",
8989
"electron-builder-squirrel-windows": "26.8.2",
9090
"electron-devtools-installer": "^4.0.0",
@@ -107,5 +107,5 @@
107107
"hakDependencies": {
108108
"matrix-seshat": "^4.0.1"
109109
},
110-
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
110+
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
111111
}

apps/web/.stylelintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ module.exports = {
5656
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
5757
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
5858
{ from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" },
59+
{ from: "res/css/views/messages/_ThreadActionBar.pcss", type: "css" },
5960
{ from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" },
6061
{ from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" },
6162
{ from: "res/css/views/settings/tabs/_SettingsTab.pcss", type: "css" },

apps/web/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Context must be the root of the monorepo
33

44
# Builder
5-
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:4bfbd78e049926e4ca595c1798810691ca7bb5aedd829ffd8a78b2ab30689810 AS builder
5+
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:27e462f5db2402700867dfa8ec35e3a68b127fdf61b505db0dd6ab98c38284bb AS builder
66

77
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
88
ARG USE_CUSTOM_SDKS=false
@@ -25,7 +25,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s
2525
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
2626

2727
# App
28-
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:4011c42f28e9b54c86b52211598dbc6bcaa520311ddd55f211587cdd71f88a9c
28+
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:b5831ee7f7aa827cbae87df4a30a642f62c747d8525f5674365389f3adab278d
2929

3030
# Need root user to install packages & manipulate the usr directory
3131
USER root

apps/web/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"react-transition-group": "^4.4.1",
102102
"rfc4648": "^1.4.0",
103103
"sanitize-filename": "^1.6.3",
104-
"sanitize-html": "2.17.1",
104+
"sanitize-html": "2.17.2",
105105
"tar-js": "^0.3.0",
106106
"ua-parser-js": "1.0.40",
107107
"uuid": "^13.0.0",
@@ -203,7 +203,7 @@
203203
"jest-raw-loader": "^1.0.1",
204204
"jsqr": "^1.4.0",
205205
"matrix-web-i18n": "catalog:",
206-
"mini-css-extract-plugin": "2.10.1",
206+
"mini-css-extract-plugin": "2.10.2",
207207
"modernizr": "^3.12.0",
208208
"playwright-core": "catalog:",
209209
"postcss": "8.5.8",
@@ -246,6 +246,6 @@
246246
"engines": {
247247
"node": ">=22.18"
248248
},
249-
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
249+
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
250250
"private": true
251251
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { type Locator, type Page } from "@playwright/test";
9+
10+
import { expect, test } from "../../../element-web-test";
11+
12+
test.describe("Room list sections", () => {
13+
test.use({
14+
displayName: "Alice",
15+
labsFlags: ["feature_new_room_list", "feature_room_list_sections"],
16+
botCreateOpts: {
17+
displayName: "BotBob",
18+
autoAcceptInvites: true,
19+
},
20+
});
21+
22+
/**
23+
* Get the room list
24+
* @param page
25+
*/
26+
function getRoomList(page: Page): Locator {
27+
return page.getByTestId("room-list");
28+
}
29+
30+
/**
31+
* Get the primary filters
32+
* @param page
33+
*/
34+
function getPrimaryFilters(page: Page): Locator {
35+
return page.getByTestId("primary-filters");
36+
}
37+
38+
/**
39+
* Get a section header toggle button by section name
40+
* @param page
41+
* @param sectionName The display name of the section (e.g. "Favourites", "Chats", "Low Priority")
42+
*/
43+
function getSectionHeader(page: Page, sectionName: string): Locator {
44+
return getRoomList(page).getByRole("gridcell", { name: `Toggle ${sectionName} section` });
45+
}
46+
47+
test.beforeEach(async ({ page, app, user }) => {
48+
// The notification toast is displayed above the search section
49+
await app.closeNotificationToast();
50+
51+
// focus the user menu to avoid to have hover decoration
52+
await page.getByRole("button", { name: "User menu" }).focus();
53+
});
54+
55+
test.describe("Section rendering", () => {
56+
test.beforeEach(async ({ app, user }) => {
57+
// Create regular rooms
58+
for (let i = 0; i < 3; i++) {
59+
await app.client.createRoom({ name: `room${i}` });
60+
}
61+
});
62+
63+
test("should render sections with correct rooms in each", { tag: "@screenshot" }, async ({ page, app }) => {
64+
// Create a favourite room
65+
const favouriteId = await app.client.createRoom({ name: "favourite room" });
66+
await app.client.evaluate(async (client, roomId) => {
67+
await client.setRoomTag(roomId, "m.favourite");
68+
}, favouriteId);
69+
70+
// Create a low priority room
71+
const lowPrioId = await app.client.createRoom({ name: "low prio room" });
72+
await app.client.evaluate(async (client, roomId) => {
73+
await client.setRoomTag(roomId, "m.lowpriority");
74+
}, lowPrioId);
75+
76+
const roomList = getRoomList(page);
77+
78+
// All three section headers should be visible
79+
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
80+
await expect(getSectionHeader(page, "Chats")).toBeVisible();
81+
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
82+
83+
// Ensure all rooms are visible
84+
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).toBeVisible();
85+
await expect(roomList.getByRole("row", { name: "Open room low prio room" })).toBeVisible();
86+
await expect(roomList.getByRole("row", { name: "Open room room0" })).toBeVisible();
87+
88+
await expect(roomList).toMatchScreenshot("room-list-sections.png");
89+
});
90+
91+
test("should only show non-empty sections", async ({ page, app }) => {
92+
// No low priority rooms created, only regular and favourite rooms
93+
const favouriteId = await app.client.createRoom({ name: "favourite room" });
94+
await app.client.evaluate(async (client, roomId) => {
95+
await client.setRoomTag(roomId, "m.favourite");
96+
}, favouriteId);
97+
98+
// Chats and Favourites sections should still be visible
99+
await expect(getSectionHeader(page, "Chats")).toBeVisible();
100+
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
101+
// Low Priority sections should not be visible
102+
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
103+
});
104+
105+
test("should render a flat list when there is only rooms in Chats section", async ({ page, app }) => {
106+
// All sections should not be visible
107+
await expect(getSectionHeader(page, "Chats")).not.toBeVisible();
108+
await expect(getSectionHeader(page, "Favourites")).not.toBeVisible();
109+
await expect(getSectionHeader(page, "Low Priority")).not.toBeVisible();
110+
// It should be a flat list (using listbox a11y role)
111+
await expect(page.getByRole("listbox", { name: "Room list", exact: true })).toBeVisible();
112+
await expect(getRoomList(page).getByRole("option", { name: "Open room room0" })).toBeVisible();
113+
});
114+
});
115+
116+
test.describe("Section collapse and expand", () => {
117+
[
118+
{ section: "Favourites", roomName: "favourite room", tag: "m.favourite" },
119+
{ section: "Low Priority", roomName: "low prio room", tag: "m.lowpriority" },
120+
].forEach(({ section, roomName, tag }) => {
121+
test(`should collapse and expand the ${section} section`, async ({ page, app }) => {
122+
const roomId = await app.client.createRoom({ name: roomName });
123+
if (tag) {
124+
await app.client.evaluate(
125+
async (client, { roomId, tag }) => {
126+
await client.setRoomTag(roomId, tag);
127+
},
128+
{ roomId, tag },
129+
);
130+
}
131+
132+
const roomList = getRoomList(page);
133+
const sectionHeader = getSectionHeader(page, section);
134+
135+
// The room should be visible
136+
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
137+
138+
// Collapse the section
139+
await sectionHeader.click();
140+
141+
// The room should no longer be visible
142+
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).not.toBeVisible();
143+
144+
// The section header should still be visible
145+
await expect(sectionHeader).toBeVisible();
146+
147+
// Expand the section again
148+
await sectionHeader.click();
149+
150+
// The room should be visible again
151+
await expect(roomList.getByRole("row", { name: `Open room ${roomName}` })).toBeVisible();
152+
});
153+
});
154+
155+
test("should render collapsed section", { tag: "@screenshot" }, async ({ page, app }) => {
156+
const favouriteId = await app.client.createRoom({ name: "favourite room" });
157+
await app.client.evaluate(async (client, roomId) => {
158+
await client.setRoomTag(roomId, "m.favourite");
159+
}, favouriteId);
160+
161+
await app.client.createRoom({ name: "regular room" });
162+
163+
const roomList = getRoomList(page);
164+
165+
// Collapse the Favourites section
166+
await getSectionHeader(page, "Favourites").click();
167+
168+
// Verify favourite room is hidden but regular room is still visible
169+
await expect(roomList.getByRole("row", { name: "Open room favourite room" })).not.toBeVisible();
170+
await expect(roomList.getByRole("row", { name: "Open room regular room" })).toBeVisible();
171+
172+
await expect(roomList).toMatchScreenshot("room-list-sections-collapsed.png");
173+
});
174+
});
175+
176+
test.describe("Rooms placement in sections", () => {
177+
test("should move a room between sections when tags change", async ({ page, app }) => {
178+
await app.client.createRoom({ name: "my room" });
179+
180+
const roomList = getRoomList(page);
181+
182+
// Flat list because there is only rooms in the Chats section
183+
let roomItem = roomList.getByRole("option", { name: "Open room my room" });
184+
await expect(roomItem).toBeVisible();
185+
186+
// Favourite the room via context menu
187+
await roomItem.click({ button: "right" });
188+
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
189+
190+
// The Favourites section header should now be visible and the room should be under it
191+
await expect(getSectionHeader(page, "Favourites")).toBeVisible();
192+
roomItem = roomList.getByRole("row", { name: "Open room my room" });
193+
await expect(roomItem).toBeVisible();
194+
195+
// Unfavourite the room
196+
await roomItem.hover();
197+
await roomItem.getByRole("button", { name: "More Options" }).click();
198+
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
199+
200+
// Mark the room as low priority via context menu
201+
roomItem = roomList.getByRole("option", { name: "Open room my room" });
202+
await roomItem.click({ button: "right" });
203+
await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click();
204+
205+
// The Low Priority section header should now be visible and the room should be under it
206+
await expect(getSectionHeader(page, "Low Priority")).toBeVisible();
207+
roomItem = roomList.getByRole("row", { name: "Open room my room" });
208+
await expect(roomItem).toBeVisible();
209+
});
210+
});
211+
212+
test.describe("Sections and filters interaction", () => {
213+
test("should not show Favourite and Low Priority filters when sections are enabled", async ({ page, app }) => {
214+
const primaryFilters = getPrimaryFilters(page);
215+
216+
// Expand the filter list to see all filters
217+
const expandButton = primaryFilters.getByRole("button", { name: "Expand filter list" });
218+
await expandButton.click();
219+
220+
// Favourite and Low Priority filters should NOT be visible since sections handle them
221+
await expect(primaryFilters.getByRole("option", { name: "Favourite" })).not.toBeVisible();
222+
223+
// Other filters should still be present
224+
await expect(primaryFilters.getByRole("option", { name: "People" })).toBeVisible();
225+
await expect(primaryFilters.getByRole("option", { name: "Rooms" })).toBeVisible();
226+
await expect(primaryFilters.getByRole("option", { name: "Unread" })).toBeVisible();
227+
});
228+
229+
test("should maintain sections when a filter is applied", async ({ page, app, bot }) => {
230+
// Create a favourite room with unread messages
231+
const favouriteId = await app.client.createRoom({ name: "fav with unread" });
232+
await app.client.evaluate(async (client, roomId) => {
233+
await client.setRoomTag(roomId, "m.favourite");
234+
}, favouriteId);
235+
await app.client.inviteUser(favouriteId, bot.credentials.userId);
236+
await bot.joinRoom(favouriteId);
237+
await bot.sendMessage(favouriteId, "Hello from favourite!");
238+
239+
// Create a regular room with unread messages
240+
const regularId = await app.client.createRoom({ name: "regular with unread" });
241+
await app.client.inviteUser(regularId, bot.credentials.userId);
242+
await bot.joinRoom(regularId);
243+
await bot.sendMessage(regularId, "Hello from regular!");
244+
245+
// Create a room without unread
246+
await app.client.createRoom({ name: "no unread room" });
247+
248+
const roomList = getRoomList(page);
249+
const primaryFilters = getPrimaryFilters(page);
250+
251+
// Apply the Unread filter
252+
await primaryFilters.getByRole("option", { name: "Unread" }).click();
253+
254+
// Only rooms with unreads should be visible
255+
await expect(roomList.getByRole("row", { name: "fav with unread" })).toBeVisible();
256+
await expect(roomList.getByRole("row", { name: "regular with unread" })).toBeVisible();
257+
await expect(roomList.getByRole("row", { name: "no unread room" })).not.toBeVisible();
258+
});
259+
});
260+
});
7.12 KB
Loading
15 KB
Loading

apps/web/res/css/_components.pcss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,13 @@
232232
@import "./views/messages/_MPollBody.pcss";
233233
@import "./views/messages/_MStickerBody.pcss";
234234
@import "./views/messages/_MTextBody.pcss";
235-
@import "./views/messages/_MVideoBody.pcss";
236235
@import "./views/messages/_MediaBody.pcss";
237236
@import "./views/messages/_MessageActionBar.pcss";
238237
@import "./views/messages/_MjolnirBody.pcss";
239238
@import "./views/messages/_ReactionsRow.pcss";
240239
@import "./views/messages/_RoomAvatarEvent.pcss";
241240
@import "./views/messages/_TextualEvent.pcss";
241+
@import "./views/messages/_ThreadActionBar.pcss";
242242
@import "./views/messages/_UnknownBody.pcss";
243243
@import "./views/messages/_ViewSourceEvent.pcss";
244244
@import "./views/messages/_common_CryptoEvent.pcss";

apps/web/res/css/views/dialogs/_MessageEditHistoryDialog.pcss

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,11 @@ Please see LICENSE files in the repository root for full details.
8282
}
8383
}
8484

85-
.mx_MessageActionBar .mx_AccessibleButton {
86-
display: flex;
87-
align-items: center;
88-
89-
padding-inline-start: $spacing-8;
90-
padding-inline-end: $spacing-8;
85+
.mx_HistoryActionBar {
86+
border-radius: 0 !important;
87+
}
9188

92-
font-size: $font-15px;
89+
.mx_HistoryActionBar [data-presentation="label"] {
90+
line-height: 24px !important;
9391
}
9492
}

0 commit comments

Comments
 (0)