Compare commits

...

411 Commits

Author SHA1 Message Date
Half-Shot
9eb5e8965a lint 2025-06-19 10:16:11 +01:00
Half-Shot
6d2f1c2e9a Issues caught by Sonar 2025-06-19 08:26:45 +01:00
Will Hunt
f44308b9a9 Merge branch 'develop' into hs/add-custom-component-modules 2025-06-17 16:30:31 +01:00
Half-Shot
bba40ca706 adapt custom module sample 2025-06-17 16:16:56 +01:00
Half-Shot
706b33fcf4 update to new module sdk 2025-06-17 16:02:44 +01:00
RiotRobot
a7a8428d1c Reset matrix-js-sdk back to develop branch 2025-06-17 13:19:00 +00:00
RiotRobot
96797c3524 Merge branch 'master' into develop 2025-06-17 13:18:47 +00:00
RiotRobot
01519f7fd5 v1.11.104 2025-06-17 13:15:36 +00:00
RiotRobot
ba3b9840ca Upgrade dependency to matrix-js-sdk@37.9.0 2025-06-17 13:11:17 +00:00
Half-Shot
66e73818a8 Use module matrix event interface instead. 2025-06-17 13:36:08 +01:00
Half-Shot
d97d999ef2 lint 2025-06-17 12:12:58 +01:00
Andy Balaam
9d1455e4dd Prevent skipping forced verification after logging in with OIDC (#30141)
Pass the freshLogin parameter along to doSetLoggedIn when restoring a session,
instead of hard-coding it to always be false.
2025-06-17 10:31:08 +00:00
Half-Shot
294857209d lint 2025-06-17 09:22:13 +01:00
Half-Shot
391bd15258 hide jump to bottom button that was causing flakes 2025-06-16 13:51:09 +01:00
Half-Shot
42edbab715 updates 2025-06-16 13:46:03 +01:00
Half-Shot
5aee224169 fix types 2025-06-16 13:43:27 +01:00
Half-Shot
ff986e4317 update @element-hq/element-web-module-api to 1.1.0 2025-06-16 12:50:53 +01:00
ElementRobot
28a232eea8 [create-pull-request] automated change (#30144)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-16 06:23:43 +00:00
Michael Telatynski
a2bea649f6 Hide add integrations button based on UIComponent.AddIntegrations (#30140)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-06-13 09:29:13 +00:00
Florian Duros
1e3fd9d3aa Update IconButton colors (#30124)
* chore: update `@vector-im/compound-web` to 8.0.0

* refactor(IconButton): use `kind="secondary"` instead of `subtleBackground` props

* test: update snapshots

* fix: force color on room header toggle

* fix: TAC button color

* test(e2e): update release announcement screenshot
2025-06-13 08:28:43 +00:00
Marc
0f0f904cb0 Mvvm split user info, create userinfoadmintools container component (#29808)
* feat: mvvm split user info, create userinfoadmintools container component

* test: mvvm userinfoadmintools and view

* feat: user info admin components more split and comments

* test: mvvm user admin info mute view models more coverage

* chore: rename user-info folder to user_info
2025-06-13 07:08:29 +00:00
ElementRobot
d89afe83a8 [create-pull-request] automated change (#30139)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-13 06:22:53 +00:00
David Baker
6f0d288c1d Use nav for new room list and label sections (#30134)
* Use nav for new room list and label sections

The old room list had a nav element but it was missed in the new one,
so add it and albel the sections. Also remove the test ID and use
this instead.

* Update snapshots

* Use the function we define above
2025-06-12 15:42:30 +00:00
Half-Shot
c6d4f38a04 update docstring 2025-06-12 16:39:11 +01:00
Half-Shot
62f62601ef Merge remote-tracking branch 'origin/develop' into hs/add-custom-component-modules 2025-06-12 16:33:17 +01:00
Half-Shot
e60a68ea1a lint a bit more 2025-06-12 16:27:58 +01:00
Half-Shot
ec13bdc910 Update tests to be complete 2025-06-12 16:25:23 +01:00
R Midhun Suresh
c0d91a46c7 Emit event after rebuilding home space (#30132) 2025-06-12 13:30:50 +00:00
Andy Balaam
55e874fb50 Fix #30118 - TypeError in manual verify - by using MatrixClientPeg (#30131)
We can't use MatrixClientContext inside a dialog at the moment.
2025-06-12 10:53:52 +00:00
Michael Telatynski
a622772a08 Handle m.room.pinned_events being invalid (#30129)
* Handle m.room.pinned_events being invalid

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update src/hooks/usePinnedEvents.ts

Co-authored-by: Florian Duros <florianduros@element.io>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Florian Duros <florianduros@element.io>
2025-06-12 08:12:45 +00:00
Florian Duros
389a0e689e New room list: filter list can be collapsed (#29992)
* feat: add new hook to check if a node is visible

* feat: filters in new room list can be collapsed

* feat: add animation to filter list

* feat: hide chevron when list fit on one line

* fix: use correct label for expand button

* test: update room list panel snapshots

* test: add tests for useIsNodeVisible

* chore: update i18n

* test: add tests for primary filters

* test(e2e): update existing screenshots

* test(e2e): update primary filter tests

* chore: typo in css file

* refactor: replace ternary by if in filter condition

* feat: compute filter height instead of hardcoded value

* fix: floor floating computation on filter

* refactor: move hooks to dedicated files

* test: update tests

* feat: rework collapse feature

* test: remove room list panel snapshot

* test: update room list primary filter tests

* test(e2e): update screenshots

* test(e2e): update screenshots

* test(e2e): fix favourite filter in scroll behaviour test

* fix: accessibility order when tabbing
2025-06-11 13:49:20 +00:00
R Midhun Suresh
451a99d49e Show EmptyRoomListView when low priority filter matches zero rooms (#30122)
* Implement empty section for low prio filter

* Write test
2025-06-11 11:36:29 +00:00
ElementRobot
ed9b480338 [create-pull-request] automated change (#30120)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-11 07:01:18 +00:00
renovate[bot]
82200b57cf Update typescript-eslint monorepo to v8.33.1 (#30113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 23:08:44 +00:00
renovate[bot]
fadaaccebc Update dependency testcontainers to v11.0.2 (#30112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 23:08:00 +00:00
renovate[bot]
e293d2b58f Update dependency @element-hq/element-web-playwright-common to v1.3.0 (#30115)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 17:32:52 +00:00
renovate[bot]
f43e953794 Update all non-major dependencies (#30106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 17:25:16 +00:00
renovate[bot]
276fa5eaa8 Update dependency @types/react-dom to v19.1.6 (#30110)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 17:11:10 +00:00
Andy Balaam
bd4509576c Warn user before doing a manual verification via slash command (#30102) 2025-06-10 16:09:24 +00:00
renovate[bot]
10b9b2cb8b Update dependency @sentry/browser to v9.27.0 (#30116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:54:32 +00:00
renovate[bot]
d770826c2d Update dependency caniuse-lite to v1.0.30001721 (#30111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:49:07 +00:00
renovate[bot]
c995496a93 Update fontsource monorepo to v5.2.6 (#30114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:40:29 +00:00
renovate[bot]
902517a02d Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to 66e34aa (#30105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:34:39 +00:00
renovate[bot]
e28b197868 Update babel monorepo (#30107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:33:57 +00:00
renovate[bot]
2350c065a4 Update definitelyTyped (#30108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:33:36 +00:00
renovate[bot]
b218b103b3 Update dependency @stylistic/eslint-plugin to v4.4.1 (#30109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:33:24 +00:00
RiotRobot
67bd11c904 v1.11.104-rc.0 2025-06-10 13:23:03 +00:00
RiotRobot
05ffa2e5ba Upgrade dependency to matrix-js-sdk@37.9.0-rc.0 2025-06-10 13:17:49 +00:00
Michael Telatynski
c51823db5e Revert "Update the mobile_guide page to the new design. (#30006)" (#30104) 2025-06-10 14:01:27 +01:00
Andy Balaam
5b51fe48af Add a scam warning to the manual verification dialog (#30101)
* Add a scam warning to the manual verification dialog

* fixup! Add a scam warning to the manual verification dialog
2025-06-10 12:29:48 +00:00
Doug
0e748710cd Update the mobile_guide page to the new design. (#30006)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-06-10 13:29:04 +01:00
RiotRobot
3f6d900627 Merge branch 'master' into develop 2025-06-10 12:10:38 +00:00
RiotRobot
eb7359403f v1.11.103 2025-06-10 12:07:31 +00:00
RiotRobot
7fe53eac16 Upgrade dependency to matrix-js-sdk@37.8.0 2025-06-10 12:02:15 +00:00
gnieto
16773f5e4a Do not send empty auth when setting up cross-signing keys (#29914)
* Do not send empty auth when setting up cross-signing keys

My understanding from the spec is that no auth parameter should be sent
when starting a UIA flow. [This section](https://spec.matrix.org/v1.14/client-server-api/#user-interactive-api-in-the-rest-api)
says that "A client should first make a request with no auth parameter"
and this is not what element-web is doing (since it is sending an auth parameter with
an empty dictionary).

In the upload cross-signing keys endpoint
[documentation](https://spec.matrix.org/v1.14/client-server-api/#post_matrixclientv3keysdevice_signingupload_request_authentication-data)
it says that type may be omitted if session is set, but in this specific
case neither of the fields is set.

* fixup! Do not send empty auth when setting up cross-signing keys
2025-06-10 11:37:04 +00:00
Andy Balaam
e7d940160a Provide a devtool for manually verifying other devices (#30094)
Also allows doing the same thing via a slash command.
2025-06-10 10:55:05 +00:00
Will Hunt
a333856c50 Implement MSC4155: Invite filtering (#29603)
* Add settings for MSC4155

* copyright

* Tweak to not use js-sdk

* Update for latest MSC

* Various tidyups

* Move tab

* i18n

* update .snap

* mvvm

* lint

* add header

* Remove capability check

* fix

* Rewrite to use Settings

* lint

* lint

* fix test

* Tweaks

* lint

* revert copyright

* update screenshot

* cleanup
2025-06-10 10:47:33 +00:00
Half-Shot
9136d841ee Add hinting 2025-06-10 11:28:15 +01:00
Jean-Baptiste Trystram
d638691fbd Settings: flip local video feed by default (#29501)
Most (if not all) video chat apps show my local video mirrored. Element
not doing it by default is makes it confusing at first.
Even my phone camera app does it.

Not only matching other video chat tools out there, 99% of times you see
yourself in your day to day life is through a mirror, which flips the
image. So matching that makes the most sense, to preserve continuity.

This setting was made optional in https://github.com/element-hq/element-web/pull/5437
but not defaulted to true.

Fixes https://github.com/element-hq/element-web/issues/10651

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-06-10 08:27:35 +00:00
R Midhun Suresh
6103f7e3b4 Add low priority avatar decoration to room tile (#30065)
* Add avatar decoration for low priority rooms

* Write tests

* Remove unnecesasry step in test

* Make the vm expose which decoration to render

* Fix jest test

* Fix broken e2e test
2025-06-10 08:15:38 +00:00
Michael Telatynski
2b24232f14 Add ability to prevent window content being captured by other apps (Desktop) (#30098)
* Add ability to prevent window content being captured by other apps (Desktop)

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Increase coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Increase coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-06-10 07:41:23 +00:00
Richard van der Hoff
3e8599bba0 AccessSecretStorageDialog: various fixes (#30093)
* AccessSecretStorageDialog: clear notice when input is empty

* AccessSecretStorageDialog: Simplify logic for calculating feedback

No functional changes, just simplification

* AccessSecretStorageDialog: use the right icon

Should be a ! in a circle, not an X. Also requires use of `Flex` to fix the
vertical alignment.

* AccessSecretStorageDialog: fix resizing when key is correct

* AccessSecretStorageDialog: remove confirmation on dialog close

Per discussion on https://github.com/element-hq/element-web/issues/30024, we
don't want this any more.
2025-06-09 10:27:14 +00:00
ElementRobot
073606207e [create-pull-request] automated change (#30097)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-09 06:24:01 +00:00
Richard van der Hoff
7eb16b3361 AccessSecretStorageDialog: fix inability to enter recovery key (#30090)
* BaseDialog: fix documentation, and make `onFinished` optional

Since `onFinished` isn't used if `hasCancel` is false, it's a bit silly to make
it mandatory.

* AccessSecretStorageDialog: fix inability to enter recovery key

Wrap AccessSecretStorageDialog in a `BaseDialog`. The main thing this achieves
is a `FocusLock`.

* playwright: factor out helper for verification

We have two copies of the same code, and we're about to add a third...

* playwright: test for verifying from Settings

* Add a unit test for BaseDialog
2025-06-06 11:21:29 +00:00
ElementRobot
e5d167dcf3 [create-pull-request] automated change (#30091)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-06 06:23:06 +00:00
Florian Duros
140afea791 New room list: move message preview in user settings (#30023)
* feat: move message preview settings to user settings

* test: update tests

* test(e2e): update preference screenshots

* test(e2e): update room list tests

* fix: display message preview settings only for new room list

* test(e2e): display all preference settings in screenshot

* test: update snapshot
2025-06-05 14:14:09 +00:00
Michael Telatynski
ad71e7bdc4 Fix failure to upload thumbnail causing image to send as file (#30086)
* Fix failure to upload thumbnail causing image to send as file

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-06-05 09:10:23 +00:00
Florian Duros
311c038fe1 New room list: change room options icon (#30029)
* feat: change sort icon

* test(e2e): update screenshots

* test: update snapshots
2025-06-05 09:08:15 +00:00
Half-Shot
f740dc3829 Allow passing in props to original component 2025-06-05 09:57:50 +01:00
R Midhun Suresh
2b1a4e007c Low priority menu item should be a toggle (#30071)
* Use toggle on low priority menu item

* Fix broken tests
2025-06-04 17:51:58 +00:00
R Midhun Suresh
231ab20dcf RoomListStore: Sort low priority rooms to the bottom of the list (#30070)
* Sort low priority rooms to the bottom of the list

* Write test
2025-06-04 17:51:49 +00:00
R Midhun Suresh
df4cf64ebe Update tests (#30066)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-06-04 17:50:42 +00:00
R Midhun Suresh
b9f319a9f5 Add sanity checks to prevent users from ignoring themselves (#30079)
* Fix missing state

* Throw error if membership event changes

* Write test

* Fix broken tests

* Cache inviter when room is loaded

* Translate error message for dialog
2025-06-04 17:39:19 +00:00
Matt Lewis
9c0604f849 Fix issue with duplicate images (#30073)
* ensure export file paths are unique

* add unit test for filepath uniqueness. fix createMessagesRequest mock.

* add return types
2025-06-04 12:54:47 +00:00
Richard van der Hoff
f97df3eb3b Handle errors returned from Seshat (#30083)
* Handle errors returned from Seshat

Fix a bug which caused errors from Seshat to be swallowed, giving only "Unknown
error".

* fix type casting
2025-06-04 12:42:55 +00:00
ElementRobot
114fd6d123 [create-pull-request] automated change (#30078)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-04 06:26:55 +00:00
RiotRobot
7bb49c567d Reset matrix-js-sdk back to develop branch 2025-06-03 14:59:41 +00:00
RiotRobot
b2258a93b4 Merge branch 'master' into develop 2025-06-03 14:59:28 +00:00
RiotRobot
dba4952721 v1.11.102 2025-06-03 14:56:05 +00:00
RiotRobot
5cf543a9a7 Upgrade dependency to matrix-js-sdk@37.7.0 2025-06-03 14:52:36 +00:00
Half-Shot
ce428b5e2d lint 2025-06-03 14:00:46 +01:00
Half-Shot
757e4e1395 Update tests 2025-06-03 13:57:57 +01:00
Half-Shot
1b2d9b392c Rewrite for new API 2025-06-03 13:42:17 +01:00
R Midhun Suresh
b9b31fa0fb Match string exactly (#30067)
The devices section in OIDC frontend has changed to include a heading
with the device name. The device name and the client name both contain
"Element", so playwright fails.
2025-06-02 10:50:58 +00:00
R Midhun Suresh
7d69ce39d9 Add low priority filter pill to the room list UI (#30060)
* Add low priority filter pill to the UI

* Fix tests
2025-06-02 08:20:15 +00:00
ElementRobot
c6445bbc2c [create-pull-request] automated change (#30064)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-06-02 06:27:41 +00:00
Half-Shot
1c5bc4a7be Merge remote-tracking branch 'origin/develop' into hs/add-custom-component-modules 2025-05-30 10:38:10 +01:00
ElementRobot
6bc117993d [create-pull-request] automated change (#30059)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-30 08:02:47 +00:00
ElementRobot
713cd472c6 Close call options popup menu when option has been selected (#30052) (#30054)
To avoid locking the user into the popup due to focus lock clash

Fixes #29985


(cherry picked from commit 7eb133286b)

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-29 14:29:48 +00:00
Michael Telatynski
7eb133286b Close call options popup menu when option has been selected (#30052)
To avoid locking the user into the popup due to focus lock clash

Fixes #29985

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-29 13:26:54 +00:00
renovate[bot]
7eb1433f32 Update all non-major dependencies (#30047)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-29 09:16:46 +00:00
renovate[bot]
ce75b9da09 Update browserslist (#30048)
* Update browserslist

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-29 08:55:11 +00:00
renovate[bot]
2e8791c651 Update dependency @sentry/browser to v9.23.0 (#30049)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 08:24:20 +00:00
renovate[bot]
52794501f4 Update dependency @types/react to v19.1.6 (#30046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 08:05:53 +00:00
renovate[bot]
fd9b981852 Update dependency @types/node to v18.19.105 (#30045)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 08:05:07 +00:00
Florian Duros
f85d0c95b8 New room list: remove color gradient in space panel (#29721)
* feat: remove blur effect in space

* feat: remove user menu border and align

* test(e2e): update snapshots
2025-05-29 07:39:02 +00:00
renovate[bot]
ff26b9e89d Update dependency testcontainers to v11 (#30044)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 21:35:14 +00:00
Matthew Hodgson
013f5a0c91 /share?msg=foo endpoint using forward message dialog (#29874)
* basic implementation of an /share?msg=foo endpoint

* SharePayload

* add sharing html & md while we're at it

* remove whitespace from imports to appease linter

* lint

* Add unit test

* More tests

* Test for showScreen

* Use one of the typed strings

* Test nasty tags stripped out

* Add playwright test

* Fix flake

by not relying on the name being synced as soon as we load

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-05-28 18:48:32 +00:00
renovate[bot]
e92bf78289 Update dependency @element-hq/element-web-playwright-common to v1.1.7 (#30034)
* Update dependency @element-hq/element-web-playwright-common to v1.1.7

* Update registerAccountMas

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update registerAccountMas

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-28 18:28:10 +00:00
renovate[bot]
f119b93e79 Update dependency caniuse-lite to v1.0.30001718 (#30036)
* Update dependency caniuse-lite to v1.0.30001718

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-28 16:49:10 +00:00
renovate[bot]
5aecdebbc7 Update all non-major dependencies (#30037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 16:31:45 +00:00
renovate[bot]
7d8f0c7832 Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to 2acffd8 (#30030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 16:23:04 +00:00
renovate[bot]
fcfcd29ec7 Update typescript-eslint monorepo to v8.33.0 (#30043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 16:01:24 +00:00
renovate[bot]
79e71fe3a0 Update babel monorepo to v7.27.3 (#30039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 15:57:10 +00:00
renovate[bot]
ae9e85e360 Update sigstore/cosign-installer digest to 3454372 (#30032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 15:35:42 +00:00
renovate[bot]
e078dc114b Update Node.js to f16d8e8 (#30031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 15:30:22 +00:00
renovate[bot]
1167776745 Update dependency @stylistic/eslint-plugin to v4.4.0 (#30041)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 14:50:16 +00:00
renovate[bot]
fe760421cd Update dependency @sentry/browser to v9.22.0 (#30040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 14:49:09 +00:00
renovate[bot]
331bbc19a6 Update dependency @types/react to v19.1.5 (#30035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 14:47:41 +00:00
renovate[bot]
7526f20ea3 Update definitelyTyped (#30033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 14:47:30 +00:00
RiotRobot
ee87b0e2d2 v1.11.102-rc.0 2025-05-28 13:10:03 +00:00
RiotRobot
3fd52c9e07 Upgrade dependency to matrix-js-sdk@37.7.0-rc.0 2025-05-28 13:03:53 +00:00
ElementRobot
e9c91ba28a [create-pull-request] automated change (#30019)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-26 06:23:56 +00:00
Hubert Chathi
45182172b8 EW: Modernize the recovery key input modal (#29819)
* initial application of recovery key input redesign

* update styling to agree more with design, and fix jest tests

* look for the right element for entering the key

* fix more playwright tests

* use return value of validation function instead of state
2025-05-23 21:06:00 +00:00
ElementRobot
8513eaa898 [create-pull-request] automated change (#30008)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-23 06:23:37 +00:00
Robin
87447c7f91 Clean up some unused code related to calls (#29997)
* Remove the unused 'preload' option

* Remove unused layout tracking code
2025-05-22 14:08:42 +00:00
Michael Telatynski
f5125ac2b8 Support build-time specified protocol scheme for oidc callback (#29814)
* Support build-time specified protocol scheme for oidc callback

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-22 10:24:53 +00:00
Michael Telatynski
bd142412e5 Downstream test element-modules in merge queue (#29899)
* Downstream test element-modules in merge queue

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Typo

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update cache key to be arch-aware

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-22 09:32:23 +00:00
ElementRobot
581920e82b [create-pull-request] automated change (#29989)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-21 06:24:35 +00:00
Florian Duros
5d2d4947f4 New room list: move secondary filters into primary filters (#29972)
* feat: move secondary filters into primary filters in vm

* test: update room list view model tests

* feat: remove secondary filter menu

* test: update and remove secondary filter component tests

* feat: update i18n

* test: update remaining tests

* test(e2e): update screenshots and tests

* feat: add new cases for empty room list

* test(e2e): add more tests for empty room list for new primary filters
2025-05-20 14:44:29 +00:00
RiotRobot
69fe2ad06c Reset matrix-js-sdk back to develop branch 2025-05-20 13:37:43 +00:00
RiotRobot
ed0b50283e Merge branch 'master' into develop 2025-05-20 13:37:27 +00:00
RiotRobot
e7e425f3db v1.11.101 2025-05-20 13:34:02 +00:00
RiotRobot
f81a127d46 Upgrade dependency to matrix-js-sdk@37.6.0 2025-05-20 13:29:08 +00:00
Half-Shot
1ed3f205f3 tidy 2025-05-20 14:27:27 +01:00
Andy Balaam
b539eda4fe Prompt the user when key storage is unexpectedly off (#29912)
* Assert that we set backup_disabled when turning off key storage

* Prompt the user when key storage is unexpectedly off

* Playwright tests for the Turn on key storage toast
2025-05-20 12:28:22 +00:00
Half-Shot
afab6c29dc Add a test for custom modules. 2025-05-20 13:27:22 +01:00
Half-Shot
4d81b36270 fix types 2025-05-20 12:40:46 +01:00
Half-Shot
3281a4128f Remove context menu, refactor 2025-05-20 12:10:45 +01:00
Florian Duros
22c7bf346c New room list: move sort menu in room list header (#29983)
* feat: move sort and preview into room list header vm

* feat: move sort menu into room list header

* test: update tests

* test:update snapshots

* chore: remove secondary filter tests

* test(e2e): update screenshots
2025-05-19 13:25:12 +00:00
Half-Shot
213a191b8c Add new custom component api. 2025-05-19 09:26:22 +01:00
ElementRobot
e1104891cb [create-pull-request] automated change (#29980)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-19 06:23:30 +00:00
R Midhun Suresh
78ec757f11 RoomListStoreV3: Only add new rooms that pass VisibilityProvider check (#29974)
* Add new rooms only after checking VisibilityProvider

Otherwise we might end up adding space rooms and other rooms that must
be hidden.

* Write test
2025-05-16 13:52:39 +00:00
R Midhun Suresh
45f41a33e7 Add packageManager field to package.json (#29966)
* Add `packageManager` field to package.json

This is helpful when using corepack to set and use particular yarn
versions.

* Include hash in the field
2025-05-16 12:43:32 +00:00
Florian Duros
b56b0f2bd0 New room list: rework spacing of room list item (#29965)
* feat: rework spacing of room list item

* test: update snapshot

* test(e2e): regenerate room list panel screenshots

* test(e2e): regenerate room list screenshots

* test(e2e): update filter screenshot
2025-05-16 12:29:52 +00:00
ElementRobot
4dcde7ec7a [create-pull-request] automated change (#29970)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-16 06:23:44 +00:00
Marc
b07225eb60 Mvvm RoomSummaryCard (#29674)
* feat: create roomsummarycard viewmodel

* feat: use roomsummurycard vm in component

* test: jest unit RoomSummaryCard and RoomSummaryCardViewModel

* chore: rename to roomsummarycardview

* feat: reput room topic without vm

* test: roomSummaryCard and roomSummaryCardVM tests

* chore: add comments on roomsummarycardVM

* fix: merge conflict with roomsummarytopic, and move to vm right_panel

* fix(roomsummarycard): remove usetransition for search update

* fix: merged file that should be deleted

* fix: roomsummurycard not well merge with roomtopic

* test: update snapshots
2025-05-15 14:17:21 +00:00
David Baker
9642af9930 Re-order primary filters (#29957)
* Re-order primary filters

to match EX

* Update tests

* Update screenshots

* Try to make screenshot deterministic

* Just use the screenshot the CI spits out

* Try again

* Another screenshot
2025-05-15 13:15:24 +00:00
Doug
c309cc8bfa Remove the Apple site association for Element X PR (#29968)
This is an old app variant that we've deleted in favour of making custom TestFlight builds.
2025-05-15 11:51:44 +00:00
R Midhun Suresh
fb65bbf521 Fix broken link in README (#29967) 2025-05-15 10:43:49 +00:00
R Midhun Suresh
57d3b2d93c RLS: Remove forgotten room from skiplist (#29933)
* Dispatch an action when room is forgotten

* Dispatch an action when room is forgotten

* Remove room on action

* Add test

* Write test for matrixchat

* Add payload info to comment
2025-05-15 10:04:05 +00:00
Michael Telatynski
aef3c8e986 Fix leaky CSS adding ! to all H1 elements (#29964)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-15 08:30:54 +00:00
David Baker
1b48269db5 Add room list sorting (#29951)
* Add room list sorting

* Prettier

* Unit test

* Playwright test

* Lint

* Use released compound

* No tooltip wrapper needed
2025-05-15 08:27:33 +00:00
David Langley
76d7f6ab43 Fix extensions panel style (#29273)
* Stop empty state scrolling

* Fix separator inset, separator vertical spacing and margin above the add extensions button

* Add screenshot for add extensions button spacing

* Add variable for add extensions overlap

* use data-testid

* Fix snapshots
2025-05-14 17:22:32 +00:00
David Langley
69c1a8cd1c Don't use the minimised width(68px) on the new room list (#29778)
* Don't toggle to the minimised left bar size.

* Don't re-style old room list when at the minimum size

* Only apply larger minimised with on new room list

* Don't tell child views we are minimised for the new room list

* move comment to the correct place

* address PR comments

* Don't wrap search text and add truncation down to a minimum of 50px

* Put min-width on the button so that we don't have to hardcode the 50px

* Keep the flex display, shrink and truncate just the search text and keep the shortcut

* Update snapshots

* Add comment for magic value

* Make inner search text a span

* Add e2e test for smallscreen to test responsiveness

* Update snapshots

* Forcing an empty commit to fix PR

* Change minWidth to 224
2025-05-14 17:21:27 +00:00
renovate[bot]
231515bc6c Update all non-major dependencies (#29961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 14:52:31 +00:00
Robin
ccd77be74a Fix state events being hidden from widgets in read_events actions (#29954)
This widget driver method was mistakenly filtering all state events out of the responses to read_events fromWidget actions. This was a hold-over from back when read_events had two different behaviors depending on whether you specified a state_key (i.e. before the introduction of the update_state action).
2025-05-14 14:05:19 +00:00
David Baker
be5dd058b3 Remove old filter test (#29963)
This wasn't testing what it thought it was testing because the primary
filters aren't present at all after selecting a secondary filter that's
not compatible with them, so it was just asserting that some other filter
(which had the same index as the old one) was disabled. It breaks if
the primary filters get reordered.
2025-05-14 13:12:34 +00:00
Michael Telatynski
2326a7c8dc Update @types/react patch (#29960)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-14 13:08:57 +00:00
Michael Telatynski
c52ec3efd1 Guard against counterpart.translate returning non-string values (#29959)
* Guard against counterpart.translate returning non-string values

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Outdent and fixup comment, remove stale development-only behaviour

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-14 12:48:42 +00:00
Michael Telatynski
785a12a029 Element Module API v1.0 support (#29934) 2025-05-14 09:21:24 +01:00
ElementRobot
c9548ec1d0 [create-pull-request] automated change (#29956)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-14 06:21:50 +00:00
renovate[bot]
fadd54f0b3 Update definitelyTyped (#29939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 16:54:55 +00:00
renovate[bot]
6ac66da5eb Update all non-major dependencies (#29945)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 13:26:41 +00:00
dependabot[bot]
8be05f0ad9 Bump base-x from 5.0.0 to 5.0.1 (#29948)
Bumps [base-x](https://github.com/cryptocoinjs/base-x) from 5.0.0 to 5.0.1.
- [Commits](https://github.com/cryptocoinjs/base-x/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: base-x
  dependency-version: 5.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 13:02:23 +00:00
renovate[bot]
5faae73055 Update browserslist (#29938)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 12:48:16 +00:00
renovate[bot]
32dfabbcb6 Update react monorepo (#29947)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 12:33:34 +00:00
renovate[bot]
972366b5ae Pin dependencies (#29946)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 12:31:07 +00:00
renovate[bot]
597a0d25ac Update dependency @sentry/browser to v9.17.0 (#29941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:50:13 +00:00
renovate[bot]
fe8d5aee63 Update typescript-eslint monorepo to v8.32.0 (#29944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:49:57 +00:00
renovate[bot]
900fa53a33 Update babel monorepo (#29940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:45:39 +00:00
renovate[bot]
706d929f3a Update dependency testcontainers to v10.25.0 (#29937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:30:57 +00:00
renovate[bot]
2b5f687c40 Update react monorepo to v19.1.3 (#29936)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:30:45 +00:00
renovate[bot]
501c8194e5 Update dependency lint-staged to v16 (#29935)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 11:12:49 +00:00
RiotRobot
138c40b0c1 v1.11.101-rc.0 2025-05-13 10:58:21 +00:00
RiotRobot
85647efadb Upgrade dependency to matrix-js-sdk@37.6.0-rc.0 2025-05-13 10:55:44 +00:00
renovate[bot]
16d57074df Update linkify to v4.3.1 (#29903)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 10:52:09 +00:00
Michael Telatynski
c84cf3c36c Remove legacy Safari/Firefox/IE compatibility aids (#29010)
* Remove legacy Safari prefix compatibility for AudioContext

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove more legacy webkit/ms/moz support

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage, cull dead code

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-13 10:51:05 +00:00
Michael Telatynski
e235100dd0 Fix flaky jest tests (#29927)
* Debug flaky jest test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Discard changes to jest.config.ts

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-13 09:27:08 +00:00
Florian Duros
10757b4357 fix: change keyboard short to go home on macos (#29929) 2025-05-13 08:03:28 +00:00
Florian Duros
64047b0702 New room list: fix outdated message preview when space or filter change (#29925)
* fix(new room list): fix outdated message preview when space change

* test(new room list): verify that message preview is check when room change
2025-05-13 07:57:02 +00:00
David Langley
0d5a8aafbd remove verticals team and add crypto team (#29926) 2025-05-12 17:36:17 +00:00
Will Hunt
fb5c4ffc8b Stop migrating to MSC4278 if the config exists. (#29924)
* Stop migrationg to MSC4278 if the config exists.

* Run migration after we have synced the client.

* Setup the SettingsController with a client.

* Add tests to check migration behaviour.

* update copyright

* Wait for sync properly

* Catch failure

* Docs

* licence

* Inline async code

* Fix migrateURLPreviewsE2EE too

* drop an import

* go away
2025-05-12 12:16:47 +00:00
Michael Telatynski
308f892cef Ensure consistent download file name on download from ImageView (#29913)
* Ensure consistent download file name on download from ImageView

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Ensure consistent download file name on download from ImageView

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-12 08:24:48 +00:00
ElementRobot
54a00baff8 [create-pull-request] automated change (#29922)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-12 07:07:00 +00:00
ElementRobot
08acbf9b14 [create-pull-request] automated change (#29853)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-05-09 07:49:23 +00:00
Michael Telatynski
a3f5d207de Switch from defer to Promise.withResolvers (#29078)
* Switch from defer to PromiseWithResolvers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add modernizr check

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-08 10:03:43 +00:00
Michael Telatynski
0f783ede5e Add error toast when service worker registration fails (#29895)
* Add error toast when service worker registration fails

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-08 08:36:35 +00:00
Michael Telatynski
e427b71040 Remove release announcement on thread activity centre (#29892)
* Remove release announcement on thread activity centre

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-07 13:26:32 +00:00
renovate[bot]
d553be6316 Update dependency @element-hq/element-web-playwright-common to v1.1.6 (#29896)
* Update dependency @element-hq/element-web-playwright-common to v1.1.6

* Update oidc-native.spec.ts

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-05-07 12:18:48 +00:00
Andy Balaam
6063209fff Cache the key backup status whether enabled or not (#29886) 2025-05-07 11:24:43 +00:00
Andy Balaam
36d25da288 Fix incorrect test of SetupEncryptionToast (#29888) 2025-05-07 11:03:53 +00:00
Florian Duros
74fbd892a1 New room list: add keyboard navigation support (#29805)
* feat: support up/down arrow navigation in the new room list

* feat: support tabbing in the new room list

* test: update snapshots

* test(e2e): fix room list test

* test(new room list): add landmark navigation test

* test(e2e): update screenshot test

* test: add test to `RoomListItemView`

* test(e2e): add keyboard navigation tests

* refactor: rename `setIsHover` on `setIsHoverWithDelay`
2025-05-06 16:09:23 +00:00
R Midhun Suresh
6ba21dafa7 New Room List: Prevent old tombstoned rooms from appearing in the list (#29881)
* Write failing playwright test

Basically when someone changes their name, any old tombstoned rooms that
were previously hidden would suddenly show up in the list.

* Split addRoom into two methods

- `reInsertRoom` that re-inserts a room that is already known by the skiplist.
- `addNewRoom` to add new rooms

The idea is that sometimes you only want to re-insert to noop, eg: when
you get an event in an old room that was upgraded.

* Use new methods in the RLS

Only use `addNewRoom` when absolutely necessary. Most events should
instead use `reInsertRoom` which will noop when the room isn't already
known by the skiplist.

* Fix broken tests

* Add new test

* Fix playwright test
2025-05-06 15:57:13 +00:00
Florian Duros
8ac2f60720 Remove lag in search field (#29885)
* fix(search): remove search input lag

* test(search): add search field update test
2025-05-06 15:24:06 +00:00
Andy Balaam
64f0dfe0bc Tidy SetupEncryptionToast tests (#29887)
* Remove extraneous 'await' from SetupEncryptionToast

* Group SetupEncryptionToast tests
2025-05-06 14:39:11 +00:00
RiotRobot
a728385385 Reset matrix-js-sdk back to develop branch 2025-05-06 14:07:09 +00:00
RiotRobot
d9926c8784 Merge branch 'master' into develop 2025-05-06 14:06:57 +00:00
RiotRobot
186f7e71be v1.11.100 2025-05-06 14:03:49 +00:00
RiotRobot
9eb90a8204 Upgrade dependency to matrix-js-sdk@37.5.0 2025-05-06 13:50:34 +00:00
David Langley
9ec54c534d Respect UIFeature.Voip (#29873)
* respect UIFeature.Voip

* Add unit tests

* reset sdk and mocks

* Update RoomHeader-test.tsx.snap

* use useSettingValue

* lint
2025-05-02 15:05:30 +00:00
Timo
671e55c5a2 Use the JoinRuleSettings component for the guest link access prompt. (#28614)
* Use the JoinRuleSettings component for the guest link access prompt.

Co-authored-by: fkwp <fkwp@users.noreply.github.com>

* increase timeout

* fix tests

---------

Co-authored-by: fkwp <fkwp@users.noreply.github.com>
2025-05-02 13:18:50 +00:00
David Langley
a430501271 Add loading state to the new room list view (#29725)
* add loading state to view model and spinner to room list vieqw

* Update snapshots and add loading test

* avoid nested ternary operator

* Add room list skeleton loading state

* Fix loading logic

- Create RoomListStoreV3Event as to not conflict with loading event definition in Create RoomListStoreEvent.
- Add a loaded event
- Use it to determine loaded state in useFilteredRooms rather than the update event which gets fired in other cases.

* Fix isLoadingRooms logic

* update snapshots and fix test

* Forcing an empty commit to fix PR

* Fix _components.pcss order

* Fix test that wasn't doing anything

* fix tests
2025-05-02 13:12:00 +00:00
Andy Balaam
72429c1350 Make OIDC identity reset consistent with EX (#29854)
* Allow providing an empty title to InteractiveAuthDialog

* Make OIDC identity reset consistent with EX

* Translation changes for OIDC Identity reset (already added to Localazy)

* Fix auth tests for new wording 'Continue to account'
2025-05-02 09:20:22 +00:00
David Baker
8e63c6618c Support error code for email / phone adding unsupported (#29855)
https://github.com/matrix-org/matrix-spec-proposals/pull/4178
2025-04-30 15:06:23 +00:00
Richard van der Hoff
f25fbdebc7 Modal: remove support for onFinished callback (#29852)
* Fix up type for `finished` result of Modal

The `finished` promise can be called with an empty array, for example if the
dialog is closed by a background click. This was not correctly represented in
the typing. Fix that, and add some documentation while we're at it.

* Type fixes to onFinished callbacks from Modal

These can all be called with zero arguments, despite what the type annotations
may say, so mark them accordingly.

* Remove uses of Modal `onFinished` property

... because it is confusing.

Instead, use the `finished` promise returned by `createDialog`.

* Modal: remove support for now-unused `onFinished` prop

* StopGapWidgetDriver: use `await` instead of promise chaining

* Fix up unit tests
2025-04-30 16:56:21 +01:00
renovate[bot]
ce1055f5fe Update playwright to v1.52.0 (#29835)
* Update playwright to v1.52.0

* Update screenshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Stabilise screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-30 12:41:02 +00:00
Michael Telatynski
4bf28f8159 Allow jumping to message search from spotlight (#29850)
* Allow jumping to message search from spotlight

replaces the message search hint which referenced the old UX

Fixes #29831

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update RoomSummaryCard.tsx

* Update actions.ts

* Delete src/hooks/useTransition.ts

* Update RoomSummaryCard.tsx

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-30 11:23:35 +00:00
Andy Balaam
23597e959b Delegate to new ResetIdentityDialog from SetupEncryptionBody (#29701) 2025-04-30 10:08:38 +00:00
David Baker
4f4f391959 Add secondary filters to the new room list (#29818)
* Secondary filters

* Update snapshots

* Fix imports

* Update screenshots

* Add unit test

* Imports

* Prettier

* Add playwright test
2025-04-30 09:12:56 +00:00
Richard van der Hoff
cd05838bf6 Un-gitignore modules.ts (#29851)
As of #29089, `modules.ts` is no longer auto-generated, so should not be
gitignored. Indeed, having a copy sitting around in your working copy can
produce unexpected results, and removing it from the ignorelist at least gives
maintainers a hint about what might be going wrong.
2025-04-29 23:18:57 +00:00
renovate[bot]
290643934d Update dependency posthog-js to v1.236.7 (#29839)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 14:31:08 +00:00
renovate[bot]
6a18cca76b Update all non-major dependencies (#29840)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 14:30:53 +00:00
renovate[bot]
421a79aaa5 Update dependency @types/react to v19.1.2 (#29837)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:45:05 +00:00
renovate[bot]
269f01fee1 Update typescript-eslint monorepo to v8.31.0 (#29843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:23:09 +00:00
renovate[bot]
8864ba939f Update dependency @sentry/browser to v9.14.0 (#29841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:12:27 +00:00
renovate[bot]
67be444c75 Update dependency stylelint to v16.19.1 (#29842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:03:42 +00:00
renovate[bot]
57d39a8d34 Update dependency caniuse-lite to v1.0.30001715 (#29838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 12:03:20 +00:00
dependabot[bot]
3800584f5a Bump http-proxy-middleware from 2.0.7 to 2.0.9 (#29844)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 11:57:11 +00:00
renovate[bot]
290916a310 Update dependency @types/node to v18.19.87 (#29836)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 11:48:12 +00:00
RiotRobot
9f560f1f89 v1.11.100-rc.0 2025-04-29 11:08:33 +00:00
RiotRobot
8e3fb5288b Upgrade dependency to matrix-js-sdk@37.5.0-rc.0 2025-04-29 10:44:08 +00:00
Marcin Bachry
02dd79f03f Fix battery drain from Web Audio (#29203)
* Fix battery drain from Web Audio

* move suspend away from constructor

* await on resume()

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-29 10:40:18 +00:00
Johannes Marbach
160a7c1ae3 Move rich topics out of labs / stabilise MSC3765 (#29817)
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-04-28 16:05:36 +00:00
Matthew Hodgson
c3c04323e1 spell out that EW does *not* work on mobile. (#29211)
* spell out that EW does *not* work on mobile.

see https://bsky.app/profile/jeroenheijmans.nl/post/3lhiwcrtdt22x

* lint

---------

Co-authored-by: David Langley <davidl@element.io>
Co-authored-by: David Langley <langley.dave@gmail.com>
2025-04-28 15:46:05 +00:00
David Langley
d6a1d9aa3d Fix incorrect display of the user info display name (#29826)
* Fix incorrect display of the user info display name and truncation after two lines

* Update screenshot for single line name
2025-04-28 14:11:12 +00:00
ElementRobot
a8ca4ff90c [create-pull-request] automated change (#29823)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-28 06:27:49 +00:00
R Midhun Suresh
83e6753c4e RoomListStore: Remove invite rooms on decline (#29804)
* Remove room when new membership is leave

It doesn't really matter what the previous membership was.

* Fix test

* Remove on join/invite only

* Exclude kicked rooms from being removed
2025-04-26 12:43:23 +00:00
Will Hunt
adc110a8d9 Fix invite flake (#29812) 2025-04-25 13:09:53 +00:00
David Baker
6329f69557 Fix the buttons not being displayed with long preview text (#29811) 2025-04-25 10:03:34 +00:00
ElementRobot
ce4b9860a8 [create-pull-request] automated change (#29810)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-25 06:21:57 +00:00
David Baker
714f8f40dd Add message preview support to the new room list (#29784)
* Add message preview support to the new room list

 * Support showing message previews in the room list items
 * Add the secondary filters bar with the '...' menu, containing
   just the option for message previews for now
 * Change message preview toggle hook to update when setting is updated

* Use new compund release

* Unused i18n keys

* Unused imports

* Fix test & update snapshot

* Fix more snapshots

* Fix test

Split into two tests that test setting & updating

* Type import

* Snapshots

* Remove unnecessary Flex container

and update screenshots as the room list has got shorter from the added bar

* More snapshots & screenshots

* More snapshots

* Add test and remove active filter that's not done yet

* Update snapshots & screenshots again

* Other screenshot

* Add more tests

* Fix syntax

* Fix tests

* Use setter directly

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix CSS

* Remopve filter button css for now

* Update to remove forwardRef

* Add comment on why lack of TypedEventEmitter

* snapshots again

* Screenshots again

* Use original screenshots, maybe they'll work now

* Add comment

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-24 15:03:39 +00:00
Michael Telatynski
22d5c00174 Replace usage of forwardRef with React 19 ref prop (#29803)
* Replace usage of `forwardRef` with React 19 ref prop

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add lint rule

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-24 12:31:37 +00:00
RiotRobot
5e7b58a722 Merge branch 'master' into develop 2025-04-23 10:33:28 +00:00
RiotRobot
40debba4dd v1.11.99 2025-04-23 10:30:13 +00:00
ElementRobot
ee59849307 [create-pull-request] automated change (#29799)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-23 06:22:39 +00:00
Florian Duros
5933f50930 New room list: fix missing/incorrect notification decoration (#29796)
* fix: recompute notification when room change in room list item vm

* test: add use case when room list change

* test(e2e): add screenshot to unread filter test
2025-04-22 13:21:50 +00:00
RiotRobot
f6a3a429f7 Reset matrix-js-sdk back to develop branch 2025-04-22 13:00:32 +00:00
RiotRobot
5dba03dff2 Merge branch 'master' into develop 2025-04-22 13:00:19 +00:00
RiotRobot
8269770db0 v1.11.98 2025-04-22 12:57:08 +00:00
RiotRobot
d0c69b4e35 Upgrade dependency to matrix-js-sdk@37.4.0 2025-04-22 12:54:18 +00:00
Will Hunt
75d9898dff Global configuration flag for media previews (#29582)
* Modify useMediaVisible to take a room.

* Add initial support for a account data level key.

* Update controls.

* Update settings

* Lint and fixes

* make some tests go happy

* lint

* i18n

* update preferences

* prettier

* Update settings tab.

* update screenshot

* Update docs

* Rewrite controller

* Rewrite tons of tests

* Rewrite RoomAvatar to be a functional component

This is so we can use hooks to determine the setting state.

* lint

* lint

* Tidy up comments

* Apply media visible hook to inline images.

* Move conditionals.

* copyright all the things

* Review changes

* Update html utils to properly discard media.

* Types fix

* Fixing tests that break settings getValue expectations

* Fix logic around media preview calculation

* Fix room header tests

* Fixup tests for timelinePanel

* Clear settings in matrixchat

* Update tests to use SettingsStore where possible.

* fix bug

* revert changes to client.ts

* copyright years

* Add header

* Add a test for MediaPreviewAccountSettingsTab

* Mark initMatrixClient as optional

* Improve on types

* Ensure we do not set the account data twice.

* lint

* Review changes

* Ensure we include the client on rendered messages.

* Fix test

* update labels

* clean designs

* update settings tab

* update snapshot

* copyright

* prevent mutation
2025-04-22 09:37:47 +00:00
Florian Duros
da6ac36f11 New room list: add partial keyboard shortcuts support (#29783)
* feat: add support to `Action.ViewRoomDelta`

* test: add tests for support of `Action.ViewRoomDelta`

* test(e2e): add tests for shortcuts

* doc: improve comments in `useRoomListNavigation`
2025-04-22 08:31:12 +00:00
ElementRobot
c1f145d802 [create-pull-request] automated change (#29788)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-21 06:22:28 +00:00
ElementRobot
81260fef57 [create-pull-request] automated change (#29787)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-18 06:21:39 +00:00
Marc
09ceb3c580 MVVM RoomSummaryCard Topic (#29710)
* feat: create roomSummaryCardTopic view model

* chore: add comments and small update on test mock
2025-04-17 15:56:19 +00:00
R Midhun Suresh
1077729a19 New Room List: Prevent potential scroll jump/flicker when switching spaces (#29781)
* Expose last active room in a given space from space store

* Calculate active index based on active room in new space

* Write test
2025-04-17 12:25:06 +00:00
Marc
4f32727829 feat: warn self change on roles settings (#28926)
* feat: warn self change on roles settings

* test: update RolesRoomSettingsTab to match new modal condition

* test: update e2e RolesRoomSettingsTab to add new modal

* feat: powerlevelselector reput initial value if cancel
2025-04-17 08:58:27 +00:00
Florian Duros
fd455179f7 New room list: avoid extra render for room list item (#29752)
* fix: avoid extra render in the new room list

* fix: listen to room name changes

* fix: trigger render when notification state change

* test: fix room list item tests

* chore: fix typo `RoomNotificationState.isUnsentMessage`

* refactor: move `isNotificationDecorationVisible` into `useRoomListItemViewModel`

* refactor: recalculate notification values on notification state changes

* refactor: rename `isNotificationDecorationVisible` to `showNotificationDecoration`

* test: add test for room list item view

* test: add notification tests in room list item vm

* fix: listen to notification updates in `NotificationDecoration`

* test: update notification decoration tests

* refactor: display notification decoration according to vm

* test: update room list item view tests

* fix: a11y label computation after room name change

* refactor: improve notification handling
2025-04-16 21:40:36 +00:00
renovate[bot]
6767e4d6ad Update dependency posthog-js to v1.235.6 (#28906)
* Update dependency posthog-js to v1.235.6

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-16 09:53:27 +00:00
renovate[bot]
1743257ca0 Update dependency caniuse-lite to v1.0.30001714 (#29776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-16 09:53:16 +00:00
renovate[bot]
d2fdd45c47 Update dependency maplibre-gl to v5.3.1 (#29777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-16 09:53:09 +00:00
renovate[bot]
f250575b08 Update dependency @vector-im/compound-design-tokens to v4.0.2 (#29775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-16 09:53:00 +00:00
renovate[bot]
db9428de87 Update react monorepo (#29765)
* Update react monorepo

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-16 09:34:01 +00:00
Florian Duros
b511bf064d New room list: new visual for invitation (#29773)
* feat: rework invitation styling in room list item

* test: update notification decoration test

* test: add test for vm

* test(e2e): update to new invitation styling
2025-04-16 09:23:23 +00:00
David Langley
427e61309b Update team members in triage-assigned.yml (#29751) 2025-04-16 08:38:00 +00:00
David Langley
aa821a5b6f Remove virtual rooms (#29635)
* Remove virtual rooms from the timelinePanel and RoomView

* Remove VoipUserMapper

* Remove some unneeded imports

* Remove tovirtual slash command test

* Remove getSupportsVirtualRooms and virtualLookup

* lint

* Remove PROTOCOL_SIP_NATIVE

* Remove native/virtual looks fields and fix tests

* Remove unused lookup fields
2025-04-16 09:36:34 +01:00
renovate[bot]
8b06714a02 Update dependency stylelint-config-standard to v38 (#29768)
* Update dependency stylelint-config-standard to v38

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-16 07:33:50 +00:00
renovate[bot]
079e1fcbc8 Update dependency caniuse-lite to v1.0.30001713 (#29756)
* Update dependency caniuse-lite to v1.0.30001713

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-16 07:30:17 +00:00
ElementRobot
07c1b406ac [create-pull-request] automated change (#29772)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-16 06:21:56 +00:00
Will Hunt
af9bde5137 Fix invite test flake (#29753)
* Mask mxid from screenshot

* s/hot/not/

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Hide the mxid entirely

* Add new snapshot

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-15 18:57:07 +00:00
renovate[bot]
ee8c1ffef4 Update all non-major dependencies (#29758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 18:45:41 +00:00
renovate[bot]
fa1043426a Update dependency @stylistic/eslint-plugin to v4 (#29304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 16:57:59 +00:00
Florian Duros
18a7250cf9 New room list: fix incorrect decoration (#29770)
* fix(call): reset call value when the roomId changes

* fix(call): reset presence indicator when the room changes

* refactor: use existing `usePresence`

* test: fix room avatar view test

* test: update snapshots
2025-04-15 16:35:37 +00:00
renovate[bot]
7e5f96c85d Update dependency @sentry/browser to v9.12.0 (#29760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:57:50 +00:00
renovate[bot]
a8e0b54d8a Update dependency typescript to v5.8.3 (#29757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:46:34 +00:00
renovate[bot]
302e3e153e Update dependency @testing-library/react to v16.3.0 (#29761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:40:09 +00:00
renovate[bot]
7642054b74 Update dependency express to v5 (#29767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:30:58 +00:00
renovate[bot]
f6955124ac Update dependency testcontainers to v10.24.2 (#29763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:29:03 +00:00
renovate[bot]
9207f25dc3 Update typescript-eslint monorepo to v8.29.1 (#29766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:28:39 +00:00
renovate[bot]
f476da8bec Update dependency stylelint to v16.18.0 (#29762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:24:23 +00:00
renovate[bot]
34e08af274 Update dependency @matrix-org/spec to v1.14.0 (#29759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:23:00 +00:00
renovate[bot]
6c4bd0c8b1 Update peter-evans/dockerhub-description digest to 432a30c (#29755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:22:38 +00:00
renovate[bot]
88e06cdc55 Update guibranco/github-status-action-v2 digest to 5f2b01c (#29754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:20:17 +00:00
RiotRobot
55c4b2fac0 v1.11.98-rc.0 2025-04-15 13:29:49 +00:00
RiotRobot
84479a86f3 Upgrade dependency to matrix-js-sdk@37.4.0-rc.0 2025-04-15 13:22:58 +00:00
Michael Telatynski
8e3830acee Update check name to match draft action 2025-04-15 14:18:59 +01:00
Will Hunt
6fc3dd4628 Refactor RoomAvatar into a functional component. (#29743)
* Refactor RoomAvatar into a functional component

* Add useRoomAvatar hook

* Remove useRoomAvatar hook and fix RoomAvatarEvents not using thumbnails.

* lint

* Ensure stable version of roomIdName

* Use new hook

* lint

* remove unused param

* Fixup tests

* remove console

* Update test
2025-04-15 09:23:26 +00:00
Michael Telatynski
c313c720de Revert "Update to Twemoji 16 (#29735)" (#29748)
This reverts commit 2e71ec748f.
2025-04-15 08:41:04 +00:00
Will Hunt
23a42e0d54 Refactor several unit tests to use SettingsStore directly. (#29744)
* Refactor notifications-test.ts

* Refactor other tests to stop mocking SettingsStore
2025-04-15 08:01:35 +00:00
R Midhun Suresh
bb23a98bc6 We don't want submit buttons (#29747)
Otherwise this will submit the form.
2025-04-15 07:43:28 +00:00
Andrew Ferrazzutti
d52b0a1467 Remove contribute.json (#29707)
as the contribute.json project is now decommissioned.
2025-04-14 14:47:54 +00:00
Florian Duros
986be9c00d Fix flaky MatrixChat tests (#29739)
* test: fix flaky MatrixChat `should persist login credentials` test

* test: fix flaky MatrixChat `should log and return to welcome page with correct error when login state is not found` test

* test: fix flaky MatrixChat `should store clientId and issuer in session storage` test
2025-04-14 14:22:46 +00:00
Julien CLEMENT
475e449e81 print better errors in the search view instead of a blocking modal (#29724)
* print better errors in the search view instead of a blocking modal

* update tests and i18n

* fix unused variable

* fix unused variable again
2025-04-14 13:36:34 +00:00
Florian Duros
7ce0a76414 New room list: fix public room icon visibility when filter change (#29737)
* fix: recompute public variable when room changes in room list item view model

* test: add test to check that isPublic is computed correctly when the room changes
2025-04-14 11:59:31 +00:00
Michael Telatynski
2e71ec748f Update to Twemoji 16 (#29735)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-14 11:50:35 +00:00
Florian Duros
07d5a72f26 New room list: video room and video call decoration (#29693)
* feat: add video call and EC call to room list item vm

* feat: add video call notification decoration to notification decoration component

* feat: add video call support to room list item view

* feat: add new RoomAvatarView component

* feat: deprecate `DecoratedRoomAvatar`

* feat: use `RoomAvatarView` in room list item

* feat: allow custom class for `RoomAvatar`

* test: update notification decoration

* test: update room list item view

* test: update room list snapshot

* test: add tests for room avatar vm

* test: add tests for room avatar view

* test(e2e): update snapshots

* fix: video room creation rights

* test: e2e add test for public and video room
2025-04-14 09:27:43 +00:00
Michael Telatynski
1430fd5af6 Fix custom theme support for short hex & rgba hex strings (#29726)
* Fix custom theme support for hex colours other than 6-char

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-14 08:31:21 +00:00
ElementRobot
779543fa0f [create-pull-request] automated change (#29733)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-14 08:09:43 +00:00
Andy Balaam
6b052fd067 Extract ResetIdentityBody into a separate object to allow re-using it (#29700) 2025-04-14 07:47:19 +00:00
Florian Duros
f39f3d2164 New room list: minor visual fixes (#29723)
* fix: use correct color for room list header

* fix: use error solid icon

* fix: rename Unread as Unreads

* test: update jest snapshots

* test(e2e): update screenshots

* test: fix test
2025-04-14 07:45:32 +00:00
Michael Telatynski
c929eedd81 Fix getOidcCallbackUrl for Element Desktop (#29711)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-11 14:44:22 +00:00
Florian Duros
bcd396e19e test: fix flaky SetIdServer test (#29719) 2025-04-11 14:26:48 +00:00
Peter Smit
ca56c2e091 Fix some webp images improperly marked as animated (#29713)
* Fix some webp images improperly marked as animated

* Add unit test for an unanimated webp file in extended file format

* Apply linting to webp test
2025-04-11 13:32:41 +00:00
Julien CLEMENT
d594441b53 Revert deletion of hydrateSession (#29703)
* Revert deletion of hydrateSession

* remove line break to make prettier happy :-)

* add tests for hydrateSession on soft logout

* fix coding style

---------

Co-authored-by: Florian Duros <florianduros@element.io>
2025-04-11 08:40:00 +00:00
ElementRobot
d4f25e8e13 [create-pull-request] automated change (#29717)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-11 06:22:22 +00:00
Michael Telatynski
d70d4486f0 Fix converttoroom & converttodm not working (#29705)
* Fix converttoroom & converttodm not working

setAccountData uses `deepCompare` within to avoid writing no-op updates

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use filterValidMDirect utility in setDMRoom

Ensure we do not mutate the account data as this would then upset `setAccountData`'s deepCompare later

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-10 15:54:41 +00:00
Michael Telatynski
60117b92d8 Ensure forceCloseAllModals also closes priority/static modals (#29706)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-10 11:19:32 +00:00
Kim Brose
afc8536d1c Don't crash the build when trying to docker-package from a shallow clone of a commit (#28503) 2025-04-10 07:59:44 +00:00
Giwayume
b5993aaabb Continue button is disabled when uploading a recovery key file (#29695)
* Wait for setState to complete before validating recovery key

* Linter fix

* Pass in recovery key to validateRecoveryKey function
2025-04-10 07:30:37 +00:00
renovate[bot]
e1b2e3a101 Update react monorepo to v19 (major) (#28914)
* Update react monorepo to v19

* Import JSX explicitly for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update usages of refs for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update react imports

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid legacy contexts as much as possible

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid deprecated React symbols

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Stash

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update usages of refs for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch pillify to use a html-react-parser approach rather than DOM muddling

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate react html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate react html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Memoize the EventContentBody component

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Discard changes to src/Linkify.tsx

* Discard changes to src/components/views/messages/TextualBody.tsx

* Discard changes to src/settings/handlers/AbstractLocalStorageSettingsHandler.ts

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Prepare for React 19 upgrade

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove stale comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-09 19:03:09 +00:00
ElementRobot
f54fbf7231 [create-pull-request] automated change (#29697)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-09 06:21:28 +00:00
Andy Balaam
01bfaec729 Catch errors after syncing recovery (#29691)
* Allow setting the Encryption settings tab to any initial state

* Add a variant to the reset flow for 'sync_failed'

* Catch errors after syncing recovery

Fixes #29229

* fixup! Allow setting the Encryption settings tab to any initial state

* fixup! Add a variant to the reset flow for 'sync_failed'

* Move docs for identity panel variants to ResetIdentityPanelVariant
2025-04-08 14:09:04 +00:00
Florian Duros
ab51ff6b7e Remove Secure Backup, Cross-signing and Cryptography sections in Security & Privacy user settings (#29088)
* feat(security tab)!: remove secure backup panel

BREAKING CHANGE: the key storage user interaction are moved into the Encryption tab. The debugging information are moved into the devtools.

* feat(security tab)!: remove cross signing section

BREAKING CHANGE: the cryptographic identity can be reseted in the Encryption tab. The debugging information are moved into the devtools

* feat(security tab)!: remove cryptography section

BREAKING CHANGE: this section can be found in the Advanced section of the encryption tab.

* test(security tab): update snapshot

* chore(security tab): remove unused component and function

* chore(security tab): update i18n

* test(e2e): remove `backups.spec.ts`
2025-04-08 12:40:06 +00:00
RiotRobot
803cb36d60 Reset matrix-js-sdk back to develop branch 2025-04-08 12:46:56 +00:00
RiotRobot
24167871e6 Merge branch 'master' into develop 2025-04-08 12:46:44 +00:00
RiotRobot
1fdd313ae9 v1.11.97 2025-04-08 12:43:24 +00:00
RiotRobot
18cd641cf6 Upgrade dependency to matrix-js-sdk@37.3.0 2025-04-08 12:39:32 +00:00
ElementRobot
2bc7223c1c Localazy Download (#29675)
* [create-pull-request] automated change

* test: fix `RoomListItemView` test

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
2025-04-08 09:51:15 +00:00
Will Hunt
8fc6638d6e Allow reporting a room when rejecting an invite. (#29570)
* Add report room dialog button/dialog.

* Update copy

* fixup tests / lint

* Fix title in test.

* update snapshot

* Add unit tests for dialog

* lint

* First pass at adding a report room on invite.

* Use a single line input field for reason to avoid bumping the layout.

* Fixups

* Embed reason to make it clear on grouping

* Revert accidental commit

* lint

* Add some playwright tests.

* tweaks

* Make ignored users list more accessible.

* i18n

* Fix sliding sync test.

* Add unit test

* Even more unit tests.

* move test

* Update to match designs.

* remove console statements

* fix css

* tidy up

* improve comments

* fix css

* updates
2025-04-08 09:08:00 +00:00
Florian Duros
e2b7852998 test e2e: use encryption tab instead of Security & Settings tab in crypto.spec.ts (#29595)
* test(e2e crypto): use encryption tab instead of Security & Settings tab in crypto.spec.ts

* test(e2e): remove wrong comment

* test(e2e crypto): keep `downloadKeysForUsers`

* test(e2e crypto): enter only password

* test: fix typo
2025-04-08 08:02:00 +00:00
R Midhun Suresh
c24a1baf38 RoomListViewModel: Reset primary and secondary filters on space change (#29672)
* Reset filters when space changes

* Write test
2025-04-04 08:40:25 +00:00
Florian Duros
d337106eed New room list: fix multiple visual issues (#29673)
* fix(room list item): add bold when there is a notification

* fix(room list item menu): fix color of check icon

* fix(menu): remove chevron

* chore: update @vector-im/compound-web

* test(room list): update tests

* test(e2e): update snapshots
2025-04-04 07:45:45 +00:00
renovate[bot]
5ce5e9092b Update dependency stylelint to v16.17.0 (#29659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 13:44:40 +00:00
Will Hunt
cb657d6848 Update report room dialog to match designs (#29669)
* Rework for designs

* Update report room position

* lint

* Improve test coverage
2025-04-03 13:25:19 +00:00
renovate[bot]
1f9db9fa1a Update dependency @sentry/browser to v9.10.1 (#29658)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 13:22:48 +00:00
R Midhun Suresh
ac3667508f New Room List: Fix mentions filter matching rooms with any highlight (#29668)
* Use new isMention instead of deprecated hasMention

So that only rooms with mentions (think @ symbol) are shown.

* Fix test
2025-04-03 12:56:06 +00:00
R Midhun Suresh
149b3b1049 RoomListStore: Support specific sorting requirements for muted rooms (#29665)
* Sort muted rooms to the bottom of the room list

* Re-insert room on mute/unmute

* Write tests

* Fix broken playwright test

Muted rooms are at the bottom, so we need to scroll.
2025-04-03 12:56:00 +00:00
renovate[bot]
d07a02fe3d Update dependency testcontainers to v10.23.0 (#29660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 08:50:52 +00:00
renovate[bot]
9d8d407019 Update dependency caniuse-lite to v1.0.30001707 (#29656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 15:30:40 +00:00
renovate[bot]
617fcdd4ce Update dependency @vector-im/matrix-wysiwyg to v2.38.3 (#29655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 15:01:45 +00:00
renovate[bot]
df38e16dbb Update babel monorepo to v7.27.0 (#29657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 14:18:57 +00:00
Florian Duros
817d7b78b8 New room list: add notification options menu (#29639)
* feat: add `utils.hasAccessToNotificationMenu`

* feat(room list item view model): use `hasAccessToNotificationMenu` to compute `showHoverMenu`

* feat(room list item menu view model): add notification options menu attributes

* feat(room list item menu view): add notification options

* test: add tests for `utils.hasAccessToNotificationMenu`

* test(room list item view model): add test for `showHoverMenu`

* test(room list item menu view model): add tests for new attributes

* test(room list item menu view): add tests for notification options menu

* chore: update i18n

* test(e2e): update screenshots

* test(e2e): add tests for notification options menu
2025-04-02 12:30:27 +00:00
renovate[bot]
31a59a5fa3 Update dependency @formatjs/intl-segmenter to v11.7.10 (#29648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 12:18:46 +00:00
R Midhun Suresh
55f1c27184 Room List: Scroll to top of the list when active room is not in the list (#29650)
* Scroll to top when active room is not in list

So that when filters are applied and the active room is not in the list
anymore, the list is scrolled to the top.

* Write test
2025-04-02 10:15:24 +00:00
renovate[bot]
92b85fcb13 Update definitelyTyped (#29647)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 10:14:56 +00:00
Florian Duros
82d93695a2 Update @vector-im/compound-web (#29641)
* chore: update `@vector-im/compound-web`

* test: update snapshots
2025-04-02 10:09:18 +00:00
Florian Duros
637ba3222e fix(SAS emoji): fix truncated emoji label (#29643) 2025-04-02 10:09:10 +00:00
Michael Telatynski
abbc1c0947 Update types for React 19 update (#29638)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-04-02 10:05:05 +00:00
renovate[bot]
602e65ff52 Update peter-evans/dockerhub-description digest to 0505d8b (#29645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 09:28:48 +00:00
renovate[bot]
e915e40e39 Update guibranco/github-status-action-v2 digest to 9b1d102 (#29644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 08:59:18 +00:00
renovate[bot]
35bf6afe55 Update all non-major dependencies (#29646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 08:34:10 +00:00
ElementRobot
52c8867e67 [create-pull-request] automated change (#29626)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-04-02 06:22:17 +00:00
David Baker
b217271027 Remove duplicate jitsi link (#29642)
jitsi.md was linked both here and in the 'setup' section and I think
it's more relevant to setup. The duplicate links are now breaking the
deploy for some reason. We probably shouldn't have both.
2025-04-01 14:28:34 +00:00
RiotRobot
286231aa37 v1.11.97-rc.0 2025-04-01 12:41:59 +00:00
RiotRobot
3f20df5e08 Upgrade dependency to matrix-js-sdk@37.3.0-rc.0 2025-04-01 12:30:05 +00:00
R Midhun Suresh
d5e070b300 Increase overscan count (#29392) 2025-04-01 09:21:15 +00:00
Florian Duros
d8ecb6362a New room list: reduce padding between avatar and room list border (#29634)
* feat(room list): reduce padding between avatar and room list border

* test(e2e): update screenshots
2025-03-31 20:33:07 +00:00
Michael Telatynski
bcc4ecf0cb Ensure clicks on spoilers do not get handled by the hidden content (#29618)
* Ensure clicks on spoilers do not get handled by the hidden content

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-31 13:37:25 +00:00
Florian Duros
24d9a174d7 fix(room list): add cursor pointer on room list item (#29627) 2025-03-31 13:15:56 +00:00
Michael Telatynski
7970b968c2 Prepare for React 19 upgrade (#29612)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-31 11:02:27 +00:00
Michael Telatynski
59e591c462 Fix missing ambiguous url tooltips on Element Desktop (#29619)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-31 11:02:13 +00:00
Florian Duros
804cb62698 test: fix BST/UTC time in preferences (#29628) 2025-03-31 10:54:51 +00:00
Michael Telatynski
8bb4d44532 Bundle Element Call with Element Web packages (#29309)
* Embed Element Call into Element Web packages

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Pass rageshakeSubmitUrl & posthogApiHost to EC widget

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use @vector-im/element-call-embedded

* Only pass posthog params to EC if Analytics is enabled

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix test mock

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update EC params to match https://github.com/element-hq/element-call/pull/3089

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update to latest element-call package

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* yarn.lock

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update to element-call-embedded@ v0.9.0-rc.1

* Gate Sentry params behind analytics consent

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update to element-call-embedded v0.9.0-rc.4

* Update Element Call embedded to 0.9.0 release

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
2025-03-28 20:34:32 +00:00
Michael Telatynski
209ab59978 Replace onHeightChanged with ResizeObserver (#29602)
* Replace onHeightChanged with ResizeObserver

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-28 10:36:10 +00:00
Florian Duros
6ae11dab52 New room list: fix spacing and padding (#29607)
* fix(menu): reduce gap between button and button size

* fix(notification decoration): increase gap between icons

* fix(room list item): different right padding depending on the menu, notification decoration and the regular case

* test: update snapshots

* test(e2e): update snapshots
2025-03-28 10:21:31 +00:00
Michael Telatynski
fac982811c Update usages of refs for React 19 compatibility (#29536)
* Update usages of refs for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-28 10:07:41 +00:00
Will Hunt
d7730f417b Hide an event notification if it is redacted (#29605)
* Hide notifications from events that have been redacted.

* lint

* add a void

* Remove ?.
2025-03-28 08:46:43 +00:00
ElementRobot
829b588dbf [create-pull-request] automated change (#29610)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-28 06:28:09 +00:00
ElementRobot
9a7cc7eb34 [create-pull-request] automated change (#29611)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-28 06:21:36 +00:00
Andrew Ferrazzutti
e537da4251 Docker: Use nginx-unprivileged as base image (#29353)
Instead of manually tweaking directory ownership & pidfile config to
enable running as non-root, use the official first-party base image for
achieving non-root.

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-27 20:43:13 +00:00
David Baker
094a7071e2 Make fetchdep check out matching branch name (#29601)
* Make fetchdep check out matching branch name

on a push to a branch.

* Remove buildkite support entirely
2025-03-27 13:54:05 +00:00
Florian Duros
a5673f603f e2e test: use encryption tab in enableKeyBackup instead of security & settings tab (#29234)
* test(e2e crypto): use encryption tab in `enableKeyBackup` instead of security & settings tab

* test(e2e crypto): verify device before trying to enable key backup

* doc: improve `enableKeyBackup` documentation
2025-03-27 11:35:54 +00:00
dependabot[bot]
0c210b9b3a Bump axios from 1.8.1 to 1.8.4 (#29590)
Bumps [axios](https://github.com/axios/axios) from 1.8.1 to 1.8.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.1...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-27 10:46:04 +00:00
Michael Telatynski
05df321f34 Import JSX explicitly for React 19 compatibility (#29535)
* Import JSX explicitly for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fixup

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fixup

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-27 10:43:58 +00:00
ElementRobot
8116dc5f60 [create-pull-request] automated change (#29597)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-27 10:20:28 +00:00
Florian Duros
d090499329 test(e2e room list): fix flaky test of activity decoration (#29600) 2025-03-27 09:23:06 +00:00
Florian Duros
6784d071a6 test(e2e dehydrated device): use Encryption tab instead of Security & Privacy tab (#29593) 2025-03-26 22:53:16 +00:00
Michael Telatynski
3f47487472 Switch away from nesting React trees and mangling the DOM (#29586)
* Switch away from nesting React trees and mangling the DOM

By parsing HTML events and manipulating the AST before passing it to React

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use MatrixClientContext in Pill now that we are in the main React tree

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing import

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Break import cycles

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Minimise

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Docs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-26 20:25:03 +00:00
Michael Telatynski
89e22e00fb Fix MFileBody fileName not considering filename (#29589)
* Fix MFileBody fileName not considering `filename`

* Update MFileBody.tsx
2025-03-26 19:00:22 +00:00
Florian Duros
bbd798ef36 New room list: add notification decoration (#29552)
* chore: update @compound-web

* feat(notification decoration): add NotificationDecoration component

* feat(room list item): get notification state in view model

* feat(room list item): use notification decoration in RoomListItemView

* test(notification decoration): add tests

* test(room list item view model): add a11yLabel tests

* test(room list item): update tests

* test(e2e): add decoration tests
2025-03-26 13:32:02 +00:00
ElementRobot
f3f05874fa Localazy Download, including changes to 'only send to verified' (#29592)
* [create-pull-request] automated change

* Update tests for copy changes on 'only send to verified'

* Update one more test snapshot for new wording of exclude unverified

* Update screenshots

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
2025-03-26 13:00:17 +00:00
ElementRobot
d9091bcba9 [create-pull-request] automated change (#29591)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-26 06:16:24 +00:00
RiotRobot
68692c5af5 Reset matrix-js-sdk back to develop branch 2025-03-25 14:54:43 +00:00
RiotRobot
03dc093e89 Merge branch 'master' into develop 2025-03-25 14:54:30 +00:00
RiotRobot
c68157ec46 v1.11.96 2025-03-25 14:51:30 +00:00
RiotRobot
4fc8b8915b Upgrade dependency to matrix-js-sdk@37.2.0 2025-03-25 14:44:39 +00:00
R Midhun Suresh
690d623dcf New Room List: Move tests that are in the wrong location (#29584)
* Move test to room-list-filter-sort.spec.ts

This test should reside with all the other filter related tests.

* Move sorting tests out of filter describe block

These two tests somehow ended up in the wrong block!

* Fix lint
2025-03-25 12:18:58 +00:00
Michael Telatynski
102a1ddb9e Fix token expiry racing with login causing wrong error to be shown (#29566)
* Fix token expiry racing with login causing wrong error to be shown

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* yay jest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-25 12:07:07 +00:00
Michael Telatynski
99ea51c6f2 Update mxLoginWithAccessToken to grab deviceId from /whoami (#29571)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-25 10:32:27 +00:00
R Midhun Suresh
3f1e56b715 RoomListStore: Unread filter should match rooms that were marked as unread (#29580)
* Unread filter should match rooms marked as unread

* Re-insert room into skip list on account data

So that filters are re-calculated when rooms are marked as unread.

* Write test
2025-03-24 16:51:42 +00:00
Richard van der Hoff
f3653abe92 Fix bug which caused startup to hang if the clock was wound back since a previous session (#29558)
* SessionLock: reduce the stale time

30 seconds staring at a spinner while we wait for a stale lock to expire is
rather painful. Pretty sure 15 seconds will be fine.

* SessionLock: deal with the clock having been wound back

If a previous session terminated uncleanly, and then the clock is wound back,
we could be waiting a very long time for the previous session's claim to
expire.

We can fix this by simply treating a future claim the same as "now", and
waiting for the normal stale timeout.

* fixup! SessionLock: deal with the clock having been wound back
2025-03-24 16:43:53 +00:00
Florian Duros
a6e8d512d0 Room notification state: add clearer methods, documentation and deprecation (#29564)
* feat(notification state): add clearer methods, documentation and deprecation

* test(room notification state): add tests for new attributes

* doc: more explicit documentation for `hasUnreadCount`

* doc: add link to `RoomNotificationState.isMention` in `hasMentions` doc

* refactor: change `isSilent` to `hasAnyNotificationOrActivity`

* refactor: add `invited` to `determineUnreadState` and use it in `NotificationState` & `RoomNotificationState`

* test: update `RoomNotificationState` test to use `invited`

* test: update other tests to add `invited`

* refactor: remove count check in `isNotification`
2025-03-24 16:00:47 +00:00
Will Hunt
13c4ab2cf4 Add support for hiding videos (#29496)
* start hide

* Move useSettingsValueWithSetter to useSettings

* Add new setting showMediaEventIds

* Add a migration path

* Add an action button to hide settings.

* Tweaks to MImageBody to support new setting.

* Fixup and add tests

* add description for migration

* docs fixes

* add type

* i18n

* appese prettier

* Add tests for HideActionButton

* lint

* lint

* First pass at support for previewing/hiding images.

* Add a test for video files.

* First pass at supporting hiding video files.

* Use a hook for media visibility.

* Drop setting hook usage.

* Fixup MImageBody test

* Fixup tests

* Support functional components for message body rendering.

* Add a comment

* Move props into IProps

* Use new wrapping logic

* lint

* fixup

* allow for a delay for the image to render

* remove .only

* lint

* Fix jest test

* Fixup tests.

* make tests happy

* Improve comments

* review fixes

* unbreak test
2025-03-24 14:38:34 +00:00
Robin
74da64db63 Use an outline icon for the report room button (#29573)
For visual consistency
2025-03-24 14:03:34 +00:00
ElementRobot
e5d37a324d Localazy Download (#29577)
* [create-pull-request] automated change

* Fix playwright tests

* Update tests to reflect string changes

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
2025-03-24 12:02:20 +00:00
Andy Balaam
d0c1610bd2 Use 'Dismiss' to close pinned identity changes, instead of 'Ok' (#29569) 2025-03-24 09:20:26 +00:00
ElementRobot
64e2a843c3 [create-pull-request] automated change (#29574)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-22 06:15:38 +00:00
Julien CLEMENT
fba59381a0 Generate/load pickle key on SSO (#29568)
* Generate/load pickle key when logged in with SSO

* add comments

* Refactor pickle key loading/creation

* Coding style fixes and fix racy loadOrCreatePickleKey

* fix outdated documentation comment

* fix prettier

Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>

---------

Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
2025-03-21 19:10:34 +00:00
Will Hunt
e1970df704 Add report room dialog button/dialog. (#29513)
* Add report room dialog button/dialog.

* Update copy

* fixup tests / lint

* Fix title in test.

* update snapshot

* Add unit tests for dialog

* lint
2025-03-21 17:08:37 +00:00
R Midhun Suresh
b54122884c RoomListViewModel: Make the active room sticky in the list (#29551)
* Add new hook for sticky room

This hook takes the filtered, sorted rooms and returns a new list of
rooms such that the active room is kept in the same index even when the
list has changes.

* Use new hook in view model

* Add tests

* Use single * in comments
2025-03-21 12:11:59 +00:00
Michael Telatynski
0d28df0f67 Reuse PushProcessor from MatrixClient (#29561)
* Reuse PushProcessor from MatrixClient

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Reuse PushProcessor getPushRuleGlobRegex

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update regex handling

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-21 11:34:06 +00:00
R Midhun Suresh
3a39486468 RoomListViewModel: Reset any primary filter on secondary filter change (#29562)
* Reset primary filter when secondary filter is applied

* Add test
2025-03-21 10:46:20 +00:00
R Midhun Suresh
0dc295e3b8 RoomListStore: Unread filter should only filter rooms having unread counts (#29555)
* Use `hasUnreadCount` instead of `isUnread`

* Fix broken test

* Write test
2025-03-21 08:28:00 +00:00
ElementRobot
5a6c9a4c9a [create-pull-request] automated change (#29559)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-21 06:22:19 +00:00
Will Hunt
599112e122 Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. (#29363)
* Fix labelling of avatar menu

* Make the integration manager toggle more clear.

* fix label

* lint

* Update snapshots.

* Refactor many cases of checkbox to use the new compound component.

* Remove non-checkbox related changes

* Reset some things

* Remove usages of mx_checkbox* styling.

* Use label locators for apperance tests.

* small linter tweaks

* lint

* update screenshot

* Test updates

* lint

* Realign checkboxes for device selection.

* Fixup QuickSettings styling

* remove comment

* lint

* flex comment

* remove unused label

* remove redundant classes

* add test for spaces

* lint

* Copyright

* fixup spaces test

* spaces lint

* Replace pin with compound pin.

* Realign icons

* Remove hack for colouring icons

* Adjust existing rooms component to correctly label room.

* Add test for adding an existing room to an existing space.

* Set deterministic sort order for rooms

* lint
2025-03-20 15:35:54 +00:00
Andy Balaam
170dcd1c0e In force-verify mode, prevent bypassing by cancelling device verification (#29487)
* In force-verify mode, prevent bypassing by cancelling device verification

* Don't show the after-login screen if we are racing with forced verification

* Unit test for not bypassing verification by cancelling device verify
2025-03-20 15:10:08 +00:00
Arpit Batra
435d0f96b8 Add title attribute to user identifier (#29547) 2025-03-20 08:58:57 +00:00
ElementRobot
c1a44414ec [create-pull-request] automated change (#29550)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-20 06:15:32 +00:00
Michael Telatynski
a32704ae5b Silence React error about getDerivedStateFromProps (#29544)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-19 18:07:21 +00:00
Michael Telatynski
5b1be70ee8 Avoid legacy contexts as much as possible (#29537)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-19 10:40:06 +00:00
Michael Telatynski
a6ae04bcde Update react imports (#29538)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-19 10:39:52 +00:00
renovate[bot]
b65d18433d Update playwright to v1.51.1 (#29539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 09:05:10 +00:00
Florian Duros
3587161a2c New room list: add selection decoration (#29531)
* fix(room list): remove 1px extra padding

* feat(room list): add selection decoration to room list item and scroll list to this element

* test(room list item): add is selected test

* test(room list): update snapshot

* test(e2e): add test to keep the room list item visible

* test(e2e): update snapshots
2025-03-19 08:39:12 +00:00
ElementRobot
35aed69604 [create-pull-request] automated change (#29541)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-19 06:20:49 +00:00
ElementRobot
d2c334dd25 [create-pull-request] automated change (#29540)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-19 06:16:03 +00:00
renovate[bot]
98470b8045 Update all non-major dependencies (#29533)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 18:08:06 +00:00
renovate[bot]
4d97af0baf Update dependency caniuse-lite to v1.0.30001704 (#29526)
* Update dependency caniuse-lite to v1.0.30001704

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-18 18:07:41 +00:00
David Baker
f59af3786e Simplified Sliding Sync (#28515)
* Experimental SSS

Working branch to get SSS functional on element-web.

Requires https://github.com/matrix-org/matrix-js-sdk/pull/4400

* Adjust tests to use new behaviour

* Remove well-known proxy URL lookup; always use native

This is actually required for SSS because otherwise it would use
the proxy over native support.

* Linting

* Debug logging

* Control the race condition when swapping between rooms

* Dont' filter by space as synapse doesn't support it

* Remove SS code related to registering lists and managing ranges

- Update the spidering code to spider all the relevant lists.
- Add canonical alias to the required_state to allow room name calcs to work.

Room sort order is busted because we don't yet look at `bump_stamp`.

* User bumpStamp if it is present

* Drop initial room load from 20 per list to 10

* Half the batch size to trickle more quickly

* Prettier

* prettier on tests too

* Remove proxy URL & unused import

* Hopefully fix tests to assert what the behaviour is supposed to be

* Move the singleton to the manager tyo fix import loop

* Very well, code, I will remove you

Why were you there in the first place?

* Strip out more unused stuff

* Fix playwright test

Seems like this lack of order updating unless a room is selected
was just always a bug with both regular and non-sliding sync. I
have no idea how the test passed on develop because it won't run.

* Fix test to do maybe what it was supposed to do... possibly?

* Remove test for old pre-simplified sliding sync behaviour

* Unused import

* Remove sliding sync proxy & test

I was wrong about what this test was asserting, it was suposed
to assert that notification dots aren't shown (because SS didn't
support them somehow I guess) but they are fine in SSS so the test
is just no longer relevant.

* Remove now pointless credentials

* Remove subscription removal as SSS doesn't do that

* Update tests

* add test

* Switch to new labs flag & break if old labs flag is enabled

* Remove unused import & fix test

* Fix other test

* Remove name & description from old labs flag

as they're not displayed anywhere so not useful

* Remove old sliding sync option

by making it not a feature

* Add back unread nindicator test but inverted

and minus the bit about disabling notification which surely would have
defeated the original point anyway?

* Reinstate test for room_subscriptions

...and also make tests actually use sliding sync

* Use UserFriendlyError

* Remove empty constructor

* Remove unrelated changes

* Unused import

* Fix import

* Avoid moving import

---------

Co-authored-by: Kegan Dougal <7190048+kegsay@users.noreply.github.com>
2025-03-18 17:54:32 +00:00
renovate[bot]
4fa540962a Update robinraju/release-downloader digest to daf26c5 (#29532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 16:16:41 +00:00
renovate[bot]
e4f9c650ee Update react monorepo (#28905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 16:12:40 +00:00
renovate[bot]
f3654e45d6 Update dependency stylelint to v16.16.0 (#29530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:04:34 +00:00
renovate[bot]
2a8b26d90a Update dependency @sentry/browser to v9.5.0 (#29529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:03:01 +00:00
renovate[bot]
6ed811d4c9 Update typescript-eslint monorepo to v8.26.1 (#29527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:02:39 +00:00
renovate[bot]
c85e6d196d Update dependency @playwright/test to v1.51.0 (#29528)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:02:36 +00:00
renovate[bot]
98c691670e Update dependency @vector-im/compound-design-tokens to v4.0.1 (#29525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:02:08 +00:00
renovate[bot]
7e3866dd9a Update dependency @types/node to v18.19.80 (#29524)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:01:50 +00:00
renovate[bot]
c6b1a92f2e Update babel monorepo to v7.26.10 (#29523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:01:24 +00:00
renovate[bot]
7b809171fc Update guibranco/github-status-action-v2 digest to fe98467 (#29522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:01:19 +00:00
renovate[bot]
0bef212679 Update docker/login-action digest to 74a5d14 (#29521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 14:56:34 +00:00
renovate[bot]
56d115c2ff Update dependency testcontainers to v10.21.0 (#29520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 14:38:09 +00:00
David Baker
cdd2622151 Doc improvements from #29138 (#29503)
* Rename props & fix typo

* Docs

* Better docs

* Add comment

* Fix typo

* Paragraphs in tsdoc

* Add comment

* Hopefully clearer comment

* Really fix typo

Co-authored-by: Will Hunt <will@half-shot.uk>

* Stray word

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>

* Hopefully clearer comment

* Typo

* Formatting & clarity

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

---------

Co-authored-by: Will Hunt <will@half-shot.uk>
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-03-18 14:35:39 +00:00
Will Hunt
e662c1959b Add ability to hide images after clicking "show image" (#29467)
* start hide

* Move useSettingsValueWithSetter to useSettings

* Add new setting showMediaEventIds

* Add a migration path

* Add an action button to hide settings.

* Tweaks to MImageBody to support new setting.

* Fixup and add tests

* add description for migration

* docs fixes

* add type

* i18n

* appese prettier

* Add tests for HideActionButton

* lint

* lint

* Use a hook for media visibility.

* Drop setting hook usage.

* Fixup MImageBody test

* Fixup tests

* Support functional components for message body rendering.

* Add a comment

* Move props into IProps
2025-03-18 14:23:24 +00:00
RiotRobot
b5f8f2b9f5 v1.11.96-rc.0 2025-03-18 13:28:56 +00:00
RiotRobot
425adb147a Upgrade dependency to matrix-js-sdk@37.2.0-rc.0 2025-03-18 13:26:07 +00:00
R Midhun Suresh
839329b52a RoomListViewModel: Track the index of the active room in the list (#29519)
* Introduce a hook to track active room

This hook simply keeps a state which tracks the index of the active room
in the list of rooms passed through props. This index will be recomputed
if the active rooms changes or if the list itself changed.

* Use hook in the view model

* Write tests

* Fix broken tests
2025-03-18 12:49:10 +00:00
Florian Duros
7de54a385e New room list: add empty state (#29512)
* refactor: extract room creation and right verification

* refactor: update `RoomListHeaderViewModel` to use utils

* feat(room list filter): add filter key to `PrimaryFilter` model

* feat(room list filter): return active primary filter

* feat(room list): add create room action and rights verification

* test: update room list tests

* feat(empty room list): add empty room list

* test(empty room list): add empty room list tests

* feat(room list): use empty room list in `RoomListView`

* test(room list panel): update tests

* test(e2e): add e2e tests for empty room list

* test(e2e): update room list header snapshot
2025-03-18 10:02:33 +00:00
ElementRobot
55b0b1107e [create-pull-request] automated change (#29515)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-18 06:15:40 +00:00
R Midhun Suresh
550f529a30 Implement MessagePreviewViewModel (#29514)
* Implement message preview vm

* Write tests
2025-03-17 16:38:52 +00:00
Michael Telatynski
a6ad6e9ae2 Remove temporary awscli s3-r2 workaround (#29393)
* Remove temporary awscli s3-r2 workaround

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update build_develop.yml

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 15:07:57 +00:00
R Midhun Suresh
d88776e2dc RoomListViewModel: Add functionality to toggle message preview setting (#29511)
* Add setting for showing message previews

* Add hook to track and toggle message preview

* Use hook in view model

* Add tests

* Fix tests

* Fix lint

* Fix typo
2025-03-17 15:07:14 +00:00
1061 changed files with 35271 additions and 22004 deletions

View File

@@ -30,6 +30,10 @@ module.exports = {
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
"Use UIStore to access window dimensions instead.",
),
...buildRestrictedPropertiesOptions(
["React.forwardRef", "*.forwardRef", "forwardRef"],
"Use ref props instead.",
),
...buildRestrictedPropertiesOptions(
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
"Use Media helper instead to centralise access for customisation.",
@@ -55,6 +59,11 @@ module.exports = {
"error",
{
paths: [
{
name: "react",
importNames: ["forwardRef"],
message: "Use ref props instead.",
},
{
name: "@testing-library/react",
message: "Please use jest-matrix-react instead",

View File

@@ -11,7 +11,7 @@ runs:
using: composite
steps:
- name: Download release tarball
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1
with:
tag: ${{ inputs.tag }}
fileName: element-*.tar.gz*

View File

@@ -43,9 +43,9 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
# Disable cache on Windows as it is slower than not caching
# https://github.com/actions/setup-node/issues/975
@@ -77,7 +77,7 @@ jobs:
yarn build
- name: Upload Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: webapp-${{ matrix.image }}
path: webapp

View File

@@ -14,7 +14,7 @@ jobs:
R2_URL: ${{ vars.CF_R2_S3_API }}
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Download package
run: |
@@ -62,7 +62,7 @@ jobs:
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: element-web.deb
path: element-web.deb

View File

@@ -26,15 +26,9 @@ jobs:
R2_URL: ${{ vars.CF_R2_S3_API }}
R2_PUBLIC_URL: "https://element-web-develop.element.io"
steps:
# Workaround for https://www.cloudflarestatus.com/incidents/t5nrjmpxc1cj
- uses: unfor19/install-aws-cli-action@v1
with:
version: 2.22.35
verbose: false
arch: amd64
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -59,7 +53,7 @@ jobs:
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: webapp
path: dist/develop.tar.gz
@@ -115,10 +109,11 @@ jobs:
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
# as the expires after 24h and requires auth to download.
# Element Desktop's fetch script uses this tarball to fetch latest develop to build Nightlies.
# Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
- name: Deploy to R2
run: |
aws s3 cp dist/develop.tar.gz s3://$R2_BUCKET/develop.tar.gz --endpoint-url $R2_URL --region=auto
aws s3 cp _deploy/ s3://$R2_BUCKET/ --recursive --endpoint-url $R2_URL --region=auto
aws s3 cp dist/develop.tar.gz s3://$R2_BUCKET/develop.tar.gz --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
aws s3 cp _deploy/ s3://$R2_BUCKET/ --recursive --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}

View File

@@ -34,7 +34,7 @@ jobs:
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Load GPG key
run: |

View File

@@ -20,12 +20,12 @@ jobs:
env:
TEST_TAG: vectorim/element-web:test
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
- name: Install Cosign
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3
if: github.event_name != 'pull_request'
- name: Set up QEMU
@@ -37,14 +37,14 @@ jobs:
install: true
- name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
if: github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
@@ -132,7 +132,7 @@ jobs:
cosign sign --yes ${images}
- name: Update repo description
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
if: github.event_name != 'pull_request'
continue-on-error: true
with:

View File

@@ -17,23 +17,23 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Fetch element-desktop
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: element-hq/element-desktop
path: element-desktop
- name: Fetch element-web
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
path: element-web
- name: Fetch matrix-js-sdk
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: matrix-org/matrix-js-sdk
path: matrix-js-sdk
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
cache-dependency-path: element-web/yarn.lock
@@ -47,7 +47,7 @@ jobs:
echo "- [Automations](automations.md)" >> docs/SUMMARY.md
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2
with:
mdbook-version: "0.4.10"
@@ -88,7 +88,7 @@ jobs:
run: mdbook build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: ./book
@@ -104,4 +104,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4

View File

@@ -25,7 +25,7 @@ jobs:
actions: read
steps:
- name: Download HTML report
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -33,7 +33,7 @@ jobs:
path: playwright-report
- name: 📤 Deploy to Netlify
uses: matrix-org/netlify-pr-preview@v3
uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
with:
path: playwright-report
owner: ${{ github.event.workflow_run.head_repository.owner.login }}

View File

@@ -50,11 +50,11 @@ jobs:
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: element-hq/element-web
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -81,7 +81,7 @@ jobs:
yarn build
- name: Upload Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: webapp
path: webapp
@@ -89,7 +89,7 @@ jobs:
- name: Calculate runner variables
id: runner-vars
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
@@ -129,18 +129,18 @@ jobs:
- runAllTests: false
project: Pinecone
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
repository: element-hq/element-web
- name: 📥 Download artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: webapp
path: webapp
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
cache-dependency-path: yarn.lock
@@ -154,12 +154,11 @@ jobs:
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
- name: Cache playwright binaries
uses: actions/cache@v4
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
id: playwright-cache
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright.outputs.version }}
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
@@ -180,25 +179,35 @@ jobs:
- name: Upload blob report to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
path: blob-report
retention-days: 1
downstream-modules:
name: Downstream Playwright tests [element-modules]
needs: build
if: inputs.skip != true && github.event_name == 'merge_group'
uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main
with:
webapp-artifact: webapp
complete:
name: end-to-end-tests
needs: playwright
needs:
- playwright
- downstream-modules
if: always()
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
if: inputs.skip != true
with:
persist-credentials: false
repository: element-hq/element-web
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
if: inputs.skip != true
with:
cache: "yarn"
@@ -210,7 +219,7 @@ jobs:
- name: Download blob reports from GitHub Actions Artifacts
if: inputs.skip != true
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: all-blob-reports-*
path: all-blob-reports
@@ -226,11 +235,11 @@ jobs:
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
- name: Upload HTML report
if: always() && inputs.skip != true
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: html-report
path: playwright-report
retention-days: 14
- if: needs.playwright.result != 'skipped' && needs.playwright.result != 'success'
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1

View File

@@ -10,7 +10,7 @@ jobs:
name: Tidy closed issues
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
id: main
with:
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
@@ -142,7 +142,7 @@ jobs:
});
}
}
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
name: Close duplicate as Not Planned
if: steps.main.outputs.closeAsNotPlanned
with:

View File

@@ -28,7 +28,7 @@ jobs:
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
@@ -36,7 +36,7 @@ jobs:
path: webapp
- name: 📤 Deploy to Netlify
uses: matrix-org/netlify-pr-preview@v3
uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
with:
path: webapp
owner: ${{ github.event.workflow_run.head_repository.owner.login }}

View File

@@ -16,7 +16,7 @@ jobs:
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
HS_URL: ${{ secrets.BETABOT_HS_URL }}
ROOM_ID: ${{ secrets.ROOM_ID }}

View File

@@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Update synapse image
run: |

View File

@@ -8,7 +8,7 @@ jobs:
name: Check PR base branch
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const baseBranch = context.payload.pull_request.base.ref;

View File

@@ -41,7 +41,7 @@ jobs:
REPOS: matrix-js-sdk element-web element-desktop
steps:
- name: Checkout Element Desktop
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
if: inputs.element-desktop
with:
repository: element-hq/element-desktop
@@ -51,7 +51,7 @@ jobs:
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- name: Checkout Element Web
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
if: inputs.element-web
with:
repository: element-hq/element-web
@@ -61,7 +61,7 @@ jobs:
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- name: Checkout Matrix JS SDK
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
if: inputs.matrix-js-sdk
with:
repository: matrix-org/matrix-js-sdk
@@ -100,7 +100,7 @@ jobs:
repo: matrix-org/matrix-js-sdk
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
wait-interval: 10
check-name: draft
check-name: "draft / draft"
allowed-conclusions: success
- name: Wait for element-web draft
@@ -111,7 +111,7 @@ jobs:
repo: element-hq/element-web
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
wait-interval: 10
check-name: draft
check-name: "draft / draft"
allowed-conclusions: success
- name: Wait for element-desktop draft
@@ -122,5 +122,5 @@ jobs:
repo: element-hq/element-desktop
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
wait-interval: 10
check-name: draft
check-name: "draft / draft"
allowed-conclusions: success

View File

@@ -23,9 +23,9 @@ jobs:
name: "Typescript Syntax Check"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -51,12 +51,13 @@ jobs:
error|invalid_json
error|misconfigured
welcome_to_element
devtools|settings|elementCallUrl
rethemendex_lint:
name: "Rethemendex Check"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- run: ./res/css/rethemendex.sh
@@ -66,9 +67,9 @@ jobs:
name: "ESLint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -84,9 +85,9 @@ jobs:
name: "Style Lint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -102,9 +103,9 @@ jobs:
name: "Workflow Lint"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"
@@ -120,9 +121,9 @@ jobs:
name: "Analyse Dead Code"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"

View File

@@ -39,12 +39,12 @@ jobs:
runner: [1, 2]
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
- name: Yarn cache
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "lts/*"
cache: "yarn"
@@ -55,7 +55,7 @@ jobs:
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
- name: Jest Cache
uses: actions/cache@v4
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
with:
path: /tmp/jest_cache
key: ${{ hashFiles('**/yarn.lock') }}
@@ -84,7 +84,7 @@ jobs:
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-${{ matrix.runner }}
path: |
@@ -104,7 +104,7 @@ jobs:
- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
uses: guibranco/github-status-action-v2@5f2b01ce1394109f70954ae6b69ef41cf7928e63
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success

View File

@@ -11,7 +11,8 @@ jobs:
runs-on: ubuntu-24.04
if: |
contains(github.event.issue.assignees.*.login, 't3chguy') ||
contains(github.event.issue.assignees.*.login, 'andybalaam') ||
contains(github.event.issue.assignees.*.login, 'florianduros') ||
contains(github.event.issue.assignees.*.login, 'dbkr') ||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
steps:
- uses: actions/add-to-project@main

View File

@@ -27,7 +27,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
github.rest.issues.addLabels({
@@ -44,7 +44,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'good first issue') ||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
github.rest.issues.addLabels({
@@ -61,7 +61,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'X-Needs-Info')
steps:
- id: add_to_project
uses: actions/add-to-project@v1.0.2
uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -84,7 +84,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
steps:
- id: add_to_project
uses: actions/add-to-project@v1.0.2
uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -150,15 +150,15 @@ jobs:
project-url: https://github.com/orgs/element-hq/projects/41
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
verticals_feature:
name: Add labelled issues to Verticals Feature project
crypto:
name: Add labelled issues to Crypto project
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
contains(github.event.issue.labels.*.name, 'Team: Crypto')
steps:
- uses: actions/add-to-project@main
with:
project-url: https://github.com/orgs/element-hq/projects/57
project-url: https://github.com/orgs/element-hq/projects/76
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
tech_debt:

View File

@@ -12,7 +12,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
operations-per-run: 100
# Flaky test issue closing

View File

@@ -62,7 +62,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
contains(github.event.issue.labels.*.name, 'Z-Labs')
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
github.rest.issues.removeLabel({

View File

@@ -9,9 +9,9 @@ jobs:
update:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
cache: "yarn"
node-version: "lts/*"

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-24.04
environment: Matrix
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
HS_URL: ${{ secrets.BETABOT_HS_URL }}
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}

1
.gitignore vendored
View File

@@ -25,7 +25,6 @@ electron/pub
.env
/coverage
# Auto-generated file
/src/modules.ts
/src/modules.js
/build_config.yaml
/book

View File

@@ -1,3 +1,226 @@
Changes in [1.11.104](https://github.com/element-hq/element-web/releases/tag/v1.11.104) (2025-06-17)
====================================================================================================
## ✨ Features
* Update the mobile\_guide page to the new design. ([#30006](https://github.com/element-hq/element-web/pull/30006)). Contributed by @pixlwave.
* Provide a devtool for manually verifying other devices ([#30094](https://github.com/element-hq/element-web/pull/30094)). Contributed by @andybalaam.
* Implement MSC4155: Invite filtering ([#29603](https://github.com/element-hq/element-web/pull/29603)). Contributed by @Half-Shot.
* Add low priority avatar decoration to room tile ([#30065](https://github.com/element-hq/element-web/pull/30065)). Contributed by @MidhunSureshR.
* Add ability to prevent window content being captured by other apps (Desktop) ([#30098](https://github.com/element-hq/element-web/pull/30098)). Contributed by @t3chguy.
* New room list: move message preview in user settings ([#30023](https://github.com/element-hq/element-web/pull/30023)). Contributed by @florianduros.
* New room list: change room options icon ([#30029](https://github.com/element-hq/element-web/pull/30029)). Contributed by @florianduros.
* RoomListStore: Sort low priority rooms to the bottom of the list ([#30070](https://github.com/element-hq/element-web/pull/30070)). Contributed by @MidhunSureshR.
* Add low priority filter pill to the room list UI ([#30060](https://github.com/element-hq/element-web/pull/30060)). Contributed by @MidhunSureshR.
* New room list: remove color gradient in space panel ([#29721](https://github.com/element-hq/element-web/pull/29721)). Contributed by @florianduros.
* /share?msg=foo endpoint using forward message dialog ([#29874](https://github.com/element-hq/element-web/pull/29874)). Contributed by @ara4n.
## 🐛 Bug Fixes
* Do not send empty auth when setting up cross-signing keys ([#29914](https://github.com/element-hq/element-web/pull/29914)). Contributed by @gnieto.
* Settings: flip local video feed by default ([#29501](https://github.com/element-hq/element-web/pull/29501)). Contributed by @jbtrystram.
* AccessSecretStorageDialog: various fixes ([#30093](https://github.com/element-hq/element-web/pull/30093)). Contributed by @richvdh.
* AccessSecretStorageDialog: fix inability to enter recovery key ([#30090](https://github.com/element-hq/element-web/pull/30090)). Contributed by @richvdh.
* Fix failure to upload thumbnail causing image to send as file ([#30086](https://github.com/element-hq/element-web/pull/30086)). Contributed by @t3chguy.
* Low priority menu item should be a toggle ([#30071](https://github.com/element-hq/element-web/pull/30071)). Contributed by @MidhunSureshR.
* Add sanity checks to prevent users from ignoring themselves ([#30079](https://github.com/element-hq/element-web/pull/30079)). Contributed by @MidhunSureshR.
* Fix issue with duplicate images ([#30073](https://github.com/element-hq/element-web/pull/30073)). Contributed by @fatlewis.
* Handle errors returned from Seshat ([#30083](https://github.com/element-hq/element-web/pull/30083)). Contributed by @richvdh.
Changes in [1.11.103](https://github.com/element-hq/element-web/releases/tag/v1.11.103) (2025-06-10)
====================================================================================================
## 🐛 Bug Fixes
+ Check the sender of an event matches owner of session, preventing sender spoofing by homeserver owners.
[13c1d20](https://github.com/matrix-org/matrix-rust-sdk/commit/13c1d2048286bbabf5e7bc6b015aafee98f04d55) (High, [GHSA-x958-rvg6-956w](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-x958-rvg6-956w)).
Changes in [1.11.102](https://github.com/element-hq/element-web/releases/tag/v1.11.102) (2025-06-03)
====================================================================================================
## ✨ Features
* EW: Modernize the recovery key input modal ([#29819](https://github.com/element-hq/element-web/pull/29819)). Contributed by @uhoreg.
* New room list: move secondary filters into primary filters ([#29972](https://github.com/element-hq/element-web/pull/29972)). Contributed by @florianduros.
* Prompt the user when key storage is unexpectedly off ([#29912](https://github.com/element-hq/element-web/pull/29912)). Contributed by @andybalaam.
* New room list: move sort menu in room list header ([#29983](https://github.com/element-hq/element-web/pull/29983)). Contributed by @florianduros.
* New room list: rework spacing of room list item ([#29965](https://github.com/element-hq/element-web/pull/29965)). Contributed by @florianduros.
* RLS: Remove forgotten room from skiplist ([#29933](https://github.com/element-hq/element-web/pull/29933)). Contributed by @MidhunSureshR.
* Add room list sorting ([#29951](https://github.com/element-hq/element-web/pull/29951)). Contributed by @dbkr.
* Don't use the minimised width(68px) on the new room list ([#29778](https://github.com/element-hq/element-web/pull/29778)). Contributed by @langleyd.
## 🐛 Bug Fixes
* [Backport staging] Close call options popup menu when option has been selected ([#30054](https://github.com/element-hq/element-web/pull/30054)). Contributed by @RiotRobot.
* RoomListStoreV3: Only add new rooms that pass `VisibilityProvider` check ([#29974](https://github.com/element-hq/element-web/pull/29974)). Contributed by @MidhunSureshR.
* Re-order primary filters ([#29957](https://github.com/element-hq/element-web/pull/29957)). Contributed by @dbkr.
* Fix leaky CSS adding `!` to all H1 elements ([#29964](https://github.com/element-hq/element-web/pull/29964)). Contributed by @t3chguy.
* Fix extensions panel style ([#29273](https://github.com/element-hq/element-web/pull/29273)). Contributed by @langleyd.
* Fix state events being hidden from widgets in read\_events actions ([#29954](https://github.com/element-hq/element-web/pull/29954)). Contributed by @robintown.
* Remove old filter test ([#29963](https://github.com/element-hq/element-web/pull/29963)). Contributed by @dbkr.
Changes in [1.11.101](https://github.com/element-hq/element-web/releases/tag/v1.11.101) (2025-05-20)
====================================================================================================
## ✨ Features
* New room list: add keyboard navigation support ([#29805](https://github.com/element-hq/element-web/pull/29805)). Contributed by @florianduros.
* Use the JoinRuleSettings component for the guest link access prompt. ([#28614](https://github.com/element-hq/element-web/pull/28614)). Contributed by @toger5.
* Add loading state to the new room list view ([#29725](https://github.com/element-hq/element-web/pull/29725)). Contributed by @langleyd.
* Make OIDC identity reset consistent with EX ([#29854](https://github.com/element-hq/element-web/pull/29854)). Contributed by @andybalaam.
* Support error code for email / phone adding unsupported (MSC4178) ([#29855](https://github.com/element-hq/element-web/pull/29855)). Contributed by @dbkr.
* Update identity reset UI (Make consistent with EX) ([#29701](https://github.com/element-hq/element-web/pull/29701)). Contributed by @andybalaam.
* Add secondary filters to the new room list ([#29818](https://github.com/element-hq/element-web/pull/29818)). Contributed by @dbkr.
* Fix battery drain from Web Audio ([#29203](https://github.com/element-hq/element-web/pull/29203)). Contributed by @mbachry.
## 🐛 Bug Fixes
* Fix go home shortcut on macos and change toggle action events shortcut ([#29929](https://github.com/element-hq/element-web/pull/29929)). Contributed by @florianduros.
* New room list: fix outdated message preview when space or filter change ([#29925](https://github.com/element-hq/element-web/pull/29925)). Contributed by @florianduros.
* Stop migrating to MSC4278 if the config exists. ([#29924](https://github.com/element-hq/element-web/pull/29924)). Contributed by @Half-Shot.
* Ensure consistent download file name on download from ImageView ([#29913](https://github.com/element-hq/element-web/pull/29913)). Contributed by @t3chguy.
* Add error toast when service worker registration fails ([#29895](https://github.com/element-hq/element-web/pull/29895)). Contributed by @t3chguy.
* New Room List: Prevent old tombstoned rooms from appearing in the list ([#29881](https://github.com/element-hq/element-web/pull/29881)). Contributed by @MidhunSureshR.
* Remove lag in search field ([#29885](https://github.com/element-hq/element-web/pull/29885)). Contributed by @florianduros.
* Respect UIFeature.Voip ([#29873](https://github.com/element-hq/element-web/pull/29873)). Contributed by @langleyd.
* Allow jumping to message search from spotlight ([#29850](https://github.com/element-hq/element-web/pull/29850)). Contributed by @t3chguy.
Changes in [1.11.100](https://github.com/element-hq/element-web/releases/tag/v1.11.100) (2025-05-06)
====================================================================================================
## ✨ Features
* Move rich topics out of labs / stabilise MSC3765 ([#29817](https://github.com/element-hq/element-web/pull/29817)). Contributed by @Johennes.
* Spell out that Element Web does \*not\* work on mobile. ([#29211](https://github.com/element-hq/element-web/pull/29211)). Contributed by @ara4n.
* Add message preview support to the new room list ([#29784](https://github.com/element-hq/element-web/pull/29784)). Contributed by @dbkr.
* Global configuration flag for media previews ([#29582](https://github.com/element-hq/element-web/pull/29582)). Contributed by @Half-Shot.
* New room list: add partial keyboard shortcuts support ([#29783](https://github.com/element-hq/element-web/pull/29783)). Contributed by @florianduros.
* MVVM RoomSummaryCard Topic ([#29710](https://github.com/element-hq/element-web/pull/29710)). Contributed by @MarcWadai.
* Warn on self change from settings > roles ([#28926](https://github.com/element-hq/element-web/pull/28926)). Contributed by @MarcWadai.
* New room list: new visual for invitation ([#29773](https://github.com/element-hq/element-web/pull/29773)). Contributed by @florianduros.
## 🐛 Bug Fixes
* Fix incorrect display of the user info display name ([#29826](https://github.com/element-hq/element-web/pull/29826)). Contributed by @langleyd.
* RoomListStore: Remove invite rooms on decline ([#29804](https://github.com/element-hq/element-web/pull/29804)). Contributed by @MidhunSureshR.
* Fix the buttons not being displayed with long preview text ([#29811](https://github.com/element-hq/element-web/pull/29811)). Contributed by @dbkr.
* New room list: fix missing/incorrect notification decoration ([#29796](https://github.com/element-hq/element-web/pull/29796)). Contributed by @florianduros.
* New Room List: Prevent potential scroll jump/flicker when switching spaces ([#29781](https://github.com/element-hq/element-web/pull/29781)). Contributed by @MidhunSureshR.
* New room list: fix incorrect decoration ([#29770](https://github.com/element-hq/element-web/pull/29770)). Contributed by @florianduros.
Changes in [1.11.99](https://github.com/element-hq/element-web/releases/tag/v1.11.99) (2025-04-23)
==================================================================================================
No changes, just bumping the version to accommodate a new Element Desktop release
Changes in [1.11.98](https://github.com/element-hq/element-web/releases/tag/v1.11.98) (2025-04-22)
==================================================================================================
## ✨ Features
* print better errors in the search view instead of a blocking modal ([#29724](https://github.com/element-hq/element-web/pull/29724)). Contributed by @Jujure.
* New room list: video room and video call decoration ([#29693](https://github.com/element-hq/element-web/pull/29693)). Contributed by @florianduros.
* Remove Secure Backup, Cross-signing and Cryptography sections in `Security & Privacy` user settings ([#29088](https://github.com/element-hq/element-web/pull/29088)). Contributed by @florianduros.
* Allow reporting a room when rejecting an invite. ([#29570](https://github.com/element-hq/element-web/pull/29570)). Contributed by @Half-Shot.
* RoomListViewModel: Reset primary and secondary filters on space change ([#29672](https://github.com/element-hq/element-web/pull/29672)). Contributed by @MidhunSureshR.
* RoomListStore: Support specific sorting requirements for muted rooms ([#29665](https://github.com/element-hq/element-web/pull/29665)). Contributed by @MidhunSureshR.
* New room list: add notification options menu ([#29639](https://github.com/element-hq/element-web/pull/29639)). Contributed by @florianduros.
* Room List: Scroll to top of the list when active room is not in the list ([#29650](https://github.com/element-hq/element-web/pull/29650)). Contributed by @MidhunSureshR.
## 🐛 Bug Fixes
* Fix unwanted form submit behaviour in memberlist ([#29747](https://github.com/element-hq/element-web/pull/29747)). Contributed by @MidhunSureshR.
* New room list: fix public room icon visibility when filter change ([#29737](https://github.com/element-hq/element-web/pull/29737)). Contributed by @florianduros.
* Fix custom theme support for short hex \& rgba hex strings ([#29726](https://github.com/element-hq/element-web/pull/29726)). Contributed by @t3chguy.
* New room list: minor visual fixes ([#29723](https://github.com/element-hq/element-web/pull/29723)). Contributed by @florianduros.
* Fix getOidcCallbackUrl for Element Desktop ([#29711](https://github.com/element-hq/element-web/pull/29711)). Contributed by @t3chguy.
* Fix some webp images improperly marked as animated ([#29713](https://github.com/element-hq/element-web/pull/29713)). Contributed by @Petersmit27.
* Revert deletion of hydrateSession ([#29703](https://github.com/element-hq/element-web/pull/29703)). Contributed by @Jujure.
* Fix converttoroom \& converttodm not working ([#29705](https://github.com/element-hq/element-web/pull/29705)). Contributed by @t3chguy.
* Ensure forceCloseAllModals also closes priority/static modals ([#29706](https://github.com/element-hq/element-web/pull/29706)). Contributed by @t3chguy.
* Continue button is disabled when uploading a recovery key file ([#29695](https://github.com/element-hq/element-web/pull/29695)). Contributed by @Giwayume.
* Catch errors after syncing recovery ([#29691](https://github.com/element-hq/element-web/pull/29691)). Contributed by @andybalaam.
* New room list: fix multiple visual issues ([#29673](https://github.com/element-hq/element-web/pull/29673)). Contributed by @florianduros.
* New Room List: Fix mentions filter matching rooms with any highlight ([#29668](https://github.com/element-hq/element-web/pull/29668)). Contributed by @MidhunSureshR.
* Fix truncated emoji label during emoji SAS ([#29643](https://github.com/element-hq/element-web/pull/29643)). Contributed by @florianduros.
* Remove duplicate jitsi link ([#29642](https://github.com/element-hq/element-web/pull/29642)). Contributed by @dbkr.
Changes in [1.11.97](https://github.com/element-hq/element-web/releases/tag/v1.11.97) (2025-04-08)
==================================================================================================
## ✨ Features
* New room list: reduce padding between avatar and room list border ([#29634](https://github.com/element-hq/element-web/pull/29634)). Contributed by @florianduros.
* Bundle Element Call with Element Web packages ([#29309](https://github.com/element-hq/element-web/pull/29309)). Contributed by @t3chguy.
* Hide an event notification if it is redacted ([#29605](https://github.com/element-hq/element-web/pull/29605)). Contributed by @Half-Shot.
* Docker: Use nginx-unprivileged as base image ([#29353](https://github.com/element-hq/element-web/pull/29353)). Contributed by @AndrewFerr.
* Switch away from nesting React trees and mangling the DOM ([#29586](https://github.com/element-hq/element-web/pull/29586)). Contributed by @t3chguy.
* New room list: add notification decoration ([#29552](https://github.com/element-hq/element-web/pull/29552)). Contributed by @florianduros.
* RoomListStore: Unread filter should match rooms that were marked as unread ([#29580](https://github.com/element-hq/element-web/pull/29580)). Contributed by @MidhunSureshR.
* Add support for hiding videos ([#29496](https://github.com/element-hq/element-web/pull/29496)). Contributed by @Half-Shot.
* Use an outline icon for the report room button ([#29573](https://github.com/element-hq/element-web/pull/29573)). Contributed by @robintown.
* Generate/load pickle key on SSO ([#29568](https://github.com/element-hq/element-web/pull/29568)). Contributed by @Jujure.
* Add report room dialog button/dialog. ([#29513](https://github.com/element-hq/element-web/pull/29513)). Contributed by @Half-Shot.
* RoomListViewModel: Make the active room sticky in the list ([#29551](https://github.com/element-hq/element-web/pull/29551)). Contributed by @MidhunSureshR.
* Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. ([#29363](https://github.com/element-hq/element-web/pull/29363)). Contributed by @Half-Shot.
* New room list: add selection decoration ([#29531](https://github.com/element-hq/element-web/pull/29531)). Contributed by @florianduros.
* Simplified Sliding Sync ([#28515](https://github.com/element-hq/element-web/pull/28515)). Contributed by @dbkr.
* Add ability to hide images after clicking "show image" ([#29467](https://github.com/element-hq/element-web/pull/29467)). Contributed by @Half-Shot.
## 🐛 Bug Fixes
* Fix scroll issues in memberlist ([#29392](https://github.com/element-hq/element-web/pull/29392)). Contributed by @MidhunSureshR.
* Ensure clicks on spoilers do not get handled by the hidden content ([#29618](https://github.com/element-hq/element-web/pull/29618)). Contributed by @t3chguy.
* New room list: add cursor pointer on room list item ([#29627](https://github.com/element-hq/element-web/pull/29627)). Contributed by @florianduros.
* Fix missing ambiguous url tooltips on Element Desktop ([#29619](https://github.com/element-hq/element-web/pull/29619)). Contributed by @t3chguy.
* New room list: fix spacing and padding ([#29607](https://github.com/element-hq/element-web/pull/29607)). Contributed by @florianduros.
* Make fetchdep check out matching branch name ([#29601](https://github.com/element-hq/element-web/pull/29601)). Contributed by @dbkr.
* Fix MFileBody fileName not considering `filename` ([#29589](https://github.com/element-hq/element-web/pull/29589)). Contributed by @t3chguy.
* Fix token expiry racing with login causing wrong error to be shown ([#29566](https://github.com/element-hq/element-web/pull/29566)). Contributed by @t3chguy.
* Fix bug which caused startup to hang if the clock was wound back since a previous session ([#29558](https://github.com/element-hq/element-web/pull/29558)). Contributed by @richvdh.
* RoomListViewModel: Reset any primary filter on secondary filter change ([#29562](https://github.com/element-hq/element-web/pull/29562)). Contributed by @MidhunSureshR.
* RoomListStore: Unread filter should only filter rooms having unread counts ([#29555](https://github.com/element-hq/element-web/pull/29555)). Contributed by @MidhunSureshR.
* In force-verify mode, prevent bypassing by cancelling device verification ([#29487](https://github.com/element-hq/element-web/pull/29487)). Contributed by @andybalaam.
* Add title attribute to user identifier ([#29547](https://github.com/element-hq/element-web/pull/29547)). Contributed by @arpitbatra123.
Changes in [1.11.96](https://github.com/element-hq/element-web/releases/tag/v1.11.96) (2025-03-25)
==================================================================================================
## ✨ Features
* RoomListViewModel: Track the index of the active room in the list ([#29519](https://github.com/element-hq/element-web/pull/29519)). Contributed by @MidhunSureshR.
* New room list: add empty state ([#29512](https://github.com/element-hq/element-web/pull/29512)). Contributed by @florianduros.
* Implement `MessagePreviewViewModel` ([#29514](https://github.com/element-hq/element-web/pull/29514)). Contributed by @MidhunSureshR.
* RoomListViewModel: Add functionality to toggle message preview setting ([#29511](https://github.com/element-hq/element-web/pull/29511)). Contributed by @MidhunSureshR.
* New room list: add more options menu on room list item ([#29445](https://github.com/element-hq/element-web/pull/29445)). Contributed by @florianduros.
* RoomListViewModel: Provide a way to resort the room list and track the active sort method ([#29499](https://github.com/element-hq/element-web/pull/29499)). Contributed by @MidhunSureshR.
* Change \*All rooms\* meta space name to \*All Chats\* ([#29498](https://github.com/element-hq/element-web/pull/29498)). Contributed by @florianduros.
* Add setting to hide avatars of rooms you have been invited to. ([#29497](https://github.com/element-hq/element-web/pull/29497)). Contributed by @Half-Shot.
* Room List Store: Save preferred sorting algorithm and use that on app launch ([#29493](https://github.com/element-hq/element-web/pull/29493)). Contributed by @MidhunSureshR.
* Add key storage toggle to Encryption settings ([#29310](https://github.com/element-hq/element-web/pull/29310)). Contributed by @dbkr.
* New room list: add primary filters ([#29481](https://github.com/element-hq/element-web/pull/29481)). Contributed by @florianduros.
* Implement MSC4142: Remove unintentional intentional mentions in replies ([#28209](https://github.com/element-hq/element-web/pull/28209)). Contributed by @tulir.
* White background for 'They do not match' button ([#29470](https://github.com/element-hq/element-web/pull/29470)). Contributed by @andybalaam.
* RoomListViewModel: Support secondary filters in the view model ([#29465](https://github.com/element-hq/element-web/pull/29465)). Contributed by @MidhunSureshR.
* RoomListViewModel: Support primary filters in the view model ([#29454](https://github.com/element-hq/element-web/pull/29454)). Contributed by @MidhunSureshR.
* Room List Store: Implement secondary filters ([#29458](https://github.com/element-hq/element-web/pull/29458)). Contributed by @MidhunSureshR.
* Room List Store: Implement rest of the primary filters ([#29444](https://github.com/element-hq/element-web/pull/29444)). Contributed by @MidhunSureshR.
* Room List Store: Support filters by implementing just the favourite filter ([#29433](https://github.com/element-hq/element-web/pull/29433)). Contributed by @MidhunSureshR.
* Move toggle switch for integration manager for a11y ([#29436](https://github.com/element-hq/element-web/pull/29436)). Contributed by @Half-Shot.
* New room list: basic flat list ([#29368](https://github.com/element-hq/element-web/pull/29368)). Contributed by @florianduros.
* Improve rageshake upload experience by providing useful error information ([#29378](https://github.com/element-hq/element-web/pull/29378)). Contributed by @Half-Shot.
* Add more functionality to the room list vm ([#29402](https://github.com/element-hq/element-web/pull/29402)). Contributed by @MidhunSureshR.
## 🐛 Bug Fixes
* New room list: fix compose menu action in space ([#29500](https://github.com/element-hq/element-web/pull/29500)). Contributed by @florianduros.
* Change ToggleHiddenEventVisibility \& GoToHome KeyBindingActions ([#29374](https://github.com/element-hq/element-web/pull/29374)). Contributed by @gy-mate.
* Fix Docker Healthcheck ([#29471](https://github.com/element-hq/element-web/pull/29471)). Contributed by @benbz.
* Room List Store: Fetch rooms after space store is ready + attach store to window ([#29453](https://github.com/element-hq/element-web/pull/29453)). Contributed by @MidhunSureshR.
* Room List Store: Fix bug where left rooms appear in room list ([#29452](https://github.com/element-hq/element-web/pull/29452)). Contributed by @MidhunSureshR.
* Add space to the bottom of the room summary actions below leave room ([#29270](https://github.com/element-hq/element-web/pull/29270)). Contributed by @langleyd.
* Show error screens in group calls ([#29254](https://github.com/element-hq/element-web/pull/29254)). Contributed by @robintown.
* Prevent user from accidentally triggering multiple identity resets ([#29388](https://github.com/element-hq/element-web/pull/29388)). Contributed by @uhoreg.
* Remove buggy tooltip on room intro \& homepage ([#29406](https://github.com/element-hq/element-web/pull/29406)). Contributed by @t3chguy.
Changes in [1.11.95](https://github.com/element-hq/element-web/releases/tag/v1.11.95) (2025-03-11)
==================================================================================================
## ✨ Features

View File

@@ -1,7 +1,7 @@
# syntax=docker.io/docker/dockerfile:1.14-labs
# syntax=docker.io/docker/dockerfile:1.16-labs@sha256:bb5e2b225985193779991f3256d1901a0b3e6a0b284c7bffa0972064f4a6d458
# Builder
FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f16d8e8af67bb6361231e932b8b3e7afa040cbfed181719a450b02c3821b26c1 AS builder
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
ARG USE_CUSTOM_SDKS=false
@@ -19,7 +19,10 @@ RUN /src/scripts/docker-package.sh
RUN cp /src/config.sample.json /src/webapp/config.json
# App
FROM nginx:alpine-slim
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:66e34aa81c2faf290ea4e4c28a490f2b35a07478265a2d5994c8637506045eee
# Need root user to install packages & manipulate the usr directory
USER root
# Install jq and moreutils for sponge, both used by our entrypoints
RUN apk add jq moreutils
@@ -31,13 +34,6 @@ COPY --from=builder /src/webapp /app
COPY /docker/nginx-templates/* /etc/nginx/templates/
COPY /docker/docker-entrypoint.d/* /docker-entrypoint.d/
# Tell nginx to put its pidfile elsewhere, so it can run as non-root
RUN sed -i -e 's,/var/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf
# nginx user must own the cache and etc directory to write cache and tweak the nginx config
RUN chown -R nginx:0 /var/cache/nginx /etc/nginx
RUN chmod -R g+w /var/cache/nginx /etc/nginx
RUN rm -rf /usr/share/nginx/html \
&& ln -s /app /usr/share/nginx/html

View File

@@ -126,7 +126,7 @@ guide](https://classic.yarnpkg.com/en/docs/install) if you do not have it alread
1. Install the prerequisites: `yarn install`.
- If you're using the `develop` branch, then it is recommended to set up a
proper development environment (see [Setting up a dev
environment](#setting-up-a-dev-environment) below). Alternatively, you
environment](./developer_guide.md#setting-up-a-dev-environment) below). Alternatively, you
can use <https://develop.element.io> - the continuous integration release of
the develop branch.
1. Configure the app by copying `config.sample.json` to `config.json` and

View File

@@ -1,13 +0,0 @@
{
"name": "Element",
"description": "A glossy Matrix collaboration client for the web.",
"repository": {
"url": "https://github.com/element-hq/element-web",
"license": "AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial"
},
"bugs": {
"list": "https://github.com/element-hq/element-web/issues",
"report": "https://github.com/element-hq/element-web/issues/new/choose"
},
"keywords": ["chat", "riot", "matrix"]
}

View File

@@ -46,7 +46,6 @@
- [Skinning](skinning.md)
- [Cider editor](ciderEditor.md)
- [Iconography](icons.md)
- [Jitsi](jitsi.md)
- [Local echo](local-echo-dev.md)
- [Media](media-handling.md)
- [Room List Store](room-list-store.md)

View File

@@ -384,8 +384,6 @@ The VoIP and Jitsi options are:
5. `audio_stream_url`: Optional URL to pass to Jitsi to enable live streaming. This option is considered experimental and may be removed
at any time without notice.
6. `element_call`: Optional configuration for native group calls using Element Call, with the following subkeys:
- `url`: The URL of the Element Call instance to use for native group calls. This option is considered experimental
and may be removed at any time without notice. Defaults to `https://call.element.io`.
- `use_exclusively`: A boolean specifying whether Element Call should be used exclusively as the only VoIP stack in
the app, removing the ability to start legacy 1:1 calls or Jitsi calls. Defaults to `false`.
- `participant_limit`: The maximum number of users who can join a call; if

View File

@@ -101,10 +101,6 @@ Under the hood this stops Element Web from adding the `perParticipantE2EE` flag
This is useful while we experiment with encryption and to make calling compatible with platforms that don't use encryption yet.
## Rich text in room topics (`feature_html_topic`) [In Development]
Enables rendering of MD / HTML in room topics.
## Enable the notifications panel in the room header (`feature_notifications`)
Unreliable in encrypted rooms.

View File

@@ -40,6 +40,8 @@ export default {
// Used by webpack
"process",
"util",
// Embedded into webapp
"@element-hq/element-call-embedded",
],
ignoreBinaries: [
// Used in scripts & workflows

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.95",
"version": "1.11.104",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -22,8 +22,7 @@
"LICENSE",
"README.md",
"AUTHORS.rst",
"package.json",
"contribute.json"
"package.json"
],
"style": "bundle.css",
"matrix_i18n_extra_translation_funcs": [
@@ -65,22 +64,24 @@
"test:playwright:screenshots": "playwright-screenshots --project=Chrome",
"coverage": "yarn test --coverage",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
"postinstall": "patch-package"
},
"resolutions": {
"@playwright/test": "1.50.1",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"oidc-client-ts": "3.1.0",
"**/pretty-format/react-is": "19.1.0",
"@playwright/test": "1.52.0",
"@types/react": "19.1.6",
"@types/react-dom": "19.1.6",
"oidc-client-ts": "3.2.1",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001701",
"testcontainers": "10.20.0",
"caniuse-lite": "1.0.30001721",
"testcontainers": "^11.0.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@element-hq/element-web-module-api": "^0.1.1",
"@element-hq/element-web-module-api": "1.2.0",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
@@ -92,8 +93,8 @@
"@types/png-chunks-extract": "^1.0.2",
"@types/react-virtualized": "^9.21.30",
"@vector-im/compound-design-tokens": "^4.0.0",
"@vector-im/compound-web": "^7.7.2",
"@vector-im/matrix-wysiwyg": "2.38.2",
"@vector-im/compound-web": "^8.0.0",
"@vector-im/matrix-wysiwyg": "2.38.3",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
@@ -107,6 +108,7 @@
"css-tree": "^3.0.0",
"diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5",
"domutils": "^3.2.2",
"emojibase-regex": "15.3.2",
"escape-html": "^1.0.3",
"file-saver": "^2.0.5",
@@ -115,15 +117,15 @@
"glob-to-regexp": "^0.4.1",
"highlight.js": "^11.3.1",
"html-entities": "^2.0.0",
"html-react-parser": "^5.2.2",
"is-ip": "^3.1.0",
"js-xxhash": "^4.0.0",
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-element": "4.2.0",
"linkify-react": "4.2.0",
"linkify-string": "4.2.0",
"linkifyjs": "4.2.0",
"linkify-react": "4.3.1",
"linkify-string": "4.3.1",
"linkifyjs": "4.3.1",
"lodash": "^4.17.21",
"maplibre-gl": "^5.0.0",
"matrix-encrypt-attachment": "^1.0.3",
@@ -136,21 +138,22 @@
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.157.2",
"posthog-js": "1.249.4",
"qrcode": "1.5.4",
"re-resizable": "6.11.2",
"react": "^18.3.1",
"react": "^19.0.0",
"react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.3.0",
"react-dom": "^18.3.1",
"react-dom": "^19.0.0",
"react-focus-lock": "^2.5.1",
"react-string-replace": "^1.1.1",
"react-transition-group": "^4.4.1",
"react-virtualized": "^9.22.5",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "2.14.0",
"sanitize-html": "2.17.0",
"tar-js": "^0.3.0",
"temporal-polyfill": "^0.2.5",
"temporal-polyfill": "^0.3.0",
"ua-parser-js": "^1.0.2",
"uuid": "^11.0.0",
"what-input": "^5.2.10"
@@ -177,12 +180,14 @@
"@babel/preset-typescript": "^7.12.7",
"@babel/runtime": "^7.12.5",
"@casualbot/jest-sonar-reporter": "2.2.7",
"@element-hq/element-call-embedded": "0.12.2",
"@element-hq/element-web-playwright-common": "^1.1.5",
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "^1.50.1",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@rrweb/types": "^2.0.0-alpha.18",
"@sentry/webpack-plugin": "^3.0.0",
"@stylistic/eslint-plugin": "^3.0.0",
"@stylistic/eslint-plugin": "^4.0.0",
"@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
@@ -207,11 +212,11 @@
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "18.3.18",
"@types/react": "19.1.6",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "18.3.5",
"@types/react-dom": "19.1.6",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.13.0",
"@types/sanitize-html": "2.16.0",
"@types/semver": "^7.5.8",
"@types/tar-js": "^0.3.5",
"@types/ua-parser-js": "^0.7.36",
@@ -242,7 +247,7 @@
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"express": "^4.18.2",
"express": "^5.0.0",
"fake-indexeddb": "^6.0.0",
"fetch-mock": "9.11.0",
"fetch-mock-jest": "^1.5.1",
@@ -257,12 +262,13 @@
"jest-raw-loader": "^1.0.1",
"jsqr": "^1.4.0",
"knip": "^5.36.2",
"lint-staged": "^15.0.2",
"lint-staged": "^16.0.0",
"matrix-web-i18n": "^3.2.1",
"mini-css-extract-plugin": "2.9.2",
"minimist": "^1.2.6",
"modernizr": "^3.12.0",
"node-fetch": "^2.6.7",
"patch-package": "^8.0.0",
"playwright-core": "^1.51.0",
"postcss": "8.4.46",
"postcss-easings": "^4.0.0",
@@ -274,20 +280,20 @@
"postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1",
"prettier": "3.5.2",
"prettier": "3.5.3",
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.0",
"semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"stylelint": "^16.13.0",
"stylelint-config-standard": "^37.0.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-scss": "^6.0.0",
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9",
"testcontainers": "^10.20.0",
"testcontainers": "^11.0.0",
"ts-node": "^10.9.1",
"typescript": "5.8.2",
"typescript": "5.8.3",
"util": "^0.12.5",
"web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0",
@@ -305,5 +311,6 @@
},
"engines": {
"node": ">=20.0.0"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts b/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
index 917a7fc..a2710c6 100644
--- a/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
+++ b/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
@@ -37,7 +37,7 @@ export interface ModuleApi {
* @returns Whether the user submitted the dialog or closed it, and the model returned by the
* dialog component if submitted.
*/
- openDialog<M extends object, P extends DialogProps = DialogProps, C extends DialogContent<P> = DialogContent<P>>(initialTitleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject<C>) => React.ReactNode, props?: Omit<P, keyof DialogProps>): Promise<{
+ openDialog<M extends object, P extends DialogProps = DialogProps, C extends DialogContent<P> = DialogContent<P>>(initialTitleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject<C | null>) => React.ReactNode, props?: Omit<P, keyof DialogProps>): Promise<{
didOkOrSubmit: boolean;
model: M;
}>;

View File

@@ -0,0 +1,31 @@
diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
index d3318dc..c2b2c77 100644
--- a/node_modules/@types/react/index.d.ts
+++ b/node_modules/@types/react/index.d.ts
@@ -134,7 +134,7 @@ declare namespace React {
props: P,
) => ReactNode | Promise<ReactNode>)
// constructor signature must match React.Component
- | (new(props: P) => Component<any, any>);
+ | (new(props: P, context?: any) => Component<any, any>);
/**
* Created by {@link createRef}, or {@link useRef} when passed `null`.
@@ -945,7 +945,7 @@ declare namespace React {
context: unknown;
// Keep in sync with constructor signature of JSXElementConstructor and ComponentClass.
- constructor(props: P);
+ constructor(props: P, context?: unknown);
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
@@ -1117,7 +1117,7 @@ declare namespace React {
*/
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
// constructor signature must match React.Component
- new(props: P): Component<P, S>;
+ new(props: P, context?: any): Component<P, S>;
/**
* Ignored by React.
* @deprecated Only kept in types for backwards compatibility. Will be removed in a future major release.

View File

@@ -0,0 +1,22 @@
diff --git a/node_modules/react-blurhash/dist/index.d.ts b/node_modules/react-blurhash/dist/index.d.ts
index 3adbd0a..32e8c13 100644
--- a/node_modules/react-blurhash/dist/index.d.ts
+++ b/node_modules/react-blurhash/dist/index.d.ts
@@ -19,7 +19,7 @@ declare class Blurhash extends React.PureComponent<Props$1> {
resolutionY: number;
};
componentDidUpdate(): void;
- render(): JSX.Element;
+ render(): React.JSX.Element;
}
declare type Props = React.CanvasHTMLAttributes<HTMLCanvasElement> & {
@@ -37,7 +37,7 @@ declare class BlurhashCanvas extends React.PureComponent<Props> {
componentDidUpdate(): void;
handleRef: (canvas: HTMLCanvasElement) => void;
draw: () => void;
- render(): JSX.Element;
+ render(): React.JSX.Element;
}
export { Blurhash, BlurhashCanvas };

View File

@@ -23,7 +23,13 @@ test.describe("Encryption state after registration", () => {
test("Key backup is enabled by default", async ({ page, mailpitClient, app }, testInfo) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(
page,
mailpitClient,
`alice_${testInfo.testId}`,
`alice_${testInfo.testId}@email.com`,
"Pa$sW0rD!",
);
// Wait for the ui to load
await expect(page.locator(".mx_MatrixChat")).toBeVisible();
@@ -35,7 +41,13 @@ test.describe("Encryption state after registration", () => {
test("user is prompted to set up recovery", async ({ page, mailpitClient, app }, testInfo) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(
page,
mailpitClient,
`alice_${testInfo.testId}`,
`alice_${testInfo.testId}@email.com`,
"Pa$sW0rD!",
);
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
@@ -64,7 +76,7 @@ test.describe("Key backup reset from elsewhere", () => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailpitClient, testUsername, "alice@email.com", testPassword);
await registerAccountMas(page, mailpitClient, testUsername, `${testUsername}@email.com`, testPassword);
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();

View File

@@ -1,108 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
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 { test, expect } from "../../element-web-test";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { completeCreateSecretStorageDialog } from "./utils.ts";
async function expectBackupVersionToBe(page: Page, version: string) {
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
version + " (Algorithm: m.megolm_backup.v1.curve25519-aes-sha2)",
);
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version);
}
test.describe("Backups", () => {
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
test.use({
displayName: "Hanako",
});
test(
"Create, delete and recreate a keys backup",
{ tag: "@no-webkit" },
async ({ page, user, app }, workerInfo) => {
// Create a backup
const securityTab = await app.settings.openUserSettings("Security & Privacy");
await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
const securityKey = await completeCreateSecretStorageDialog(page);
// Open the settings again
await app.settings.openUserSettings("Security & Privacy");
await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
// expand the advanced section to see the active version in the reports
await page
.locator(".mx_Dialog .mx_SettingsSubsection_content details .mx_SecureBackupPanel_advanced")
.locator("..")
.click();
await expectBackupVersionToBe(page, "1");
await securityTab.getByRole("button", { name: "Delete Backup", exact: true }).click();
const currentDialogLocator = page.locator(".mx_Dialog");
await expect(currentDialogLocator.getByRole("heading", { name: "Delete Backup" })).toBeVisible();
// Delete it
await currentDialogLocator.getByTestId("dialog-primary-button").click(); // Click "Delete Backup"
// Create another
await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
await expect(currentDialogLocator.getByRole("heading", { name: "Recovery Key" })).toBeVisible();
await currentDialogLocator.getByLabel("Recovery Key").fill(securityKey);
await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
// Should be successful
await expect(currentDialogLocator.getByRole("heading", { name: "Success!" })).toBeVisible();
await currentDialogLocator.getByRole("button", { name: "OK", exact: true }).click();
// Open the settings again
await app.settings.openUserSettings("Security & Privacy");
await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
// expand the advanced section to see the active version in the reports
await page
.locator(".mx_Dialog .mx_SettingsSubsection_content details .mx_SecureBackupPanel_advanced")
.locator("..")
.click();
await expectBackupVersionToBe(page, "2");
// ==
// Ensure that if you don't have the secret storage passphrase the backup won't be created
// ==
// First delete version 2
await securityTab.getByRole("button", { name: "Delete Backup", exact: true }).click();
await expect(currentDialogLocator.getByRole("heading", { name: "Delete Backup" })).toBeVisible();
// Click "Delete Backup"
await currentDialogLocator.getByTestId("dialog-primary-button").click();
// Try to create another
await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
await expect(currentDialogLocator.getByRole("heading", { name: "Recovery Key" })).toBeVisible();
// But cancel the recovery key dialog, to simulate not having the secret storage passphrase
await currentDialogLocator.getByTestId("dialog-cancel-button").click();
await expect(currentDialogLocator.getByRole("heading", { name: "Starting backup…" })).toBeVisible();
// check that it failed
await expect(currentDialogLocator.getByText("Unable to create key backup")).toBeVisible();
// cancel
await currentDialogLocator.getByTestId("dialog-cancel-button").click();
// go back to the settings to check that no backup was created (the setup button should still be there)
await app.settings.openUserSettings("Security & Privacy");
await expect(securityTab.getByRole("button", { name: "Set up", exact: true })).toBeVisible();
},
);
});

View File

@@ -8,14 +8,7 @@ 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 {
autoJoin,
completeCreateSecretStorageDialog,
copyAndContinue,
createSharedRoomWithUser,
enableKeyBackup,
verify,
} from "./utils";
import { autoJoin, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils";
import { type Bot } from "../../pages/bot";
import { type ElementAppPage } from "../../pages/ElementAppPage";
import { isDendrite } from "../../plugins/homeserver/dendrite";
@@ -84,85 +77,43 @@ test.describe("Cryptography", function () {
},
});
for (const isDeviceVerified of [true, false]) {
test.describe(`setting up secure key backup should work isDeviceVerified=${isDeviceVerified}`, () => {
/**
* Verify that the `m.cross_signing.${keyType}` key is available on the account data on the server
* @param keyType
*/
async function verifyKey(app: ElementAppPage, keyType: "master" | "self_signing" | "user_signing") {
const accountData: { encrypted: Record<string, Record<string, string>> } = await app.client.evaluate(
(cli, keyType) => cli.getAccountDataFromServer(`m.cross_signing.${keyType}`),
keyType,
);
expect(accountData.encrypted).toBeDefined();
const keys = Object.keys(accountData.encrypted);
const key = accountData.encrypted[keys[0]];
expect(key.ciphertext).toBeDefined();
expect(key.iv).toBeDefined();
expect(key.mac).toBeDefined();
}
/**
* Verify that the `m.cross_signing.${keyType}` key is available on the account data on the server
* @param keyType
*/
async function verifyKey(app: ElementAppPage, keyType: "master" | "self_signing" | "user_signing") {
const accountData: { encrypted: Record<string, Record<string, string>> } = await app.client.evaluate(
(cli, keyType) => cli.getAccountDataFromServer(`m.cross_signing.${keyType}`),
keyType,
);
test("by recovery code", async ({ page, app, user: aliceCredentials }) => {
// Verified the device
if (isDeviceVerified) {
await app.client.bootstrapCrossSigning(aliceCredentials);
}
await page.route("**/_matrix/client/v3/keys/signatures/upload", async (route) => {
// We delay this API otherwise the `Setting up keys` may happen too quickly and cause flakiness
await new Promise((resolve) => setTimeout(resolve, 500));
await route.continue();
});
await app.settings.openUserSettings("Security & Privacy");
await page.getByRole("button", { name: "Set up Secure Backup" }).click();
await completeCreateSecretStorageDialog(page);
// Verify that the SSSS keys are in the account data stored in the server
await verifyKey(app, "master");
await verifyKey(app, "self_signing");
await verifyKey(app, "user_signing");
});
test("by passphrase", async ({ page, app, user: aliceCredentials }) => {
// Verified the device
if (isDeviceVerified) {
await app.client.bootstrapCrossSigning(aliceCredentials);
}
await app.settings.openUserSettings("Security & Privacy");
await page.getByRole("button", { name: "Set up Secure Backup" }).click();
const dialog = page.locator(".mx_Dialog");
// Select passphrase option
await dialog.getByText("Enter a Security Phrase").click();
await dialog.getByRole("button", { name: "Continue" }).click();
// Fill passphrase input
await dialog.locator("input").fill("new passphrase for setting up a secure key backup");
await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
// Confirm passphrase
await dialog.locator("input").fill("new passphrase for setting up a secure key backup");
await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
await copyAndContinue(page);
await expect(dialog.getByText("Secure Backup successful")).toBeVisible();
await dialog.getByRole("button", { name: "Done" }).click();
await expect(dialog.getByText("Secure Backup successful")).not.toBeVisible();
// Verify that the SSSS keys are in the account data stored in the server
await verifyKey(app, "master");
await verifyKey(app, "self_signing");
await verifyKey(app, "user_signing");
});
});
expect(accountData.encrypted).toBeDefined();
const keys = Object.keys(accountData.encrypted);
const key = accountData.encrypted[keys[0]];
expect(key.ciphertext).toBeDefined();
expect(key.iv).toBeDefined();
expect(key.mac).toBeDefined();
}
test("Setting up key backup by recovery key", async ({ page, app, user: aliceCredentials }) => {
await app.client.bootstrapCrossSigning(aliceCredentials);
await enableKeyBackup(app);
// Wait for the cross signing keys to be uploaded
// Waiting for "Change the recovery key" button ensure that all the secrets are uploaded and cached locally
const encryptionTab = await app.settings.openUserSettings("Encryption");
await expect(encryptionTab.getByRole("button", { name: "Change recovery key" })).toBeVisible();
// Verify that the SSSS keys are in the account data stored in the server
await verifyKey(app, "master");
await verifyKey(app, "self_signing");
await verifyKey(app, "user_signing");
});
test("Can reset cross-signing keys", async ({ page, app, user: aliceCredentials }) => {
const secretStorageKey = await enableKeyBackup(app);
await app.client.bootstrapCrossSigning(aliceCredentials);
await enableKeyBackup(app);
// Fetch the current cross-signing keys
async function fetchMasterKey() {
@@ -176,18 +127,15 @@ test.describe("Cryptography", function () {
return k;
});
}
const masterKey1 = await fetchMasterKey();
// Find the "reset cross signing" button, and click it
await app.settings.openUserSettings("Security & Privacy");
await page.locator("div.mx_CrossSigningPanel_buttonRow").getByRole("button", { name: "Reset" }).click();
// Find "the Reset cryptographic identity" button
const encryptionTab = await app.settings.openUserSettings("Encryption");
await encryptionTab.getByRole("button", { name: "Reset cryptographic identity" }).click();
// Confirm
await page.getByRole("button", { name: "Clear cross-signing keys" }).click();
// Enter the 4S key
await page.getByPlaceholder("Recovery Key").fill(secretStorageKey);
await page.getByRole("button", { name: "Continue" }).click();
await encryptionTab.getByRole("button", { name: "Continue" }).click();
// Enter the password
await page.getByPlaceholder("Password").fill(aliceCredentials.password);
@@ -197,9 +145,6 @@ test.describe("Cryptography", function () {
const masterKey2 = await fetchMasterKey();
expect(masterKey1).not.toEqual(masterKey2);
}).toPass();
// The dialog should have gone away
await expect(page.locator(".mx_Dialog")).toHaveCount(1);
});
test(

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { test, expect } from "../../element-web-test";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { completeCreateSecretStorageDialog, createBot, logIntoElement } from "./utils.ts";
import { createBot, logIntoElement } from "./utils.ts";
import { type Client } from "../../pages/client.ts";
import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
@@ -27,16 +27,28 @@ test.use({
test.describe("Dehydration", () => {
test.skip(isDendrite, "does not yet support dehydration v2");
test("'Set up secure backup' creates dehydrated device", async ({ page, user, app }, workerInfo) => {
// Create a backup (which will create SSSS, and dehydrated device)
test("Verify device and reset creates dehydrated device", async ({ page, user, credentials, app }, workerInfo) => {
// Verify the device by resetting the identity key, and then set up recovery (which will create SSSS, and dehydrated device)
const securityTab = await app.settings.openUserSettings("Security & Privacy");
await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
await expect(securityTab.getByText("Offline device enabled")).not.toBeVisible();
await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
await completeCreateSecretStorageDialog(page);
await app.closeDialog();
// Reset the identity key
const settings = await app.settings.openUserSettings("Encryption");
await settings.getByRole("button", { name: "Verify this device" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// Set up recovery
await page.getByRole("button", { name: "Set up recovery" }).click();
await page.getByRole("button", { name: "Continue" }).click();
const recoveryKey = await page.getByTestId("recoveryKey").innerText();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("textbox").fill(recoveryKey);
await page.getByRole("button", { name: "Finish set up" }).click();
await page.getByRole("button", { name: "Close" }).click();
await expectDehydratedDeviceEnabled(app);
@@ -74,7 +86,7 @@ test.describe("Dehydration", () => {
await expectDehydratedDeviceEnabled(app);
});
test("Reset recovery key during login re-creates dehydrated device", async ({
test("Reset identity during login and set up recovery re-creates dehydrated device", async ({
page,
homeserver,
app,
@@ -93,16 +105,26 @@ test.describe("Dehydration", () => {
// Log in our client
await logIntoElement(page, credentials);
// Oh no, we forgot our recovery key
// Oh no, we forgot our recovery key - reset our identity
await page.locator(".mx_AuthPage").getByRole("button", { name: "Reset all" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "Proceed with reset" }).click();
await expect(
page.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
).toBeVisible();
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByPlaceholder("Password").fill(credentials.password);
await page.getByRole("button", { name: "Continue" }).click();
await completeCreateSecretStorageDialog(page, { accountPassword: credentials.password });
// And set up recovery
const settings = await app.settings.openUserSettings("Encryption");
await settings.getByRole("button", { name: "Set up recovery" }).click();
await settings.getByRole("button", { name: "Continue" }).click();
const recoveryKey = await settings.getByTestId("recoveryKey").innerText();
await settings.getByRole("button", { name: "Continue" }).click();
await settings.getByRole("textbox").fill(recoveryKey);
await settings.getByRole("button", { name: "Finish set up" }).click();
// There should be a brand new dehydrated device
const dehydratedDeviceIds = await getDehydratedDeviceIds(app.client);
expect(dehydratedDeviceIds.length).toBe(1);
expect(dehydratedDeviceIds[0]).not.toEqual(initialDehydratedDeviceIds[0]);
await expectDehydratedDeviceEnabled(app);
});
test("'Reset cryptographic identity' removes dehydrated device", async ({ page, homeserver, app, credentials }) => {

View File

@@ -22,6 +22,7 @@ import {
} from "./utils";
import { type Bot } from "../../pages/bot";
import { Toasts } from "../../pages/toasts.ts";
import type { ElementAppPage } from "../../pages/ElementAppPage.ts";
test.describe("Device verification", { tag: "@no-webkit" }, () => {
let aliceBotClient: Bot;
@@ -163,39 +164,44 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => {
await logIntoElement(page, credentials);
// Select the security phrase
await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
// Fill the passphrase
const dialog = page.locator(".mx_Dialog");
await dialog.locator("input").fill("new passphrase");
await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click();
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase");
});
test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => {
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey;
await logIntoElement(page, credentials);
await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey);
});
test("Verify device with Recovery Key from settings", async ({ page, app, credentials }) => {
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey;
await logIntoElement(page, credentials);
// Select the security phrase
await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
/* Dismiss "Verify this device" */
const authPage = page.locator(".mx_AuthPage");
await authPage.getByRole("button", { name: "Skip verification for now" }).click();
await authPage.getByRole("button", { name: "I'll verify later" }).click();
await page.waitForSelector(".mx_MatrixChat");
// Fill the recovery key
const settings = await app.settings.openUserSettings("Encryption");
await settings.getByRole("button", { name: "Verify this device" }).click();
await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey);
});
/** Helper for the three tests above which verify by recovery key */
async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) {
await page.getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
// Enter the recovery key
const dialog = page.locator(".mx_Dialog");
await dialog.getByRole("button", { name: "use your Recovery Key" }).click();
const aliceRecoveryKey = await aliceBotClient.getRecoveryKey();
await dialog.locator("#mx_securityKey").fill(aliceRecoveryKey.encodedPrivateKey);
await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
// (cf https://github.com/element-hq/element-web/issues/30089)
await dialog.locator("textarea").pressSequentially(recoveryKey);
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click();
await page.getByRole("button", { name: "Done" }).click();
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
@@ -203,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
});
}
test("Handle incoming verification request with SAS", async ({ page, credentials, homeserver, toasts }) => {
await logIntoElement(page, credentials);

View File

@@ -67,8 +67,9 @@ test.describe("Cryptography", function () {
// Bob has a second, not cross-signed, device
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
// Dismiss the toast nagging us to set up recovery otherwise it gets in the way of clicking the room list
await page.getByRole("button", { name: "Not now" }).click();
// Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
await page.getByRole("button", { name: "Dismiss" }).click();
await page.getByRole("button", { name: "Yes, dismiss" }).click();
await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
algorithm: "m.megolm.v1.aes-sha2",
@@ -324,7 +325,7 @@ test.describe("Cryptography", function () {
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Sender's verified identity has changed",
"Sender's verified identity was reset",
);
});
});

View File

@@ -52,6 +52,6 @@ test.describe("Invisible cryptography", () => {
/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const lastTile = page.locator(".mx_EventTile_last");
await expect(lastTile).toContainText("Sender's verified identity has changed");
await expect(lastTile).toContainText("Sender's verified identity was reset");
});
});

View File

@@ -8,7 +8,8 @@
import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
import { test, expect } from "../../element-web-test";
import { createBot, deleteCachedSecrets, logIntoElement } from "./utils";
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement } from "./utils";
import { type Bot } from "../../pages/bot";
test.describe("Key storage out of sync toast", () => {
let recoveryKey: GeneratedSecretStorageKey;
@@ -29,7 +30,9 @@ test.describe("Key storage out of sync toast", () => {
});
test("should prompt for recovery key if 'enter recovery key' pressed", { tag: "@screenshot" }, async ({ page }) => {
// Need to wait for 2 to appear since playwright only evaluates 'first()' initially, so the waiting won't work
// We need to wait for there to be two toasts as the wait below won't work in isolation:
// playwright only evaluates the 'first()' call initially, not subsequent times it checks, so
// it would always be checking the same toast, even if another one is now the first.
await expect(page.getByRole("alert")).toHaveCount(2);
await expect(page.getByRole("alert").first()).toMatchScreenshot("key-storage-out-of-sync-toast.png");
@@ -51,3 +54,114 @@ test.describe("Key storage out of sync toast", () => {
).toBeVisible();
});
});
test.describe("'Turn on key storage' toast", () => {
let botClient: Bot | undefined;
test.beforeEach(async ({ page, homeserver, credentials, toasts }) => {
// Set up all crypto stuff. Key storage defaults to on.
const res = await createBot(page, homeserver, credentials);
const recoveryKey = res.recoveryKey;
botClient = res.botClient;
await logIntoElement(page, credentials, recoveryKey.encodedPrivateKey);
// We won't be prompted for crypto setup unless we have an e2e room, so make one
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("Test room");
await page.getByRole("button", { name: "Create room" }).click();
await toasts.rejectToast("Notifications");
});
test("should not show toast if key storage is on", async ({ page, toasts }) => {
// Given the default situation after signing in
// Then no toast is shown (because key storage is on)
await toasts.assertNoToasts();
// When we reload
await page.reload();
// Give the toasts time to appear
await new Promise((resolve) => setTimeout(resolve, 2000));
// Then still no toast is shown
await toasts.assertNoToasts();
});
test("should not show toast if key storage is off because we turned it off", async ({ app, page, toasts }) => {
// Given the backup is disabled because we disabled it
await disableKeyBackup(app);
// Then no toast is shown
await toasts.assertNoToasts();
// When we reload
await page.reload();
// Give the toasts time to appear
await new Promise((resolve) => setTimeout(resolve, 2000));
// Then still no toast is shown
await toasts.assertNoToasts();
});
test("should show toast if key storage is off but account data is missing", async ({ app, page, toasts }) => {
// Given the backup is disabled but we didn't set account data saying that is expected
await disableKeyBackup(app);
await botClient.setAccountData("m.org.matrix.custom.backup_disabled", { disabled: false });
// Wait for the account data setting to stick
await new Promise((resolve) => setTimeout(resolve, 2000));
// When we enter the app
await page.reload();
// Then the toast is displayed
let toast = await toasts.getToast("Turn on key storage");
// And when we click "Continue"
await toast.getByRole("button", { name: "Continue" }).click();
// Then we see the Encryption settings dialog with an option to turn on key storage
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
// And when we close that
await page.getByRole("button", { name: "Close dialog" }).click();
// Then we see the toast again
toast = await toasts.getToast("Turn on key storage");
// And when we click "Dismiss"
await toast.getByRole("button", { name: "Dismiss" }).click();
// Then we see the "are you sure?" dialog
await expect(
page.getByRole("heading", { name: "Are you sure you want to keep key storage turned off?" }),
).toBeVisible();
// And when we close it by clicking away
await page.getByTestId("dialog-background").click({ force: true, position: { x: 10, y: 10 } });
// Then we see the toast again
toast = await toasts.getToast("Turn on key storage");
// And when we click Dismiss and then "Go to Settings"
await toast.getByRole("button", { name: "Dismiss" }).click();
await page.getByRole("button", { name: "Go to Settings" }).click();
// Then we see Encryption settings again
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
// And when we close that, see the toast, click Dismiss, and Yes, Dismiss
await page.getByRole("button", { name: "Close dialog" }).click();
toast = await toasts.getToast("Turn on key storage");
await toast.getByRole("button", { name: "Dismiss" }).click();
await page.getByRole("button", { name: "Yes, dismiss" }).click();
// Then the toast is gone
await toasts.assertNoToasts();
});
});

View File

@@ -221,12 +221,15 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key" }).click();
const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "use your Recovery Key" });
// If the user has set a recovery *passphrase*, they'll be prompted for that first and have to click
// through to enter the recovery key which is what we have here. If they haven't, they'll be prompted
// for a recovery key straight away. We click the button if it's there so this works in both cases.
if (await useSecurityKey.isVisible()) {
await useSecurityKey.click();
}
// Fill in the recovery key
await page.locator(".mx_Dialog").locator('input[type="password"]').fill(securityKey);
await page.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
await page.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click();
}
}
@@ -260,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) {
const settings = await app.settings.openUserSettings("Encryption");
await settings.getByRole("button", { name: "Verify this device" }).click();
await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
await app.page.locator(".mx_Dialog").locator('input[type="password"]').fill(securityKey);
await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
await app.page.getByRole("button", { name: "Done" }).click();
await app.settings.closeDialog();
@@ -289,17 +292,47 @@ export async function doTwoWaySasVerification(page: Page, verifier: JSHandle<Ver
}
/**
* Open the security settings and enable secure key backup.
*
* Assumes that the current device has been cross-signed (which means that we skip a step where we set it up).
* Open the encryption settings and enable key storage and recovery
* Assumes that the current device has been verified
*
* Returns the recovery key
*/
export async function enableKeyBackup(app: ElementAppPage): Promise<string> {
await app.settings.openUserSettings("Security & Privacy");
await app.page.getByRole("button", { name: "Set up Secure Backup" }).click();
const encryptionTab = await app.settings.openUserSettings("Encryption");
return await completeCreateSecretStorageDialog(app.page);
const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
if (!(await keyStorageToggle.isChecked())) {
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
}
await encryptionTab.getByRole("button", { name: "Set up recovery" }).click();
await encryptionTab.getByRole("button", { name: "Continue" }).click();
const recoveryKey = await encryptionTab.getByTestId("recoveryKey").innerText();
await encryptionTab.getByRole("button", { name: "Continue" }).click();
await encryptionTab.getByRole("textbox").fill(recoveryKey);
await encryptionTab.getByRole("button", { name: "Finish set up" }).click();
await app.settings.closeDialog();
return recoveryKey;
}
/**
* Open the encryption settings and disable key storage (and recovery)
* Assumes that the current device has been verified
*/
export async function disableKeyBackup(app: ElementAppPage): Promise<void> {
const encryptionTab = await app.settings.openUserSettings("Encryption");
const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
if (await keyStorageToggle.isChecked()) {
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
await encryptionTab.getByRole("button", { name: "Delete key storage" }).click();
await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).isVisible();
// Wait for the update to account data to stick
await new Promise((resolve) => setTimeout(resolve, 2000));
}
await app.settings.closeDialog();
}
/**

View File

@@ -5,8 +5,11 @@
* Please see LICENSE files in the repository root for full details.
*/
import { type Visibility } from "matrix-js-sdk/src/matrix";
import { type Locator, type Page } from "@playwright/test";
import { expect, test } from "../../../element-web-test";
import type { Page } from "@playwright/test";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
test.describe("Room list filters and sort", () => {
test.use({
@@ -18,6 +21,22 @@ 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
@@ -26,63 +45,330 @@ test.describe("Room list filters and sort", () => {
return page.getByTestId("room-list");
}
function getPrimaryFilters(page: Page) {
return page.getByRole("listbox", { name: "Room list filters" });
}
test.beforeEach(async ({ page, app, bot, user }) => {
// The notification toast is displayed above the search section
await app.closeNotificationToast();
await app.client.createRoom({ name: "empty room" });
const unReadDmId = await bot.createRoom({
name: "unread dm",
invite: [user.userId],
is_direct: true,
});
await bot.sendMessage(unReadDmId, "I am a robot. Beep.");
const unReadRoomId = await app.client.createRoom({ name: "unread room" });
await app.client.inviteUser(unReadRoomId, bot.credentials.userId);
await bot.joinRoom(unReadRoomId);
await bot.sendMessage(unReadRoomId, "I am a robot. Beep.");
const favouriteId = await app.client.createRoom({ name: "favourite room" });
await app.client.evaluate(async (client, favouriteId) => {
await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
}, favouriteId);
});
test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomList = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);
test("Tombstoned rooms are not shown even when they receive updates", async ({ page, app, bot }) => {
// This bug shows up with this setting turned on
await app.settings.setValue("Spaces.allRoomsInHome", null, SettingLevel.DEVICE, true);
const allFilters = await primaryFilters.locator("option").all();
for (const filter of allFilters) {
expect(await filter.getAttribute("aria-selected")).toBe("false");
/*
We will first create a room named 'Old Room' and will invite the bot user to this room.
We will also send a simple message in this room.
*/
const oldRoomId = await app.client.createRoom({ name: "Old Room" });
await app.client.inviteUser(oldRoomId, bot.credentials.userId);
await bot.joinRoom(oldRoomId);
const response = await app.client.sendMessage(oldRoomId, "Hello!");
/*
At this point, we haven't done anything interesting.
So we expect 'Old Room' to show up in the room list.
*/
const roomListView = getRoomList(page);
const oldRoomTile = roomListView.getByRole("gridcell", { name: "Open room Old Room" });
await expect(oldRoomTile).toBeVisible();
/*
Now let's tombstone 'Old Room'.
First we create a new room ('New Room') with the predecessor set to the old room..
*/
const newRoomId = await bot.createRoom({
name: "New Room",
creation_content: {
predecessor: {
event_id: response.event_id,
room_id: oldRoomId,
},
},
visibility: "public" as Visibility,
});
/*
Now we can send the tombstone event itself to the 'Old Room'.
*/
await app.client.sendStateEvent(oldRoomId, "m.room.tombstone", {
body: "This room has been replaced",
replacement_room: newRoomId,
});
// Let's join the replaced room.
await app.client.joinRoom(newRoomId);
// We expect 'Old Room' to be hidden from the room list.
await expect(oldRoomTile).not.toBeVisible();
/*
Let's say some user in the 'Old Room' changes their display name.
This will send events to the all the rooms including 'Old Room'.
Nevertheless, the replaced room should not be shown in the room list.
*/
await bot.setDisplayName("MyNewName");
await expect(oldRoomTile).not.toBeVisible();
});
test.describe("Scroll behaviour", () => {
test("should scroll to the top of list when filter is applied and active room is not in filtered list", async ({
page,
app,
}) => {
const createFavouriteRoom = async (name: string) => {
const id = await app.client.createRoom({
name,
});
await app.client.evaluate(async (client, favouriteId) => {
await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
}, id);
};
// Create 5 favourite rooms
let i = 0;
for (; i < 5; i++) {
await createFavouriteRoom(`room${i}-fav`);
}
// Create a non-favourite room
await app.client.createRoom({ name: `room-non-fav` });
// Create rest of the favourite rooms
for (; i < 20; i++) {
await createFavouriteRoom(`room${i}-fav`);
}
// Open the non-favourite room
const roomListView = getRoomList(page);
const tile = roomListView.getByRole("gridcell", { name: "Open room room-non-fav" });
await tile.scrollIntoViewIfNeeded();
await tile.click();
// Enable Favourite filter
await getFilterExpandButton(page).click();
const primaryFilters = getPrimaryFilters(page);
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
await expect(tile).not.toBeVisible();
// Ensure the room list is not scrolled
const isScrolledDown = await page
.getByRole("grid", { name: "Room list" })
.evaluate((e) => e.scrollTop !== 0);
expect(isScrolledDown).toStrictEqual(false);
});
});
test.describe("Room list", () => {
let unReadDmId: string | undefined;
let unReadRoomId: string | undefined;
test.beforeEach(async ({ page, app, bot, user }) => {
await app.client.createRoom({ name: "empty room" });
unReadDmId = await bot.createRoom({
name: "unread dm",
invite: [user.userId],
is_direct: true,
});
await app.client.joinRoom(unReadDmId);
await bot.sendMessage(unReadDmId, "I am a robot. Beep.");
unReadRoomId = await app.client.createRoom({ name: "unread room" });
await app.client.inviteUser(unReadRoomId, bot.credentials.userId);
await bot.joinRoom(unReadRoomId);
await bot.sendMessage(unReadRoomId, "I am a robot. Beep.");
const favouriteId = await app.client.createRoom({ name: "favourite room" });
await app.client.evaluate(async (client, favouriteId) => {
await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
}, favouriteId);
const lowPrioId = await app.client.createRoom({ name: "Low prio room" });
await app.client.evaluate(async (client, id) => {
await client.setRoomTag(id, "m.lowpriority", { order: 0.5 });
}, lowPrioId);
await bot.createRoom({
name: "invited room",
invite: [user.userId],
is_direct: true,
});
const mentionRoomId = await app.client.createRoom({ name: "room with mention" });
await app.client.inviteUser(mentionRoomId, bot.credentials.userId);
await bot.joinRoom(mentionRoomId);
const clientBot = await bot.prepareClient();
await clientBot.evaluate(
async (client, { mentionRoomId, userId }) => {
await client.sendMessage(mentionRoomId, {
// @ts-ignore ignore usage of MsgType.text
"msgtype": "m.text",
"body": "User",
"format": "org.matrix.custom.html",
"formatted_body": `<a href="https://matrix.to/#/${userId}">User</a>`,
"m.mentions": {
user_ids: [userId],
},
});
},
{ mentionRoomId, userId: user.userId },
);
});
test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomList = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);
const allFilters = await primaryFilters.locator("option").all();
for (const filter of allFilters) {
expect(await filter.getAttribute("aria-selected")).toBe("false");
}
await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png");
await primaryFilters.getByRole("option", { name: "Unread" }).click();
// only one room should be visible
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(4);
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(2);
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(5);
await getFilterExpandButton(page).click();
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "Mentions" }).click();
await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "Invites" }).click();
await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await getFilterCollapseButton(page).click();
await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites");
});
test(
"unread filter should only match unread rooms that have a count",
{ tag: "@screenshot" },
async ({ page, app, bot }) => {
const roomListView = getRoomList(page);
// Let's configure unread dm room so that we only get notification for mentions and keywords
await app.viewRoomById(unReadDmId);
await app.settings.openRoomSettings("Notifications");
await page.getByText("@mentions & keywords").click();
await app.settings.closeDialog();
// Let's open a room other than unread room or unread dm
await roomListView.getByRole("gridcell", { name: "Open room favourite room" }).click();
// Let's make the bot send a new message in both rooms
await bot.sendMessage(unReadDmId, "Hello!");
await bot.sendMessage(unReadRoomId, "Hello!");
// Let's activate the unread filter now
await page.getByRole("option", { name: "Unread" }).click();
// Unread filter should only show unread room and not unread dm!
const unreadDm = roomListView.getByRole("gridcell", { name: "Open room unread room" });
await expect(unreadDm).toBeVisible();
await expect(unreadDm).toMatchScreenshot("unread-dm.png");
await expect(roomListView.getByRole("gridcell", { name: "Open room unread dm" })).not.toBeVisible();
},
);
test("should sort the room list alphabetically", async ({ page }) => {
const roomListView = getRoomList(page);
await getRoomOptionsMenu(page).click();
await page.getByRole("menuitemradio", { name: "A-Z" }).click();
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/empty room/);
});
test("should move room to the top on message when sorting by activity", async ({ page, bot }) => {
const roomListView = getRoomList(page);
await bot.sendMessage(unReadDmId, "Hello!");
await expect(roomListView.getByRole("gridcell").first()).toHaveText(/unread dm/);
});
});
test.describe("Empty room list", () => {
/**
* Get the empty state
* @param page
*/
function getEmptyRoomList(page: Page) {
return page.getByTestId("empty-room-list");
}
await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png");
await primaryFilters.getByRole("option", { name: "Unread" }).click();
// only one room should be visible
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(2);
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
test(
"should render the default placeholder when there is no filter",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
const emptyRoomList = getEmptyRoomList(page);
await expect(emptyRoomList).toMatchScreenshot("default-empty-room-list.png");
await expect(page.getByRole("navigation", { name: "Room list" })).toMatchScreenshot(
"room-panel-empty-room-list.png",
);
},
);
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
[
{ filter: "Unreads", action: "Show all chats" },
{ filter: "Mentions", action: "See all activity" },
{ filter: "Invites", action: "See all activity" },
].forEach(({ filter, action }) => {
test(
`should render the placeholder for ${filter} filter`,
{ tag: "@screenshot" },
async ({ page, app, user }) => {
const primaryFilters = getPrimaryFilters(page);
await getFilterExpandButton(page).click();
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await primaryFilters.getByRole("option", { name: filter }).click();
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(3);
const emptyRoomList = getEmptyRoomList(page);
await expect(emptyRoomList).toMatchScreenshot(`${filter}-empty-room-list.png`);
await emptyRoomList.getByRole("button", { name: action }).click();
await expect(primaryFilters.getByRole("option", { name: filter })).not.toBeChecked();
},
);
});
["People", "Rooms", "Favourite"].forEach((filter) => {
test(
`should render the placeholder for ${filter} filter`,
{ tag: "@screenshot" },
async ({ page, app, user }) => {
const primaryFilters = getPrimaryFilters(page);
await getFilterExpandButton(page).click();
await primaryFilters.getByRole("option", { name: filter }).click();
const emptyRoomList = getEmptyRoomList(page);
await expect(emptyRoomList).toMatchScreenshot(`${filter}-empty-room-list.png`);
},
);
});
});
});

View File

@@ -19,7 +19,7 @@ test.describe("Room list panel", () => {
* @param page
*/
function getRoomListView(page: Page) {
return page.getByTestId("room-list-panel");
return page.getByRole("navigation", { name: "Room list" });
}
test.beforeEach(async ({ page, app, user }) => {
@@ -30,6 +30,9 @@ test.describe("Room list panel", () => {
for (let i = 0; i < 20; i++) {
await app.client.createRoom({ name: `room${i}` });
}
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
});
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
@@ -38,4 +41,10 @@ test.describe("Room list panel", () => {
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
});
test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page }) => {
await page.setViewportSize({ width: 575, height: 600 });
const roomListPanel = getRoomListView(page);
await expect(roomListPanel).toMatchScreenshot("room-list-panel-smallscreen.png");
});
});

View File

@@ -7,12 +7,15 @@
import { type Page } from "@playwright/test";
import { test, expect } from "../../../element-web-test";
import { expect, test } from "../../../element-web-test";
test.describe("Room list", () => {
test.use({
displayName: "Alice",
labsFlags: ["feature_new_room_list"],
botCreateOpts: {
displayName: "BotBob",
},
});
/**
@@ -26,55 +29,412 @@ test.describe("Room list", () => {
test.beforeEach(async ({ page, app, user }) => {
// The notification toast is displayed above the search section
await app.closeNotificationToast();
for (let i = 0; i < 30; i++) {
await app.client.createRoom({ name: `room${i}` });
}
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
});
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list.png");
test.describe("Room list", () => {
test.beforeEach(async ({ page, app, user }) => {
for (let i = 0; i < 30; i++) {
await app.client.createRoom({ name: `room${i}` });
}
});
await roomListView.hover();
// Scroll to the end of the room list
await page.mouse.wheel(0, 1000);
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list.png");
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
// Scroll to the end of the room list
await page.mouse.wheel(0, 1000);
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
});
test("should open the room when it is clicked", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
});
test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
await roomItem.hover();
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
const roomItemMenu = roomItem.getByRole("button", { name: "More Options" });
await roomItemMenu.click();
await expect(page).toMatchScreenshot("room-list-item-open-more-options.png");
// It should make the room favourited
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
// Check that the room is favourited
await roomItem.hover();
await roomItemMenu.click();
await expect(page.getByRole("menuitemcheckbox", { name: "Favourited" })).toBeChecked();
// It should show the invite dialog
await page.getByRole("menuitem", { name: "invite" }).click();
await expect(page.getByRole("heading", { name: "Invite to room29" })).toBeVisible();
await app.closeDialog();
// It should leave the room
await roomItem.hover();
await roomItemMenu.click();
await page.getByRole("menuitem", { name: "leave room" }).click();
await expect(roomItem).not.toBeVisible();
});
test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
await roomItem.hover();
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
let roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
await roomItemMenu.click();
// Default settings should be selected
await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
"aria-selected",
"true",
);
await expect(page).toMatchScreenshot("room-list-item-open-notification-options.png");
// It should make the room muted
await page.getByRole("menuitem", { name: "Mute room" }).click();
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
// Scroll to the end of the room list
await page.mouse.wheel(0, 1000);
// The room decoration should have the muted icon
await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
await roomItem.hover();
// On hover, the room should show the muted icon
await expect(roomItem).toMatchScreenshot("room-list-item-hover-silent.png");
roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
await roomItemMenu.click();
// The Mute room option should be selected
await expect(page.getByRole("menuitem", { name: "Mute room" })).toHaveAttribute("aria-selected", "true");
await expect(page).toMatchScreenshot("room-list-item-open-notification-options-selection.png");
});
test("should scroll to the current room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
// Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
// Scroll to the end of the room list
await page.mouse.wheel(0, 1000);
await roomListView.getByRole("gridcell", { name: "Open room room0" }).click();
const filters = page.getByRole("listbox", { name: "Room list filters" });
await filters.getByRole("option", { name: "People" }).click();
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).not.toBeVisible();
await filters.getByRole("option", { name: "People" }).click();
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
});
test.describe("Shortcuts", () => {
test("should select the next room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await page.keyboard.press("Alt+ArrowDown");
await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible();
});
test("should select the previous room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
await page.keyboard.press("Alt+ArrowUp");
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
});
test("should select the last room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await page.keyboard.press("Alt+ArrowUp");
await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible();
});
test("should select the next unread room", async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
const roomId = await app.client.createRoom({ name: "1 notification" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
await roomListView.getByRole("gridcell", { name: "Open room room20" }).click();
await page.keyboard.press("Alt+Shift+ArrowDown");
await expect(page.getByRole("heading", { name: "1 notification", level: 1 })).toBeVisible();
});
});
test.describe("Keyboard navigation", () => {
test("should navigate to the room list", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const room28 = roomListView.getByRole("gridcell", { name: "Open room room28" });
// open the room
await room29.click();
// put focus back on the room list item
await room29.click();
await expect(room29).toBeFocused();
await page.keyboard.press("ArrowDown");
await expect(room28).toBeFocused();
await expect(room29).not.toBeFocused();
await page.keyboard.press("ArrowUp");
await expect(room29).toBeFocused();
await expect(room28).not.toBeFocused();
});
test("should navigate to the notification menu", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const moreButton = room29.getByRole("button", { name: "More options" });
const notificationButton = room29.getByRole("button", { name: "Notification options" });
await room29.click();
// put focus back on the room list item
await room29.click();
await page.keyboard.press("Tab");
await expect(moreButton).toBeFocused();
await page.keyboard.press("Tab");
await expect(notificationButton).toBeFocused();
// Open the menu
await notificationButton.click();
// Wait for the menu to be open
await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
"aria-selected",
"true",
);
// Close the menu
await page.keyboard.press("Escape");
// Focus should be back on the room list item
await expect(room29).toBeFocused();
});
});
});
test("should open the room when it is clicked", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
test.describe("Avatar decoration", () => {
test.use({ labsFlags: ["feature_video_rooms", "feature_new_room_list"] });
test("should be a public room", { tag: "@screenshot" }, async ({ page, app, user }) => {
// @ts-ignore Visibility enum is not accessible
await app.client.createRoom({ name: "public room", visibility: "public" });
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
const roomListView = getRoomList(page);
const publicRoom = roomListView.getByRole("gridcell", { name: "public room" });
await expect(publicRoom).toBeVisible();
await expect(publicRoom).toMatchScreenshot("room-list-item-public.png");
});
test("should be a low priority room", { tag: "@screenshot" }, async ({ page, app, user }) => {
// @ts-ignore Visibility enum is not accessible
await app.client.createRoom({ name: "low priority room", visibility: "public" });
const roomListView = getRoomList(page);
const publicRoom = roomListView.getByRole("gridcell", { name: "low priority room" });
// Make room low priority
await publicRoom.hover();
const roomItemMenu = publicRoom.getByRole("button", { name: "More Options" });
await roomItemMenu.click();
await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click();
// Should have low priority decoration
await expect(publicRoom.locator(".mx_RoomAvatarView_icon")).toHaveAccessibleName(
"This is a low priority room",
);
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await expect(publicRoom).toMatchScreenshot("room-list-item-low-priority.png");
});
test("should be a video room", { tag: "@screenshot" }, async ({ page, app, user }) => {
await page.getByRole("navigation", { name: "Room list" }).getByRole("button", { name: "Add" }).click();
await page.getByRole("menuitem", { name: "New video room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("video room");
await page.getByRole("button", { name: "Create video room" }).click();
const roomListView = getRoomList(page);
const videoRoom = roomListView.getByRole("gridcell", { name: "video room" });
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await expect(videoRoom).toBeVisible();
await expect(videoRoom).toMatchScreenshot("room-list-item-video.png");
});
});
test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
await roomItem.hover();
test.describe("Notification decoration", () => {
test("should render the invitation decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
const roomItemMenu = roomItem.getByRole("button", { name: "More Options" });
await roomItemMenu.click();
await expect(page).toMatchScreenshot("room-list-item-open-more-options.png");
await bot.createRoom({
name: "invited room",
invite: [user.userId],
is_direct: true,
});
const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" });
await expect(invitedRoom).toBeVisible();
await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
});
// It should make the room favourited
await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
test("should render the regular decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
// Check that the room is favourited
await roomItem.hover();
await roomItemMenu.click();
await expect(page.getByRole("menuitemcheckbox", { name: "Favourited" })).toBeChecked();
// It should show the invite dialog
await page.getByRole("menuitem", { name: "invite" }).click();
await expect(page.getByRole("heading", { name: "Invite to room29" })).toBeVisible();
await app.closeDialog();
const roomId = await app.client.createRoom({ name: "2 notifications" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
// It should leave the room
await roomItem.hover();
await roomItemMenu.click();
await page.getByRole("menuitem", { name: "leave room" }).click();
await expect(roomItem).not.toBeVisible();
await bot.sendMessage(roomId, "I am a robot. Beep.");
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "2 notifications" });
await expect(room).toBeVisible();
await expect(room.getByTestId("notification-decoration")).toHaveText("2");
await expect(room).toMatchScreenshot("room-list-item-notification.png");
});
test("should render the mention decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
const roomId = await app.client.createRoom({ name: "mention" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
const clientBot = await bot.prepareClient();
await clientBot.evaluate(
async (client, { roomId, userId }) => {
await client.sendMessage(roomId, {
// @ts-ignore ignore usage of MsgType.text
"msgtype": "m.text",
"body": "User",
"format": "org.matrix.custom.html",
"formatted_body": `<a href="https://matrix.to/#/${userId}">User</a>`,
"m.mentions": {
user_ids: [userId],
},
});
},
{ roomId, userId: user.userId },
);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "mention" });
await expect(room).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-mention.png");
});
test("should render a message preview", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
await app.settings.openUserSettings("Preferences");
await page.getByRole("switch", { name: "Show message previews" }).click();
await app.closeDialog();
const roomListView = getRoomList(page);
const roomId = await app.client.createRoom({ name: "activity" });
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "activity" });
await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
});
test("should render an activity decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
const otherRoomId = await app.client.createRoom({ name: "other room" });
const roomId = await app.client.createRoom({ name: "activity" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
await app.viewRoomById(roomId);
await app.settings.openRoomSettings("Notifications");
await page.getByText("@mentions & keywords").click();
await app.settings.closeDialog();
await app.settings.openUserSettings("Notifications");
await page.getByText("Show all activity in the room list (dots or number of unread messages)").click();
await app.settings.closeDialog();
// Switch to the other room to avoid the notification to be cleared
await app.viewRoomById(otherRoomId);
await bot.sendMessage(roomId, "I am a robot. Beep.");
const room = roomListView.getByRole("gridcell", { name: "activity" });
await expect(room.getByTestId("notification-decoration")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-activity.png");
});
test("should render a mark as unread decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
const roomId = await app.client.createRoom({ name: "mark as unread" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
const room = roomListView.getByRole("gridcell", { name: "mark as unread" });
await room.hover();
await room.getByRole("button", { name: "More Options" }).click();
await page.getByRole("menuitem", { name: "mark as unread" }).click();
// focus the user menu to avoid to have hover decoration
await page.getByRole("button", { name: "User menu" }).focus();
await expect(room).toMatchScreenshot("room-list-item-mark-as-unread.png");
});
test("should render silent decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
const roomId = await app.client.createRoom({ name: "silent" });
await app.client.inviteUser(roomId, bot.credentials.userId);
await bot.joinRoom(roomId);
await app.viewRoomById(roomId);
await app.settings.openRoomSettings("Notifications");
await page.getByText("Off").click();
await app.settings.closeDialog();
const room = roomListView.getByRole("gridcell", { name: "silent" });
await expect(room.getByTestId("notification-decoration")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-silent.png");
});
});
});

View File

@@ -13,6 +13,7 @@ import { selectHomeserver } from "../utils";
import { type Credentials, type HomeserverInstance } from "../../plugins/homeserver";
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { createBot } from "../crypto/utils.ts";
// This test requires fixed credentials for the device signing keys below to work
const username = "user1234";
@@ -258,6 +259,71 @@ test.describe("Login", () => {
await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
});
test("Continues to show verification prompt after cancelling device verification", async ({
page,
homeserver,
credentials,
}) => {
// Create a different device which is cross-signed, meaning we need to verify this device
await createBot(page, homeserver, credentials, true);
// Wait to avoid homeserver rate limit on logins
await page.waitForTimeout(100);
// Load the page and see that we are asked to verify
await page.goto("/#/welcome");
await login(page, homeserver, credentials);
let h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
// Click "Verify with another device"
await page.getByRole("button", { name: "Verify with another device" }).click();
// Cancel the new dialog
await page.getByRole("button", { name: "Close dialog" }).click();
// Check that we are still being asked to verify
h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
});
});
test("Can reset identity to become verified", async ({ page, homeserver, request, credentials }) => {
// Log in
const res = await request.post(`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
data: DEVICE_SIGNING_KEYS_BODY,
});
if (!res.ok()) {
console.log(`Uploading dummy keys failed with HTTP status ${res.status}`, await res.json());
throw new Error("Uploading dummy keys failed");
}
await page.goto("/");
await login(page, homeserver, credentials);
await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
// Start the reset process
await page.getByRole("button", { name: "Proceed with reset" }).click();
// First try cancelling and restarting
await page.getByRole("button", { name: "Cancel" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
// Then click outside the dialog and restart
await page.getByRole("link", { name: "Powered by Matrix" }).click({ force: true });
await page.getByRole("button", { name: "Proceed with reset" }).click();
// Finally we actually continue
await page.getByRole("button", { name: "Continue" }).click();
await page.getByPlaceholder("Password").fill(credentials.password);
await page.getByRole("button", { name: "Continue" }).click();
// We end up at the Home screen
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
await expect(page.getByRole("heading", { name: "Welcome Dave", exact: true })).toBeVisible();
});
});
});

View File

@@ -0,0 +1,112 @@
/*
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 Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
const screenshotOptions = (page: Page) => ({
mask: [page.locator(".mx_MessageTimestamp")],
// Hide the jump to bottom button in the timeline to avoid flakiness
// Exclude timestamp and read marker from snapshot
css: `
.mx_JumpToBottomButton {
display: none !important;
}
.mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
display: none !important;
}
`,
});
test.describe("Custom Component API", () => {
test.use({
displayName: "Manny",
config: {
modules: ["/modules/custom-component-module.js"],
},
page: async ({ page }, use) => {
await page.route("/modules/custom-component-module.js", async (route) => {
await route.fulfill({ path: "playwright/sample-files/custom-component-module.js" });
});
await use(page);
},
room: async ({ page, app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name: "TestRoom" });
await use({ roomId });
},
});
test.describe("basic functionality", () => {
test(
"should replace the render method of a textual event",
{ tag: "@screenshot" },
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Simple message");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile.png",
screenshotOptions(page),
);
},
);
test(
"should fall through if one module does not render a component",
{ tag: "@screenshot" },
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Fall through here");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile-fall-through.png",
screenshotOptions(page),
);
},
);
test(
"should render the original content of a textual event conditionally",
{ tag: "@screenshot" },
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Do not replace me");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile-original.png",
screenshotOptions(page),
);
},
);
test("should disallow editing when the allowEditingEvent hint is set to false", async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Do not show edits");
await page.getByText("Do not show edits").hover();
await expect(
await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }),
).not.toBeVisible();
});
test(
"should render the next registered component if the filter function throws",
{ tag: "@screenshot" },
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Crash the filter!");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-crash-handle-filter.png",
screenshotOptions(page),
);
},
);
test(
"should render original component if the render function throws",
{ tag: "@screenshot" },
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Crash the renderer!");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-crash-handle-renderer.png",
screenshotOptions(page),
);
},
);
});
});

View File

@@ -15,6 +15,7 @@ test.describe("Module loading", () => {
test.describe("Example Module", () => {
test.use({
config: {
brand: "TestBrand",
modules: ["/modules/example-module.js"],
},
page: async ({ page }, use) => {
@@ -25,11 +26,31 @@ test.describe("Module loading", () => {
},
});
test("should show alert", async ({ page }) => {
const dialogPromise = page.waitForEvent("dialog");
await page.goto("/");
const dialog = await dialogPromise;
expect(dialog.message()).toBe("Testing module loading successful!");
});
const testCases = [
["en", "TestBrand module loading successful!"],
["de", "TestBrand-Module erfolgreich geladen!"],
];
for (const [lang, message] of testCases) {
test.describe(`language-${lang}`, () => {
test.use({
config: async ({ config }, use) => {
await use({
...config,
setting_defaults: {
language: lang,
},
});
},
});
test("should show alert", async ({ page }) => {
const dialogPromise = page.waitForEvent("dialog");
await page.goto("/");
const dialog = await dialogPromise;
expect(dialog.message()).toBe(message);
});
});
}
});
});

View File

@@ -11,6 +11,9 @@ import { type Page } from "@playwright/test";
import { expect } from "../../element-web-test";
/**
* Click through registering a new user in the MAS UI.
*/
export async function registerAccountMas(
page: Page,
mailpit: MailpitClient,
@@ -37,6 +40,22 @@ export async function registerAccountMas(
await page.getByRole("textbox", { name: "6-digit code" }).fill(code);
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("textbox", { name: "Display Name" }).fill(username);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByText("Allow access to your account?")).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
}
/**
* Click through entering username and password into the MAS login prompt.
*/
export async function logInAccountMas(page: Page, username: string, password: string): Promise<void> {
await expect(page.getByText("Please sign in to continue:")).toBeVisible();
await page.getByRole("textbox", { name: "Username" }).fill(username);
await page.getByRole("textbox", { name: "Password", exact: true }).fill(password);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByText("Allow access to your account?")).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
}

View File

@@ -6,8 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { type Config, CONFIG_JSON } from "@element-hq/element-web-playwright-common";
import { type Browser, type Page } from "@playwright/test";
import { type StartedHomeserverContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/HomeserverContainer";
import { test, expect } from "../../element-web-test.ts";
import { registerAccountMas } from ".";
import { logInAccountMas, registerAccountMas } from ".";
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
@@ -33,7 +37,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await page.getByRole("button", { name: "Continue" }).click();
const userId = `alice_${testInfo.testId}`;
await registerAccountMas(page, mailpitClient, userId, "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!");
// Eventually, we should end up at the home screen.
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
@@ -55,7 +59,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
const newPage = await newPagePromise;
await newPage.getByText("Devices").click();
await newPage.getByText(deviceId).click();
await expect(newPage.getByText("Element")).toBeVisible();
await expect(newPage.getByText("Element", { exact: true })).toBeVisible();
await expect(newPage.getByText("http://localhost:8080/")).toBeVisible();
await expect(newPage).toHaveURL(/\/oauth2_session/);
await newPage.close();
@@ -73,4 +77,182 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await revokeAccessTokenPromise;
await revokeRefreshTokenPromise;
});
test(
"it should log out the user & wipe data when logging out via MAS",
{ tag: "@screenshot" },
async ({ mas, page, mailpitClient }, testInfo) => {
// We use this over the `user` fixture to ensure we get an OIDC session rather than a compatibility one
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
const userId = `alice_${testInfo.testId}`;
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!");
await expect(page.getByText("Welcome")).toBeVisible();
await page.goto("about:blank");
const result = await mas.manage("kill-sessions", userId);
expect(result.output).toContain("Ended 1 active OAuth 2.0 session");
await page.goto("http://localhost:8080");
await expect(
page.getByText("For security, this session has been signed out. Please sign in again."),
).toBeVisible();
await expect(page).toMatchScreenshot("token-expired.png", { includeDialogBackground: true });
const localStorageKeys = await page.evaluate(() => Object.keys(localStorage));
expect(localStorageKeys).toHaveLength(0);
},
);
test("can log in to an existing MAS account", { tag: "@screenshot" }, async ({ page, mailpitClient }, testInfo) => {
// Register an account with MAS
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
const userId = `alice_${testInfo.testId}`;
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!");
await expect(page.getByText("Welcome")).toBeVisible();
// Log out
await page.getByRole("button", { name: "User menu" }).click();
await expect(page.getByText(userId, { exact: true })).toBeVisible();
// Allow the outstanding requests queue to settle before logging out
await page.waitForTimeout(2000);
await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click();
await expect(page).toHaveURL(/\/#\/login$/);
// Log in again
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// We should be in (we see an error because we have no recovery key).
await expect(page.getByText("Unable to verify this device")).toBeVisible();
});
test.describe("with force_verification on", () => {
test.use({
config: {
force_verification: true,
},
});
test("verify dialog cannot be dismissed", { tag: "@screenshot" }, async ({ page, mailpitClient }, testInfo) => {
// Register an account with MAS
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
const userId = `alice_${testInfo.testId}`;
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!");
await expect(page.getByText("Welcome")).toBeVisible();
// Log out
await page.getByRole("button", { name: "User menu" }).click();
await expect(page.getByText(userId, { exact: true })).toBeVisible();
await page.waitForTimeout(2000);
await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click();
await expect(page).toHaveURL(/\/#\/login$/);
// Log in again
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// We should be being warned that we need to verify (but we can't)
await expect(page.getByText("Unable to verify this device")).toBeVisible();
// And there should be no way to close this prompt
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
});
test(
"continues to show verification prompt after cancelling device verification",
{ tag: "@screenshot" },
async ({ browser, config, homeserver, page, mailpitClient }, testInfo) => {
// Register an account with MAS
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
const userId = `alice_${testInfo.testId}`;
const password = "Pa$sW0rD!";
await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, password);
await expect(page.getByText("Welcome")).toBeVisible();
// Log in an additional account, and verify it.
//
// This means that when we log out and in again, we are offered
// to verify using another device.
const otherContext = await newContext(browser, config, homeserver);
const otherDevicePage = await otherContext.newPage();
await otherDevicePage.goto("/#/login");
await otherDevicePage.getByRole("button", { name: "Continue" }).click();
await logInAccountMas(otherDevicePage, userId, password);
await verifyUsingOtherDevice(otherDevicePage, page);
await otherDevicePage.close();
// Log out
await page.getByRole("button", { name: "User menu" }).click();
await expect(page.getByText(userId, { exact: true })).toBeVisible();
await page.waitForTimeout(2000);
await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click();
await expect(page).toHaveURL(/\/#\/login$/);
// Log in again
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
// We should be in, and not able to dismiss the verify dialog
await expect(page.getByText("Verify this device")).toBeVisible();
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
// When we start verifying with another device
await page.getByRole("button", { name: "Verify with another device" }).click();
// And then cancel it
await page.getByRole("button", { name: "Close dialog" }).click();
// Then we should still be at the unskippable verify prompt
await expect(page.getByText("Verify this device")).toBeVisible();
await expect(page.getByRole("button", { name: "Skip verification for now" })).not.toBeVisible();
},
);
});
});
/**
* Perform interactive emoji verification for a new device.
*/
async function verifyUsingOtherDevice(deviceToVerifyPage: Page, alreadyVerifiedDevicePage: Page) {
await deviceToVerifyPage.getByRole("button", { name: "Verify with another device" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "Verify session" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "Start" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "They match" }).click();
await deviceToVerifyPage.getByRole("button", { name: "They match" }).click();
await alreadyVerifiedDevicePage.getByRole("button", { name: "Got it" }).click();
await deviceToVerifyPage.getByRole("button", { name: "Got it" }).click();
}
/**
* Create a new browser context which serves up the default config plus what you supplied, and sets m.homeserver to the
* supplied homeserver's URL.
*/
async function newContext(browser: Browser, config: Partial<Partial<Config>>, homeserver: StartedHomeserverContainer) {
const otherContext = await browser.newContext();
await otherContext.route(`http://localhost:8080/config.json*`, async (route) => {
const json = {
...CONFIG_JSON,
...config,
default_server_config: {
"m.homeserver": {
base_url: homeserver.baseUrl,
},
},
};
await route.fulfill({ json });
});
return otherContext;
}

View File

@@ -43,6 +43,7 @@ test.describe("Pills", () => {
// go back to the message room and try to click on the pill text, as a user would
await app.viewRoomByName(messageRoom);
await expect(page).toHaveURL(new RegExp(`/#/room/${messageRoomId}`));
const pillText = page.locator(".mx_EventTile_body .mx_Pill .mx_Pill_text");
await expect(pillText).toHaveCSS("pointer-events", "none");
await pillText.click({ force: true }); // force is to ensure we bypass pointer-events

View File

@@ -15,20 +15,34 @@ test.describe("Release announcement", () => {
feature_release_announcement: true,
},
},
labsFlags: ["threadsActivityCentre"],
room: async ({ app, user }, use) => {
const roomId = await app.client.createRoom({
name: "Test room",
});
await app.viewRoomById(roomId);
await use({ roomId });
},
});
test("should display the release announcement process", { tag: "@screenshot" }, async ({ page, app, util }) => {
// The TAC release announcement should be displayed
await util.assertReleaseAnnouncementIsVisible("Threads Activity Centre");
// Hide the release announcement
await util.markReleaseAnnouncementAsRead("Threads Activity Centre");
await util.assertReleaseAnnouncementIsNotVisible("Threads Activity Centre");
test(
"should display the pinned messages release announcement",
{ tag: "@screenshot" },
async ({ page, app, room, util }) => {
await app.toggleRoomInfoPanel();
await page.reload();
// Wait for EW to load
await expect(page.getByRole("navigation", { name: "Spaces" })).toBeVisible();
// Check that once the release announcement has been marked as viewed, it does not appear again
await util.assertReleaseAnnouncementIsNotVisible("Threads Activity Centre");
});
const name = "All new pinned messages";
// The release announcement should be displayed
await util.assertReleaseAnnouncementIsVisible(name);
// Hide the release announcement
await util.markReleaseAnnouncementAsRead(name);
await util.assertReleaseAnnouncementIsNotVisible(name);
await page.reload();
await app.toggleRoomInfoPanel();
await expect(page.getByRole("menuitem", { name: "Pinned messages" })).toBeVisible();
// Check that once the release announcement has been marked as viewed, it does not appear again
await util.assertReleaseAnnouncementIsNotVisible(name);
},
);
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -10,6 +10,8 @@ import { type Locator, type Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { checkRoomSummaryCard, viewRoomSummaryByName } from "./utils";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { Bot } from "../../pages/bot";
const ROOM_NAME = "Test room";
const ROOM_NAME_LONG =
@@ -20,20 +22,23 @@ const ROOM_NAME_LONG =
"officia deserunt mollit anim id est laborum.";
const SPACE_NAME = "Test space";
const NAME = "Alice";
const LONG_NAME = "Bob long long long long long long long long long long long long long long long name";
const ROOM_ADDRESS_LONG =
"loremIpsumDolorSitAmetConsecteturAdipisicingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliqua";
function getMemberTileByName(page: Page, name: string): Locator {
return page.locator(`.mx_MemberTileView, [title="${name}"]`);
return page.locator(".mx_MemberListView .mx_MemberTileView_name").filter({ hasText: name });
}
test.describe("RightPanel", () => {
let testRoomId: string;
test.use({
displayName: NAME,
});
test.beforeEach(async ({ app, user }) => {
await app.client.createRoom({ name: ROOM_NAME });
testRoomId = await app.client.createRoom({ name: ROOM_NAME });
await app.client.createSpace({ name: SPACE_NAME });
});
@@ -76,10 +81,12 @@ test.describe("RightPanel", () => {
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png");
});
test("should handle clicking add widgets", async ({ page, app }) => {
test("should handle clicking add widgets", { tag: "@screenshot" }, async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Extensions" }).click();
await expect(page.getByTestId("right-panel")).toMatchScreenshot("with-extensions.png");
await page.getByRole("button", { name: "Add extensions" }).click();
await expect(page.locator(".mx_IntegrationManager")).toBeVisible();
});
@@ -133,6 +140,65 @@ test.describe("RightPanel", () => {
await page.getByLabel("Room info").nth(1).click();
await checkRoomSummaryCard(page, ROOM_NAME);
});
test(
"should handle viewing long room member name",
{ tag: "@screenshot" },
async ({ page, homeserver, app }) => {
const bobLongName = new Bot(page, homeserver, { displayName: LONG_NAME });
await bobLongName.prepareClient();
await app.client.inviteUser(testRoomId, bobLongName.credentials.userId);
await bobLongName.joinRoom(testRoomId);
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.locator(".mx_RightPanel").getByRole("menuitem", { name: "People" }).click();
await expect(page.locator(".mx_MemberListView")).toBeVisible();
await getMemberTileByName(page, LONG_NAME).click();
await expect(page.locator(".mx_UserInfo")).toBeVisible();
await expect(page.locator(".mx_UserInfo_profile").getByText(LONG_NAME)).toBeVisible();
await expect(page.locator(".mx_UserInfo")).toMatchScreenshot("with-long-name.png", {
mask: [page.locator(".mx_UserInfo_profile_mxid")],
css: `
/* Use monospace font for consistent mask width */
.mx_UserInfo_profile_mxid {
font-family: Inconsolata !important;
}
`,
});
},
);
test.describe("room reporting", () => {
test.skip(isDendrite, "Dendrite does not implement room reporting");
test("should handle reporting a room", { tag: "@screenshot" }, async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Report room" }).click();
const dialog = await page.getByRole("dialog", { name: "Report Room" });
await dialog.getByLabel("reason").fill("This room should be reported");
await expect(dialog).toMatchScreenshot("room-report-dialog.png");
await dialog.getByRole("button", { name: "Send report" }).click();
// Dialog should have gone
await expect(page.locator(".mx_Dialog")).toHaveCount(0);
});
test("should handle reporting a room and leaving the room", async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Report room" }).click();
const dialog = await page.getByRole("dialog", { name: "Report room" });
await dialog.getByRole("switch", { name: "Leave room" }).click();
await dialog.getByLabel("reason").fill("This room should be reported");
await dialog.getByRole("button", { name: "Send report" }).click();
await page.getByRole("dialog", { name: "Leave room" }).getByRole("button", { name: "Leave" }).click();
// Dialog should have gone
await expect(page.locator(".mx_Dialog")).toHaveCount(0);
});
});
});
test.describe("in spaces", () => {

View File

@@ -0,0 +1,74 @@
/*
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 { test, expect } from "../../element-web-test";
test.describe("Invites", () => {
test.use({
displayName: "Alice",
botCreateOpts: {
displayName: "Bob",
},
});
test("should render an invite view", { tag: "@screenshot" }, async ({ page, homeserver, user, bot, app }) => {
const roomId = await bot.createRoom({ is_direct: true });
await bot.inviteUser(roomId, user.userId);
await app.viewRoomByName("Bob");
await expect(page.locator(".mx_RoomView")).toMatchScreenshot("Invites_room_view.png", {
// Hide the mxid, which is not stable.
css: `
.mx_RoomPreviewBar_inviter_mxid {
display: none !important;
}
`,
});
});
test("should be able to decline an invite", async ({ page, homeserver, user, bot, app }) => {
const roomId = await bot.createRoom({ is_direct: true });
await bot.inviteUser(roomId, user.userId);
await app.viewRoomByName("Bob");
await page.getByRole("button", { name: "Decline", exact: true }).click();
await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Bob", exact: true }),
).not.toBeVisible();
});
test(
"should be able to decline an invite, report the room and ignore the user",
{ tag: "@screenshot" },
async ({ page, homeserver, user, bot, app }) => {
const roomId = await bot.createRoom({ is_direct: true });
await bot.inviteUser(roomId, user.userId);
await app.viewRoomByName("Bob");
await page.getByRole("button", { name: "Decline and block" }).click();
await page.getByLabel("Ignore user").click();
await page.getByLabel("Report room").click();
await page.getByLabel("Reason").fill("Do not want the room");
const roomReported = page.waitForRequest(
(req) =>
req.url().endsWith(`/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/report`) &&
req.method() === "POST",
);
await expect(page.getByRole("dialog", { name: "Decline invitation" })).toMatchScreenshot(
"Invites_reject_dialog.png",
);
await page.getByRole("button", { name: "Decline invite" }).click();
// Check room was reported.
await roomReported;
// Check user is ignored.
await app.settings.openUserSettings("Security & Privacy");
const ignoredUsersList = page.getByRole("list", { name: "Ignored users" });
await ignoredUsersList.scrollIntoViewIfNeeded();
await expect(ignoredUsersList.getByRole("listitem", { name: bot.credentials.userId })).toBeVisible();
},
);
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2023 Suguru Hirahara
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -50,8 +50,8 @@ test.describe("Appearance user settings tab", () => {
// Click "Show advanced" link button
await tab.getByRole("button", { name: "Show advanced" }).click();
await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click();
await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click();
await tab.getByLabel("Use bundled emoji font").click();
await tab.getByLabel("Use a system font").click();
// Assert that the font-family value was removed
await expect(page.locator("body")).toHaveCSS("font-family", '""');

View File

@@ -19,126 +19,162 @@ import {
test.describe("Encryption tab", () => {
test.use({ displayName: "Alice" });
let recoveryKey: GeneratedSecretStorageKey;
let expectedBackupVersion: string;
test.describe("when encryption is set up", () => {
let recoveryKey: GeneratedSecretStorageKey;
let expectedBackupVersion: string;
test.beforeEach(async ({ page, homeserver, credentials }) => {
// The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
const res = await createBot(page, homeserver, credentials);
recoveryKey = res.recoveryKey;
expectedBackupVersion = res.expectedBackupVersion;
});
test.beforeEach(async ({ page, homeserver, credentials }) => {
// The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
const res = await createBot(page, homeserver, credentials);
recoveryKey = res.recoveryKey;
expectedBackupVersion = res.expectedBackupVersion;
});
test(
"should show a 'Verify this device' button if the device is unverified",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
const dialog = await util.openEncryptionTab();
const content = util.getEncryptionTabContent();
test(
"should show a 'Verify this device' button if the device is unverified",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
const dialog = await util.openEncryptionTab();
const content = util.getEncryptionTabContent();
// The user's device is in an unverified state, therefore the only option available to them here is to verify it
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
await expect(verifyButton).toBeVisible();
await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
await verifyButton.click();
// The user's device is in an unverified state, therefore the only option available to them here is to verify it
const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
await expect(verifyButton).toBeVisible();
await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
await verifyButton.click();
await util.verifyDevice(recoveryKey);
await util.verifyDevice(recoveryKey);
await expect(content).toMatchScreenshot("default-tab.png", {
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
});
await expect(content).toMatchScreenshot("default-tab.png", {
mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
});
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
//
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
// We simulate this case by deleting the cached secrets in the indexedDB.
test(
"should prompt to enter the recovery key when the secrets are not cached locally",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
// Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
//
// This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
// We simulate this case by deleting the cached secrets in the indexedDB.
test(
"should prompt to enter the recovery key when the secrets are not cached locally",
{ tag: "@screenshot" },
async ({ page, app, util }) => {
await verifySession(app, recoveryKey.encodedPrivateKey);
// We need to delete the cached secrets
await deleteCachedSecrets(page);
await util.openEncryptionTab();
// We ask the user to enter the recovery key
const dialog = util.getEncryptionTabContent();
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
await expect(enterKeyButton).toBeVisible();
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
await enterKeyButton.click();
// Fill the recovery key
await util.enterRecoveryKey(recoveryKey);
await expect(dialog).toMatchScreenshot("default-tab.png", {
mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
});
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({
page,
app,
util,
}) => {
await verifySession(app, recoveryKey.encodedPrivateKey);
// We need to delete the cached secrets
await deleteCachedSecrets(page);
// The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button
await util.openEncryptionTab();
// We ask the user to enter the recovery key
const dialog = util.getEncryptionTabContent();
const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
await expect(enterKeyButton).toBeVisible();
await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
await enterKeyButton.click();
await dialog.getByRole("button", { name: "Forgot recovery key?" }).click();
// Fill the recovery key
await util.enterRecoveryKey(recoveryKey);
await expect(dialog).toMatchScreenshot("default-tab.png", {
mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
});
// The user is prompted to reset their identity
await expect(
dialog.getByText("Forgot your recovery key? Youll need to reset your identity."),
).toBeVisible();
});
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => {
await verifySession(app, recoveryKey.encodedPrivateKey);
await util.openEncryptionTab();
// Check that the current device is connected to key backup
// The backup decryption key should be in cache also, as we got it directly from the 4S
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
await page.getByRole("checkbox", { name: "Allow key storage" }).click();
test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({
page,
app,
util,
}) => {
await verifySession(app, recoveryKey.encodedPrivateKey);
// We need to delete the cached secrets
await deleteCachedSecrets(page);
await expect(
page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
).toBeVisible();
// The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button
await util.openEncryptionTab();
const dialog = util.getEncryptionTabContent();
await dialog.getByRole("button", { name: "Forgot recovery key?" }).click();
await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png");
// The user is prompted to reset their identity
await expect(dialog.getByText("Forgot your recovery key? Youll need to reset your identity.")).toBeVisible();
const deleteRequestPromises = [
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.master")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.self_signing")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.user_signing")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.megolm_backup.v1")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.secret_storage.default_key")),
page.waitForRequest((req) => req.url().includes("/account_data/m.secret_storage.key.")),
];
await page.getByRole("button", { name: "Delete key storage" }).click();
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked();
for (const prom of deleteRequestPromises) {
const request = await prom;
expect(request.method()).toBe("PUT");
expect(request.postData()).toBe(JSON.stringify({}));
}
});
});
test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => {
await verifySession(app, recoveryKey.encodedPrivateKey);
await util.openEncryptionTab();
test.describe("when encryption is not set up", () => {
test("'Verify this device' allows us to become verified", async ({
page,
user,
credentials,
app,
}, workerInfo) => {
const settings = await app.settings.openUserSettings("Encryption");
await page.getByRole("checkbox", { name: "Allow key storage" }).click();
// Initially, our device is not verified
await expect(settings.getByRole("heading", { name: "Device not verified" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
).toBeVisible();
// We will reset our identity
await settings.getByRole("button", { name: "Verify this device" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png");
// First try cancelling and restarting
await page.getByRole("button", { name: "Cancel" }).click();
await page.getByRole("button", { name: "Proceed with reset" }).click();
const deleteRequestPromises = [
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.master")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.self_signing")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.user_signing")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.megolm_backup.v1")),
page.waitForRequest((req) => req.url().endsWith("/account_data/m.secret_storage.default_key")),
page.waitForRequest((req) => req.url().includes("/account_data/m.secret_storage.key.")),
];
// Then click outside the dialog and restart
await page.locator("li").filter({ hasText: "Encryption" }).click({ force: true });
await page.getByRole("button", { name: "Proceed with reset" }).click();
await page.getByRole("button", { name: "Delete key storage" }).click();
// Finally we actually continue
await page.getByRole("button", { name: "Continue" }).click();
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked();
for (const prom of deleteRequestPromises) {
const request = await prom;
expect(request.method()).toBe("PUT");
expect(request.postData()).toBe(JSON.stringify({}));
}
// Now we are verified, so we see the Key storage toggle
await expect(settings.getByRole("heading", { name: "Key storage" })).toBeVisible();
});
});
});

View File

@@ -21,14 +21,19 @@ test.describe("Preferences user settings tab", () => {
const locator = await app.settings.openUserSettings("Preferences");
await use(locator);
},
// display message preview settings
labsFlags: ["feature_new_room_list"],
});
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
await page.setViewportSize({ width: 1024, height: 3300 });
await page.setViewportSize({ width: 1024, height: 4000 });
const tab = await app.settings.openUserSettings("Preferences");
// Assert that the top heading is rendered
await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png");
await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png", {
// masked due to daylight saving time
mask: [tab.locator("#mx_dropdownUserTimezone_value")],
});
});
test("should be able to change the app language", { tag: ["@no-firefox", "@no-webkit"] }, async ({ uut, user }) => {

View File

@@ -37,6 +37,15 @@ test.describe("Roles & Permissions room settings tab", () => {
// Change the role of Alice to Moderator (50)
await combobox.selectOption("Moderator");
await expect(combobox).toHaveValue("50");
// Should display a modal to warn that we are demoting the only admin user
const modal = await page.locator(".mx_Dialog", {
hasText: "Warning",
});
await expect(modal).toBeVisible();
// Click on the continue button in the modal
await modal.getByRole("button", { name: "Continue" }).click();
const respPromise = page.waitForRequest("**/state/**");
await applyButton.click();
await respPromise;

View File

@@ -0,0 +1,34 @@
/*
* 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 { test, expect } from "../../element-web-test";
test.describe("share from URL", () => {
test.use({
displayName: "Alice",
room: async ({ app }, use) => {
const roomId = await app.client.createRoom({ name: "A test room" });
await use({ roomId });
},
});
test("should share message when users navigates to share URL", async ({ page, user, room, app }) => {
await page.goto("/#/share?msg=Hello+world");
// The forward message dialog doesn't update as new infomation arrives via sync, which means sometimes
// this is just says, "Empty room". For the same reason, we can't reliably write a test for loading the
// app straight away with a /#/share url as the room doesn't appear until the client syncs.]
// Ideally we should fix the forward dialog to update and eliminate races, until then, there is only one
// room so we click the first button.
await page.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
await page.keyboard.press("Escape");
await app.viewRoomByName("A test room");
const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
await expect(lastMessage).toBeVisible();
const lastMessageText = await lastMessage.locator(".mx_EventTile_body").innerText();
await expect(lastMessageText).toBe("Hello world");
});
});

View File

@@ -7,47 +7,15 @@ Please see LICENSE files in the repository root for full details.
*/
import { type Page, type Request } from "@playwright/test";
import { GenericContainer, type StartedTestContainer, Wait } from "testcontainers";
import { test as base, expect } from "../../element-web-test";
import type { ElementAppPage } from "../../pages/ElementAppPage";
import type { Bot } from "../../pages/bot";
const test = base.extend<{
slidingSyncProxy: StartedTestContainer;
testRoom: { roomId: string; name: string };
joinedBot: Bot;
}>({
slidingSyncProxy: async ({ logger, network, postgres, page, homeserver }, use, testInfo) => {
const container = await new GenericContainer("ghcr.io/matrix-org/sliding-sync:v0.99.3")
.withNetwork(network)
.withExposedPorts(8008)
.withLogConsumer(logger.getConsumer("sliding-sync-proxy"))
.withWaitStrategy(Wait.forHttp("/client/server.json", 8008))
.withEnvironment({
SYNCV3_SECRET: "bwahahaha",
SYNCV3_DB: `user=${postgres.getUsername()} dbname=postgres password=${postgres.getPassword()} host=postgres sslmode=disable`,
SYNCV3_SERVER: `http://homeserver:8008`,
})
.start();
const proxyAddress = `http://${container.getHost()}:${container.getMappedPort(8008)}`;
await page.addInitScript((proxyAddress) => {
window.localStorage.setItem(
"mx_local_settings",
JSON.stringify({
feature_sliding_sync_proxy_url: proxyAddress,
}),
);
window.localStorage.setItem("mx_labs_feature_feature_sliding_sync", "true");
}, proxyAddress);
await use(container);
await container.stop();
},
// Ensure slidingSyncProxy is set up before the user fixture as it relies on an init script
credentials: async ({ slidingSyncProxy, credentials }, use) => {
await use(credentials);
},
testRoom: async ({ user, app }, use) => {
const name = "Test Room";
const roomId = await app.client.createRoom({ name });
@@ -82,6 +50,14 @@ test.describe("Sliding Sync", () => {
});
};
test.use({
config: {
features: {
feature_simplified_sliding_sync: true,
},
},
});
// Load the user fixture for all tests
test.beforeEach(({ user }) => {});
@@ -188,15 +164,7 @@ test.describe("Sliding Sync", () => {
).not.toBeAttached();
});
test("should not show unread indicators", async ({ page, app, joinedBot: bot, testRoom }) => {
// TODO: for now. Later we should.
// disable notifs in this room (TODO: CS API call?)
const locator = page.getByRole("treeitem", { name: "Test Room" });
await locator.hover();
await locator.getByRole("button", { name: "Notification options" }).click();
await page.getByRole("menuitemradio", { name: "Mute room" }).click();
test("should show unread indicators", async ({ page, app, joinedBot: bot, testRoom }) => {
// create a new room so we know when the message has been received as it'll re-shuffle the room list
await app.client.createRoom({ name: "Dummy" });
@@ -207,9 +175,7 @@ test.describe("Sliding Sync", () => {
// wait for this message to arrive, tell by the room list resorting
await checkOrder(["Test Room", "Dummy"], page);
await expect(
page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge"),
).not.toBeAttached();
await expect(page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge")).toBeAttached();
});
test("should update user settings promptly", async ({ page, app }) => {
@@ -221,6 +187,37 @@ test.describe("Sliding Sync", () => {
await expect(locator.locator(".mx_ToggleSwitch_on")).toBeAttached();
});
test("should send subscribe_rooms on room switch if room not already subscribed", async ({ page, app }) => {
// create rooms and check room names are correct
const roomIds: string[] = [];
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit });
roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
}
const [roomAId, roomPId] = roomIds;
const matchRoomSubRequest = (subRoomId: string) => (request: Request) => {
if (!request.url().includes("/sync")) return false;
const body = request.postDataJSON();
return body.room_subscriptions?.[subRoomId];
};
// Select the Test Room and wait for playwright to get the request
const [request] = await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomAId)),
page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
]);
const roomSubscriptions = request.postDataJSON().room_subscriptions;
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
// Switch to another room and wait for playwright to get the request
await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomPId)),
page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
]);
});
test("should show and be able to accept/reject/rescind invites", async ({
page,
app,
@@ -258,8 +255,8 @@ test.describe("Sliding Sync", () => {
// Select the room to reject
await page.getByRole("treeitem", { name: "Room to Reject" }).click();
// Reject the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Reject", exact: true }).click();
// Decline the invite
await page.locator(".mx_RoomView").getByRole("button", { name: "Decline", exact: true }).click();
await expect(
page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
@@ -361,52 +358,4 @@ test.describe("Sliding Sync", () => {
// ensure the reply-to does not disappear
await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
});
test("should send unsubscribe_rooms for every room switch", async ({ page, app }) => {
// create rooms and check room names are correct
const roomIds: string[] = [];
for (const fruit of ["Apple", "Pineapple", "Orange"]) {
const id = await app.client.createRoom({ name: fruit });
roomIds.push(id);
await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
}
const [roomAId, roomPId, roomOId] = roomIds;
const matchRoomSubRequest = (subRoomId: string) => (request: Request) => {
if (!request.url().includes("/sync")) return false;
const body = request.postDataJSON();
return body.txn_id && body.room_subscriptions?.[subRoomId];
};
const matchRoomUnsubRequest = (unsubRoomId: string) => (request: Request) => {
if (!request.url().includes("/sync")) return false;
const body = request.postDataJSON();
return (
body.txn_id && body.unsubscribe_rooms?.includes(unsubRoomId) && !body.room_subscriptions?.[unsubRoomId]
);
};
// Select the Test Room and wait for playwright to get the request
const [request] = await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomAId)),
page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
]);
const roomSubscriptions = request.postDataJSON().room_subscriptions;
expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
// Switch to another room and wait for playwright to get the request
await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomPId)),
page.waitForRequest(matchRoomUnsubRequest(roomAId)),
page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
]);
// And switch to even another room and wait for playwright to get the request
await Promise.all([
page.waitForRequest(matchRoomSubRequest(roomOId)),
page.waitForRequest(matchRoomUnsubRequest(roomPId)),
page.getByRole("treeitem", { name: "Orange", exact: true }).click(),
]);
// TODO: Add tests for encrypted rooms
});
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -35,17 +35,18 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
name: spaceName,
},
},
...roomIds.map(spaceChildInitialState),
...roomIds.map((r) => spaceChildInitialState(r)),
],
};
}
function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"]["0"] {
function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
return {
type: "m.space.child",
state_key: roomId,
content: {
via: [roomId.split(":")[1]],
order,
},
};
}
@@ -121,9 +122,10 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
const roomList = page.getByRole("tree", { name: "Rooms" });
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
// Assert rooms exist in the space explorer
await expect(
@@ -155,7 +157,7 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Just me" }).click();
await page.getByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
await page.getByRole("checkbox", { name: "Sample Room" }).click();
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
@@ -165,6 +167,50 @@ test.describe("Spaces", () => {
).toBeVisible();
});
test(
"should allow user to add an existing room to a space after creation",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await app.client.createRoom({
name: "Sample Room",
});
await app.client.createRoom({
name: "A Room that will not be selected",
});
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
await menu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("playwright/sample-files/riot.png");
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
await menu
.getByRole("textbox", { name: "Description" })
.fill("This is a personal space to mourn Riot.im...");
await menu.getByRole("textbox", { name: "Name" }).fill("This is my Riot");
await menu.getByRole("textbox", { name: "Name" }).press("Enter");
await page.getByRole("button", { name: "Just me" }).click();
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "Add existing room" }).click();
await page.getByRole("checkbox", { name: "Sample Room" }).click();
await expect(page.getByRole("dialog", { name: "Avatar Add existing rooms" })).toMatchScreenshot(
"add-existing-rooms-dialog.png",
);
await page.getByRole("button", { name: "Add" }).click();
await expect(
page.locator(".mx_SpaceHierarchy_list").getByRole("treeitem", { name: "Sample Room" }),
).toBeVisible();
},
);
test("should allow user to invite another to a space", { tag: "@no-webkit" }, async ({ page, app, user, bot }) => {
await app.client.createSpace({
visibility: "public" as any,
@@ -291,4 +337,36 @@ test.describe("Spaces", () => {
// Assert we get shown the new room intro, and thus not the soft crash screen
await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
});
test("should render spaces view", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
axe.disableRules([
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive",
// XXX: We have some known contrast issues here
"color-contrast",
]);
const childSpaceId1 = await app.client.createSpace({
name: "Child Space 1",
initial_state: [],
});
const childSpaceId2 = await app.client.createSpace({
name: "Child Space 2",
initial_state: [],
});
const childSpaceId3 = await app.client.createSpace({
name: "Child Space 3",
initial_state: [],
});
await app.client.createSpace({
name: "Root Space",
initial_state: [
spaceChildInitialState(childSpaceId1, "a"),
spaceChildInitialState(childSpaceId2, "b"),
spaceChildInitialState(childSpaceId3, "c"),
],
});
await app.viewSpaceByName("Root Space");
await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("space-room-view.png");
});
});

View File

@@ -19,7 +19,6 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
test.use({
displayName: "Alice",
botCreateOpts: { displayName: "Other User" },
labsFlags: ["threadsActivityCentre"],
});
test(

View File

@@ -0,0 +1,139 @@
/*
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 * as fs from "node:fs";
import { type EventType, type MsgType, type RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
import { test, expect } from "../../element-web-test";
const MEDIA_FILE = fs.readFileSync("playwright/sample-files/riot.png");
test.describe("Media preview settings", () => {
test.use({
displayName: "Alan",
botCreateOpts: {
displayName: "Bob",
},
room: async ({ app, page, homeserver, bot, user }, use) => {
const mxc = (await bot.uploadContent(MEDIA_FILE, { name: "image.png", type: "image/png" })).content_uri;
const roomId = await bot.createRoom({
name: "Test room",
invite: [user.userId],
initial_state: [{ type: "m.room.avatar", content: { url: mxc }, state_key: "" }],
});
await bot.sendEvent(roomId, null, "m.room.message" as EventType, {
msgtype: "m.image" as MsgType,
body: "image.png",
url: mxc,
});
await use({ roomId });
},
});
test("should be able to hide avatars of inviters", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
let settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Hide avatars of room and inviter").click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await expect(
page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
).toMatchScreenshot("invite-no-avatar.png", {
// Hide the mxid, which is not stable.
css: `
.mx_RoomPreviewBar_inviter_mxid {
display: none !important;
}
`,
});
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-no-avatar.png");
// And then go back to being visible
settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Hide avatars of room and inviter").click();
await app.closeDialog();
await page.goto("#/home");
await app.viewRoomById(room.roomId);
await expect(
page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
).toMatchScreenshot("invite-with-avatar.png", {
// Hide the mxid, which is not stable.
css: `
.mx_RoomPreviewBar_inviter_mxid {
display: none !important;
}
`,
});
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-with-avatar.png");
});
test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).toBeVisible();
});
test("should be able to hide media in non-private rooms globally", async ({ page, app, room, user, bot }) => {
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
join_rule: "public",
});
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByLabel("In private rooms").click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).toBeVisible();
for (const joinRule of ["invite", "knock", "restricted"] as RoomJoinRulesEventContent["join_rule"][]) {
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
join_rule: joinRule,
} satisfies RoomJoinRulesEventContent);
await expect(page.getByText("Show image")).not.toBeVisible();
}
});
test("should be able to show media in rooms globally", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).not.toBeVisible();
});
test("should be able to hide media in an individual room", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
const roomSettings = await app.settings.openRoomSettings("General");
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();
await expect(page.getByText("Show image")).toBeVisible();
});
test("should be able to show media in an individual room", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
const roomSettings = await app.settings.openRoomSettings("General");
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();
await expect(page.getByText("Show image")).not.toBeVisible();
});
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -28,6 +28,8 @@ const NEW_AVATAR = fs.readFileSync("playwright/sample-files/element.png");
const OLD_NAME = "Alan";
const NEW_NAME = "Alan (away)";
const VIDEO_FILE = fs.readFileSync("playwright/sample-files/5secvid.webm");
const getEventTilesWithBodies = (page: Page): Locator => {
return page.locator(".mx_EventTile").filter({ has: page.locator(".mx_EventTile_body") });
};
@@ -905,6 +907,39 @@ test.describe("Timeline", () => {
mask: [page.locator(".mx_MessageTimestamp")],
});
});
test("should be able to hide an image", { tag: "@screenshot" }, async ({ page, app, room, context }) => {
await app.viewRoomById(room.roomId);
await sendImage(app.client, room.roomId, NEW_AVATAR);
await app.timeline.scrollToBottom();
const imgTile = page.locator(".mx_MImageBody").first();
await expect(imgTile).toBeVisible();
await imgTile.hover();
await page.getByRole("button", { name: "Hide" }).click();
// Check that the image is now hidden.
await expect(page.getByRole("button", { name: "Show image" })).toBeVisible();
});
test("should be able to hide a video", async ({ page, app, room, context }) => {
await app.viewRoomById(room.roomId);
const upload = await app.client.uploadContent(VIDEO_FILE, { name: "bbb.webm", type: "video/webm" });
await app.client.sendEvent(room.roomId, null, "m.room.message" as EventType, {
msgtype: "m.video" as MsgType,
body: "bbb.webm",
url: upload.content_uri,
});
await app.timeline.scrollToBottom();
const imgTile = page.locator(".mx_MVideoBody").first();
await expect(imgTile).toBeVisible();
await imgTile.hover();
await page.getByRole("button", { name: "Hide" }).click();
// Check that the video is now hidden.
await expect(page.getByRole("button", { name: "Show video" })).toBeVisible();
await expect(page.locator("video")).not.toBeVisible();
});
});
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
@@ -1306,4 +1341,44 @@ test.describe("Timeline", () => {
);
});
});
test.describe("spoilers", { tag: "@screenshot" }, () => {
test("clicking a spoiler containing the pill de-spoilers on 1st click, then follows link on 2nd", async ({
page,
user,
app,
room,
}) => {
// View room
await page.goto(`/#/room/${room.roomId}`);
// Send a spoilered pill
await app.client.sendMessage(room.roomId, {
msgtype: "m.text",
body: user.userId,
format: "org.matrix.custom.html",
formatted_body: `<span data-mx-spoiler>https://matrix.to/#/${user.userId}</span>`,
});
const screenshotOptions = {
css: `
.mx_MessageTimestamp {
display: none !important;
}
`,
};
const eventTile = page.locator(".mx_RoomView_body .mx_EventTile_last");
await expect(eventTile).toMatchScreenshot("spoiler.png", screenshotOptions);
const rightPanelButton = page.getByText("Share profile");
const pill = page.locator(".mx_UserPill");
await pill.click({ force: true }); // force to click the spoiler wrapper instead
await expect(eventTile).toMatchScreenshot("spoiler-uncovered.png", screenshotOptions);
await expect(rightPanelButton).not.toBeVisible(); // assert the right panel is not yet open
await pill.click();
await expect(rightPanelButton).toBeVisible(); // assert the right panel is open
});
});
});

View File

@@ -114,7 +114,7 @@ export class ElementAppPage {
* @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
*/
public getComposerField(isRightPanel?: boolean): Locator {
return this.getComposer(isRightPanel).locator("[contenteditable]");
return this.getComposer(isRightPanel).locator("div[contenteditable]");
}
/**

Binary file not shown.

View File

@@ -0,0 +1,55 @@
/*
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.
*/
export default class CustomComponentModule {
static moduleApiVersion = "^1.2.0";
constructor(api) {
this.api = api;
this.api.customComponents.registerMessageRenderer(
(evt) => evt.content.body === "Do not show edits",
(_props, originalComponent) => {
return originalComponent();
},
{ allowEditingEvent: false },
);
this.api.customComponents.registerMessageRenderer(
(evt) => evt.content.body === "Fall through here",
(props) => {
const body = props.mxEvent.content.body;
return `Fallthrough text for ${body}`;
},
);
this.api.customComponents.registerMessageRenderer(
(evt) => {
if (evt.content.body === "Crash the filter!") {
throw new Error("Fail test!");
}
return false;
},
() => {
return `Should not render!`;
},
);
this.api.customComponents.registerMessageRenderer(
(evt) => evt.content.body === "Crash the renderer!",
() => {
throw new Error("Fail test!");
},
);
// Order is specific here to avoid this overriding the other renderers
this.api.customComponents.registerMessageRenderer("m.room.message", (props, originalComponent) => {
const body = props.mxEvent.content.body;
if (body === "Do not replace me") {
return originalComponent();
} else if (body === "Fall through here") {
return null;
}
return `Custom text for ${body}`;
});
}
async load() {}
}

View File

@@ -6,11 +6,19 @@ Please see LICENSE files in the repository root for full details.
*/
export default class ExampleModule {
static moduleApiVersion = "^0.1.0";
static moduleApiVersion = "^1.0.0";
constructor(api) {
this.api = api;
this.api.i18n.register({
key: {
en: "%(brand)s module loading successful!",
de: "%(brand)s-Module erfolgreich geladen!",
},
});
}
async load() {
alert("Testing module loading successful!");
const brand = this.api.config.get("brand");
alert(this.api.i18n.translate("key", { brand }));
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files have changed in this diff Show More