Compare commits

...

85 Commits

Author SHA1 Message Date
Michael Telatynski
01ffd72b9d Coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-08 09:28:47 +01:00
Michael Telatynski
0eee4e3a18 Coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-08 09:10:29 +01:00
Michael Telatynski
98ed46b0f3 types
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-07 16:20:39 +01:00
Michael Telatynski
78690f032d Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-07 15:12:04 +01:00
Michael Telatynski
607dbb03df Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/module-api-1.3.0
# Conflicts:
#	src/stores/RoomViewStore.tsx
2025-08-07 13:51:47 +01:00
David Langley
bdfdf5fc49 Fix issue of new room list taking up the full width (#30459)
* Better handle for resizer for new room list that doesn't support collapsing.

* Add unit test

* Test the new guards/checks on resize

* Finish cleaning up mock resets
2025-08-07 11:28:11 +00:00
ElementRobot
cc094f4b56 Synchronise internationalisations with Localazy (#30407)
* [create-pull-request] automated change

* First pass of fixing tests

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

* Second pass of fixing tests

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

* Third pass of fixing tests

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-07 11:25:34 +00:00
Richard van der Hoff
2d0facd47b Refactor MultiInviter (#30500)
* MultiInviter: remove cancellation support

This is unused and untested, so we can basically assume it doesn't work.

* MultiInviter: factor out `handleUnknownProfileUsers` method

* MultiInviter: remove unused `ignoreProfile` arg from `inviteMore`

* MultiInviter: simplify `deferred` usage

No point in doing `deferred.resolve(this.completionStates)` everywhere

* MultiInviter.doInvite: do not `reject` for known fatal errors

Using `reject` for known, handled, fatal errors is somewhat confusing here,
since it looks like we swallow the error. (It's actually up to the caller to
check the recoreded `errors` and report them.)

Rather than rejecting, rely on the `_fatal` flag.

* MultiInviter: move finish logic to `.invite`

... for less `deferred` complication

* MultiInviter: rewrite loop as a `for` loop

Async functions are a thing in modern javascript, and way easier to grok than a
stack of promises.
2025-08-07 10:27:27 +00:00
Robin
c53b17d291 Delegate the sending of call notifications to Element Call (#30507)
* Move Element Call event types to a more appropriate file

To remove the potential for import cycles in src/models/Call.ts, which I was accidentally creating when I tried to reference data from the RoomListStore in the ElementCall class.

* Make sure ElementCall tests clean up the call object

* Upgrade Element Call to v0.14.1

* Delegate the sending of call notifications to Element Call

As of Element Call version 0.14.0, the widget is now capable of sending call notifications itself if we just request this with the sendNotificationType URL parameter. This makes Element Web's group call code a little bit more succinct.

* Fix createRoom test
2025-08-07 09:27:53 +00:00
Florian Duros
8086262e04 Move AudioPlayer to shared components (#30386)
* feat: add `PlayPauseButton` to storybook

* feat: add generic media body

* feat: add seekbar component

* chore: add ViewWrapper to help writing stories with vm

* refactor: move `formatBytes` from `formattingUtils` into shared component

* refactor: add `className` props to `Clock`

* feat: add new audio player component

* test(e2e): add screenshots for new shared components

* feat: add AudioPlayerViewModel

* feat: use new audio player in `MAudioBody`

* refactor: remove old audio player

* test(e2e): update existing tests

* refactor: remove unused `DurationClock`

* refactor: rename `SeekBar` into `LegacySeekBar`
2025-08-07 09:02:49 +00:00
Robin
f9a0a626a6 Fix widget persistence in React development mode (#30509)
15f1291cbc was really close to making widgets just work again in React development mode following the upgrade to React 19, but I forgot to test one thing: that persistent widgets (such as Element Call) still reuse the same iframe across their entire lifecycle as expected. The solution is to not manually destroy the iframe when AppTile is being unmounted; even if it turns out that the widget isn't actually persistent, React will still destroy it automatically for us.
2025-08-07 07:44:20 +00:00
ElementRobot
d7f54355ac [create-pull-request] automated change (#30510)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-07 06:20:30 +00:00
renovate[bot]
a668216e20 Update dependency stylelint-config-standard to v39 (#30497)
* Update dependency stylelint-config-standard to v39

* 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-08-06 12:59:04 +00:00
renovate[bot]
1cadf1a82e Update dependency @sentry/browser to v10 (#30495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 12:48:48 +00:00
R Midhun Suresh
ee37734cfc MVVM - Introduce some helpers for snapshot management (#30398)
* Introduce snapshot class to track snapshot updates

This avoids the hassle of having to manually call emit.

* Better viewmodel ergonomics

- Rename `SubscriptionViewModel` to `BaseViewModel`. I feel this is
  appropriate since that class does more than just manage subscriptions.
- `getSnapshot` is no longer an abstract method. It's simply a method
  that returns the current snapshot state. This ensures that getSnapshot
result is cached by default which is required by `useSyncExternalStore`.
- `props` are a property of the base vm class so that actual VMs don't
  have to keep creating this property.

* Update `TextualEventViewModel`

* Fix test

* Rename `TextualEvent` to `TextualEventView`

* Fix snapshot object not being merged

* Rename directory to `EventTileView`

* Fix broken snapshot

* Add test for snapshot class
2025-08-06 12:29:32 +00:00
Robin
15f1291cbc Fix widget initialization in React development mode (#30463)
Since the upgrade to React 19, widget initialization (most notably affecting group calls) has been broken in development mode. This is because React now executes all callback refs twice, and the callback ref that receives the widget's iframe was not prepared to deal with that. I've fixed this by creating and attaching the iframe to the DOM in the callback ref, which allows us to properly couple its lifetime to that of the StopGapWidget. I've also added some insurance against strict mode-style races in StopGapWidget (doesn't hurt).
2025-08-06 12:17:00 +00:00
ElementRobot
8a550cf3f6 [create-pull-request] automated change (#30503)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-06 08:37:31 +00:00
renovate[bot]
9c911d5c59 Update definitelyTyped (#30484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 18:07:47 +00:00
Florian Duros
6fca4d106e Move clock into shared components (#30480)
* refactor: extract `formatSeconds` from `DateUtils`

* refactor: move clock into shared-components

* refactor: update clock imports

* test(e2e): add screenshots
2025-08-05 17:04:55 +00:00
Florian Duros
24f923feac Move number.ts to utils in shared components (#30498)
* refactor: move `number.ts` in shared components

* chore: include ts test file in sonar config
2025-08-05 17:04:45 +00:00
renovate[bot]
9be2b973d0 Update react monorepo (#30486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 16:39:49 +00:00
renovate[bot]
d837d2f62d Update Node.js to 2d63e0f (#30483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 16:04:58 +00:00
renovate[bot]
f2379878cd Update typescript-eslint monorepo to v8.39.0 (#30494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:25:54 +00:00
renovate[bot]
261d073f6d Update dependency @babel/runtime to v7.28.2 (#30489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:25:41 +00:00
renovate[bot]
401fc63eb0 Update dependency @vector-im/compound-design-tokens to v6 (#30496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:21:51 +00:00
renovate[bot]
51c4506431 Update dependency testcontainers to v11.5.0 (#30491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:21:25 +00:00
renovate[bot]
1de27b265b Update dependency @sentry/browser to v9.44.0 (#30490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:21:01 +00:00
renovate[bot]
db9514760d Update testing-library monorepo (#30487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:20:40 +00:00
renovate[bot]
4b8f404bb3 Update playwright to v1.54.2 (#30485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:20:19 +00:00
renovate[bot]
e10b1f9222 Update nginxinc/nginx-unprivileged:alpine-slim Docker digest to e61b77b (#30482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:19:44 +00:00
renovate[bot]
ff87df4825 Update docker (#30481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 15:19:36 +00:00
Andy Balaam
c1a163cbc9 Hide recovery key when prompting for verification (#30471)
* Separate security_key_title from security_key_label since they differ in designs

See https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=92-8818&t=02JILBe2n7sx7ljU-1

In parallel with this, I have updated security_key_title in localazy.

* Hide recovery key on entry screen after login
2025-08-05 14:57:40 +00:00
Michael Telatynski
1e6f9dd096 Fix race condition in flaky reply chain test (#30479)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-05 13:06:44 +00:00
David Baker
6a8493c6eb Update for compatibility with v12 rooms (#30452)
* Update for compatibility with v12 rooms

Stop using powerLevelNorm and reading PL events manually.

To support https://github.com/matrix-org/matrix-js-sdk/pull/4937

* Add test for leave space dialog

* Don't compute stuff if we don't need it

* Use room.client

* Use getSafeUserId

* Remove client arg

* Use getJoinedMembers

and add doc

* Fix tests

* Fix more tests

* Fix other test

* Clarify comment

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-08-05 11:10:30 +00:00
ElementRobot
12927cc4a7 [create-pull-request] automated change (#30465)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-05 08:28:41 +00:00
Florian Duros
814f4a85df fix: tooltip on presence (#30474) 2025-08-04 13:54:38 +00:00
Florian Duros
475504d33b New room list: change icon and label of menu item for to start a DM (#30470)
* feat: change `New message` to `Start chat` and change icon

* feat: update the room list empty states

* test: update existing tests

* test(e2e): update playwright tests
2025-08-04 12:42:05 +00:00
Florian Duros
7faee3d1b7 New room list: add tooltip for presence and room status (#30472)
* feat: add tooltip to room avatar

* test: update snapshots
2025-08-04 11:32:32 +00:00
renovate[bot]
30e7567064 Update dependency linkifyjs to v4.3.2 [SECURITY] (#30430)
* Update dependency linkifyjs to v4.3.2 [SECURITY]

* Bump the other linkify deps

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-08-03 21:20:35 +00:00
David Langley
2250f5e6a2 Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view (#30455)
* Fix issue and add test

* Fix MemberTileView

* Add e2e test and comment
2025-08-01 13:16:13 +00:00
Will Hunt
e43b696461 Kickoff an Element Web Pro build when a new Docker image is pushed (#30451)
* Kickoff an Element Web Pro build on successful docker push

* v3
2025-08-01 12:01:10 +00:00
ElementRobot
bf98ede4fa [create-pull-request] automated change (#30456)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-08-01 06:22:21 +00:00
David Langley
cc0ece9837 Implement the member list with virtuoso (#29869)
* implement basic scrolling and keyboard navigation

* Update focus style and improve keyboard navigation

* lint

* Use avatar tootltip for the title rather than the whole button

It's more performant and feels less glitchy than the button tooltip moving around when you scroll.

* lint

* Add tooltip for invite buttons active state

As we have for other icon based buttons in the right panel/app

* Fix location of scrollToIndex and add useCallback

* Improve voiceover experience

- As well as stylng cells, set the tabIndex(roving)
- Natively focus the div with .focus() so screen reader actually moves over the cells
- improve labels and roles

* Fix jest tests

* Add aria index/counts and remove repeating "Open" string in label

* update snapshot

* Add the rest of the keyboard navigation and handle the case when the list looses focus.

* lint and update snapshot

* lint

* Only focus first/lastFocsed cell if focus.currentTarget is the overall list.

So it isn't erroneously called during onClick of an item.

* Put back overscan and fix formatting

* Extract ListView out of MemberList

* lint and fix e2e test

* Update screenshot

It looks like it is slightly better center aligned in the new list, as if maybe it was 1 px to high with the old one.

* Fix default overscan value and add ListView tests

* Just leave the avatar as it was

* We removed the tooltip that showed power level. Removing string.

* Use key rather than index to track focus.

* Remove overscan, fix typos, fix scrollToItem logic

* Use listbox role for member list and correct position/count values to account for the separator

* Fix inadvertant scrolling of the timeline when using pageUp/pageDown

* Always set the roving tab index regardless of whether we are actually focused.

Fixes the issue of not being able to shift+t

* Add aria-hidden to items within the option to avoid the SR calling it a group.

Also

* Make sure there is a roving tab set if the last one has been removed from the list.

* Update snapshot
2025-07-31 15:49:53 +00:00
Richard van der Hoff
ab6ef2fa85 Add labs option for history sharing on invite (#30313)
* Add labs option for "share history on invite"

* Set `acceptSharedHistory` when joining a room

* set `shareEncryptedHistory` when sending an invite

* Update src/i18n/strings/en_EN.json

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-31 14:20:37 +00:00
David Baker
c79c8c836b Put the 'decrypting' tooltip back (#30446)
...when downloading encrypted attachments (regressed by https://github.com/element-hq/element-web/pull/30330).

Also adds tests for the tooltips and fix the tests so they don't pollute
mocks / dialogs.
2025-07-31 14:20:33 +00:00
ElementRobot
3f0dcaa64c Playwright Docker image updates (#30406)
* [create-pull-request] automated change

* [create-pull-request] automated change

* Bump playwright-common

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-31 10:23:44 +00:00
Robin
652e891663 Stop using deprecated Element Call URL parameters (#30422)
These deprecated parameters will be removed very soon (planned for Element Call version 0.15.0) and we no longer have to care about backward compatibility with old versions of Element Call (due to the embedding/bundling work), so now is the right time to migrate.
2025-07-30 22:41:30 +00:00
Richard van der Hoff
7eb5a29cf0 Hacky fix to the MatrixChat flakiness (#30429)
Add a sleep to let these tests clean up.
2025-07-30 20:50:19 +00:00
RiotRobot
1b38624fd8 Merge branch 'master' into develop 2025-07-30 14:26:24 +00:00
RiotRobot
d98533025a v1.11.108 2025-07-30 14:22:51 +00:00
ElementRobot
c3e5367e45 Fix downloaded attachments not being decrypted (#30433) (#30434)
* Fix downloaded attachments not being decrypted

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

* Import order

(cherry picked from commit 1e15a322a5)

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-07-30 13:57:09 +00:00
David Baker
1e15a322a5 Fix downloaded attachments not being decrypted (#30433)
* Fix downloaded attachments not being decrypted

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

* Import order
2025-07-30 12:30:18 +00:00
Richard van der Hoff
452996eacf Playwright: clean up after verification test, to deflake later tests (#30425) 2025-07-30 12:11:14 +00:00
Will Hunt
ee120f2fa9 Use server name explicitly for via. (#30362)
* Use server name explicitly for via.

* lint
2025-07-29 17:40:56 +00:00
RiotRobot
94aa51dc57 Reset matrix-js-sdk back to develop branch 2025-07-29 13:08:36 +00:00
RiotRobot
e19d3dcd44 Merge branch 'master' into develop 2025-07-29 13:08:16 +00:00
RiotRobot
5a4b5418cc v1.11.107 2025-07-29 13:04:43 +00:00
RiotRobot
d1f62317ba Upgrade dependency to matrix-js-sdk@37.12.0 2025-07-29 13:01:13 +00:00
renovate[bot]
9232a220dc Update dependency filesize to v11 (#30380)
* Update dependency filesize to v11

* Update fileSize types

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-07-29 10:15:11 +00:00
Michael Telatynski
07e6f0af35 Fix test mocks
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-29 10:44:43 +01:00
Michael Telatynski
be318f9cb8 Update module API and remove jest stub
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-29 10:32:10 +01:00
Michael Telatynski
5ff42e8548 Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/module-api-1.3.0
# Conflicts:
#	yarn.lock
2025-07-29 09:49:44 +01:00
Richard van der Hoff
45a2fd9d63 Re-enable matrixchat test (#30410)
Now that we have better logging for our tests
(https://github.com/element-hq/element-web/pull/30405), I'd like to re-enable
this test so we can try and understsnd what makes it fail.
2025-07-28 21:07:20 +00:00
Richard van der Hoff
7e40e3697f MatrixChat test robustness fixes (#30413)
* MatrixChat-test: clean up better in `afterEach`

Make the MatrixChat tests behave better by letting them finish their work in an
`act` in afterEach. Otherwise we can end up mounting new components during
cleanup, which run tasks in the background

* MatrixChat-test: clean up dispatcher test

This test was kicking off a dispatcher job which would then open a
UserDeviceSettings dialog once the test had finished. That would then throw
exceptions because some of the mock environment had been torn down.

We're just testing that it opens the right dialog, so better to intercept
`createDialog`.

Aso add an `act` to reduce warnings, and replace a `flushPromises` with a
`waitFor` to make the test more robust.
2025-07-28 16:32:53 +00:00
Michael Telatynski
6599601000 Bump module API
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-28 16:03:31 +01:00
David Langley
beaabd5b44 bump wysiwyg to 2.39.0 (#30421) 2025-07-28 15:21:24 +01:00
ElementRobot
db5c69e228 Fix e2e shield being invisible in white mode for encrypted room (#30408) (#30411)
Co-authored-by: Florian Duros <florianduros@element.io>
2025-07-28 09:58:20 +01:00
Robin
a23a2c03d3 Allow Element Call to send call notifications (#30404)
* Allow Element Call to send call notifications

Currently Element Web is responsible for sending the call notification event, but this is planned to be changed soon. As of the upcoming Element Call 0.14.0 release, it will request the capability to send call notifications itself, and we should auto-approve this capability.

* Add reaction capability missing from test

Element Call does in fact request this one.
2025-07-27 13:37:10 +00:00
Richard van der Hoff
c2c040dd42 Lifecycle: add a bit more logging (#30414)
... to see what exactly it thinks is wrong with the session

This may be useful in debugging
https://github.com/element-hq/element-web/issues/30337 and
https://github.com/element-hq/element-web/issues/29708. but will likely be
useful in any case.
2025-07-25 16:17:19 +01:00
Florian Duros
c98358cb26 Fix e2e shield being invisible in white mode for encrypted room (#30408)
* fix: e2e icon for encrypted room

* test(e2e): add screenshot for grey shield in encrypted room
2025-07-25 10:56:21 +00:00
Richard van der Hoff
d384a9b71b Work around jest bug that swallows console output (#30405)
* Work around jest bug that swallows console output

Hacky workaround for https://github.com/jestjs/jest/issues/15747

* Fix unit test

* Only write logs if there are some to write

* Another test fix
2025-07-25 10:13:52 +00:00
Richard van der Hoff
fc04ad26ce Support EventShieldReason.MISMATCHED_SENDER (#30403)
The js-sdk now exposes a new event shield reason, which we should handle
correctly.
2025-07-25 09:43:40 +00:00
Michael Telatynski
6fb9098c30 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-25 09:52:27 +01:00
Michael Telatynski
9e50e59168 Fix import
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-25 09:47:19 +01:00
Florian Duros
b5160c47b3 chore: move i18n.tsx into utils folder (#30400) 2025-07-25 08:14:48 +00:00
Florian Duros
3af8273d6b fix: replace hardcoded string in poll history dialog (#30402) 2025-07-24 16:41:01 +00:00
Michael Telatynski
008b004976 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-24 16:25:22 +01:00
Michael Telatynski
68a2748813 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-23 15:22:07 +01:00
RiotRobot
acb3d31a07 v1.11.107-rc.0 2025-07-22 13:29:36 +00:00
RiotRobot
9136332f42 Upgrade dependency to matrix-js-sdk@37.12.0-rc.0 2025-07-22 13:25:38 +00:00
Michael Telatynski
61e266e8e5 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-17 17:31:41 +01:00
Michael Telatynski
a7c7c27ae8 Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/module-api-1.3.0 2025-07-17 16:16:23 +01:00
Michael Telatynski
1414b8ff54 Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/module-api-1.3.0 2025-07-16 14:56:15 +01:00
Michael Telatynski
c6a5d288ed Add missing import
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-07 12:49:35 +01:00
Michael Telatynski
8c2a035302 Add support for Module API 1.3.0
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-07-07 12:49:06 +01:00
302 changed files with 4837 additions and 1487 deletions

View File

@@ -37,14 +37,14 @@ jobs:
install: true
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # 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@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
@@ -96,7 +96,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
if: github.event_name != 'pull_request'
with:
images: |
@@ -139,3 +139,16 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: vectorim/element-web
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
if: github.event_name != 'pull_request'
with:
repository: element-hq/element-web-pro
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
event-type: image-built
# Stable way to determine the :version
client-payload: |-
{
"base-ref": "${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}"
}

View File

@@ -5,7 +5,7 @@ import "../res/css/shared.pcss";
import "./preview.css";
import React, { useLayoutEffect } from "react";
import { FORCE_RE_RENDER } from "storybook/internal/core-events";
import { setLanguage } from "../src/shared-components/i18n";
import { setLanguage } from "../src/shared-components/utils/i18n";
import { TooltipProvider } from "@vector-im/compound-web";
export const globalTypes = {

View File

@@ -70,5 +70,13 @@ module.exports = {
],
},
],
"property-no-deprecated": [
true,
{
ignoreProperties: ["-webkit-box-orient", "word-wrap"],
},
],
"nesting-selector-no-missing-scoping-root": null,
"no-invalid-position-declaration": null,
},
};

View File

@@ -1,3 +1,29 @@
Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30)
====================================================================================================
## 🐛 Bug Fixes
* [Backport staging] Fix downloaded attachments not being decrypted ([#30434](https://github.com/element-hq/element-web/pull/30434)). Contributed by @RiotRobot.
Changes in [1.11.107](https://github.com/element-hq/element-web/releases/tag/v1.11.107) (2025-07-29)
====================================================================================================
## ✨ Features
* Message preview should show tooltip with the full message on hover ([#30265](https://github.com/element-hq/element-web/pull/30265)). Contributed by @MidhunSureshR.
* Support rendering notification badges on platforms that do their own icon overlays ([#30315](https://github.com/element-hq/element-web/pull/30315)). Contributed by @Half-Shot.
* Add SubscriptionViewModel base class ([#30297](https://github.com/element-hq/element-web/pull/30297)). Contributed by @dbkr.
* Enhancement: Save image on CTRL+S ([#30330](https://github.com/element-hq/element-web/pull/30330)). Contributed by @ioalexander.
* Add quote functionality to MessageContextMenu (#29893) ([#30323](https://github.com/element-hq/element-web/pull/30323)). Contributed by @AlirezaMrtz.
* Initial structure for shared component views ([#30216](https://github.com/element-hq/element-web/pull/30216)). Contributed by @dbkr.
## 🐛 Bug Fixes
* [Backport staging] Fix e2e shield being invisible in white mode for encrypted room ([#30411](https://github.com/element-hq/element-web/pull/30411)). Contributed by @RiotRobot.
* Force ED titlebar color for new room list ([#30332](https://github.com/element-hq/element-web/pull/30332)). Contributed by @florianduros.
* Add a background color to left panel for macos titlebar in element desktop ([#30328](https://github.com/element-hq/element-web/pull/30328)). Contributed by @florianduros.
* Fix: Prevent page refresh on Enter key in right panel member search ([#30312](https://github.com/element-hq/element-web/pull/30312)). Contributed by @AlirezaMrtz.
Changes in [1.11.106](https://github.com/element-hq/element-web/releases/tag/v1.11.106) (2025-07-15)
====================================================================================================
## ✨ Features

View File

@@ -1,7 +1,7 @@
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
# Builder
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a AS builder
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 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,7 @@ RUN /src/scripts/docker-package.sh
RUN cp /src/config.sample.json /src/webapp/config.json
# App
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
# Need root user to install packages & manipulate the usr directory
USER root

View File

@@ -40,8 +40,6 @@ const config: Config = {
"^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
// Requires ESM which is incompatible with our current Jest setup
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
},
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
collectCoverageFrom: [

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.106",
"version": "1.11.108",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -73,10 +73,10 @@
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
},
"resolutions": {
"**/pretty-format/react-is": "19.1.0",
"@playwright/test": "1.54.1",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"**/pretty-format/react-is": "19.1.1",
"@playwright/test": "1.54.2",
"@types/react": "19.1.9",
"@types/react-dom": "19.1.7",
"oidc-client-ts": "3.3.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001724",
@@ -86,7 +86,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@element-hq/element-web-module-api": "1.3.0",
"@element-hq/element-web-module-api": "1.4.1",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
@@ -94,12 +94,12 @@
"@matrix-org/emojibase-bindings": "^1.3.4",
"@matrix-org/react-sdk-module-api": "^2.4.0",
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^9.0.0",
"@sentry/browser": "^10.0.0",
"@types/png-chunks-extract": "^1.0.2",
"@types/react-virtualized": "^9.21.30",
"@vector-im/compound-design-tokens": "^5.0.0",
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.1.2",
"@vector-im/matrix-wysiwyg": "2.38.4",
"@vector-im/matrix-wysiwyg": "2.39.0",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
@@ -117,7 +117,7 @@
"emojibase-regex": "15.3.2",
"escape-html": "^1.0.3",
"file-saver": "^2.0.5",
"filesize": "10.1.6",
"filesize": "11.0.2",
"github-markdown-css": "^5.5.1",
"glob-to-regexp": "^0.4.1",
"highlight.js": "^11.3.1",
@@ -128,9 +128,9 @@
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-react": "4.3.1",
"linkify-string": "4.3.1",
"linkifyjs": "4.3.1",
"linkify-react": "4.3.2",
"linkify-string": "4.3.2",
"linkifyjs": "4.3.2",
"lodash": "^4.17.21",
"maplibre-gl": "^5.0.0",
"matrix-encrypt-attachment": "^1.0.3",
@@ -154,6 +154,7 @@
"react-string-replace": "^1.1.1",
"react-transition-group": "^4.4.1",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.12.6",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "2.17.0",
@@ -185,8 +186,8 @@
"@babel/preset-typescript": "^7.12.7",
"@babel/runtime": "^7.12.5",
"@casualbot/jest-sonar-reporter": "2.2.7",
"@element-hq/element-call-embedded": "0.13.1",
"@element-hq/element-web-playwright-common": "^1.4.3",
"@element-hq/element-call-embedded": "0.14.1",
"@element-hq/element-web-playwright-common": "^1.4.4",
"@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "^1.50.1",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
@@ -222,9 +223,9 @@
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "19.1.8",
"@types/react": "19.1.9",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "19.1.6",
"@types/react-dom": "19.1.7",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.16.0",
"@types/semver": "^7.5.8",
@@ -299,8 +300,8 @@
"semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"storybook": "^9.0.12",
"stylelint": "^16.13.0",
"stylelint-config-standard": "^38.0.0",
"stylelint": "^16.23.0",
"stylelint-config-standard": "^39.0.0",
"stylelint-scss": "^6.0.0",
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9",

View File

@@ -19,6 +19,7 @@ const clickButtonReply = async (tile: Locator) => {
await tile.hover();
await tile.getByRole("button", { name: "Reply", exact: true }).click();
}).toPass();
await expect(tile.page().getByText("Replying", { exact: true })).toBeVisible();
};
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
@@ -39,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
// wait for the tile to finish loading
await expect(
page
.locator(".mx_AudioPlayer_mediaName")
.getByTestId("audio-player-name")
.last()
.filter({ hasText: file.split("/").at(-1) }),
).toBeVisible();
@@ -54,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
// Check that the audio player is rendered and its button becomes visible
const checkPlayerVisibility = async (locator: Locator) => {
// Assert that the audio player and media information are visible
const mediaInfo = locator.locator(
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo",
);
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
const mediaInfo = locator.getByRole("region", { name: "Audio player" });
await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension
await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration
await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size;
// Assert that the play button can be found and is visible
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
@@ -78,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
}
// Check the status of the seek bar
expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0);
expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0);
// Enable IRC layout
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
@@ -100,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
display: none !important;
}
`,
mask: [page.locator(".mx_AudioPlayer_seek")],
mask: [page.getByTestId("audio-player-seek")],
};
// Take a snapshot of mx_EventTile_last on IRC layout
@@ -186,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/1sec.ogg");
// Assert that the audio player is rendered
const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container");
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
// Assert that the counter is zero before clicking the play button
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(container.getByRole("timer")).toHaveText("00:00");
// Find and click "Play" button, the wait is to make the test less flaky
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
@@ -198,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
// Assert that the timer is reset when the audio file finished playing
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(container.getByRole("timer")).toHaveText("00:00");
// Assert that "Play" button can be found
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
@@ -226,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/1sec.ogg");
// Assert the audio player is rendered
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
// Find and click "Reply" button on MessageActionBar
const tile = page.locator(".mx_EventTile_last");
@@ -236,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/1sec.ogg");
// Assert that the audio player is rendered
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
// Assert that replied audio file is rendered as file button inside ReplyChain
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
@@ -261,7 +260,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
// Assert that the audio player is rendered
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
await expect(
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
).toBeVisible();
await clickButtonReply(tile);
@@ -269,7 +270,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
// Assert that the audio player is rendered
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
await expect(
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
).toBeVisible();
await clickButtonReply(tile);
@@ -277,7 +280,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
// Assert that the audio player is rendered
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
// Assert that there are two "mx_ReplyChain" elements
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
@@ -313,7 +316,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
// On the main timeline
const messageList = page.locator(".mx_RoomView_MessageList");
// Assert the audio player is rendered
await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
await expect(
messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
).toBeVisible();
// Find and click "Reply in thread" button
await messageList.locator(".mx_EventTile_last").hover();
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
@@ -321,10 +326,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
// On a thread
const thread = page.locator(".mx_ThreadView");
const threadTile = thread.locator(".mx_EventTile_last");
const audioPlayer = threadTile.locator(".mx_AudioPlayer_container");
const audioPlayer = threadTile.getByRole("region", { name: "Audio player" });
// Assert that the counter is zero before clicking the play button
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
// Find and click "Play" button, the wait is to make the test less flaky
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
@@ -334,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
// Assert that the timer is reset when the audio file finished playing
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
// Assert that "Play" button can be found
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();

View File

@@ -28,7 +28,7 @@ test.describe("Composer", () => {
test.describe("CIDER", () => {
test("sends a message when you click send or press Enter", async ({ page }) => {
const composer = page.getByRole("textbox", { name: "Send a message…" });
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
// Type a message
await composer.pressSequentially("my message 0");
@@ -52,7 +52,7 @@ test.describe("Composer", () => {
});
test("can write formatted text", async ({ page }) => {
const composer = page.getByRole("textbox", { name: "Send a message…" });
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
await composer.pressSequentially("my bold");
await composer.press(`${CtrlOrMeta}+KeyB`);
@@ -68,7 +68,7 @@ test.describe("Composer", () => {
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
});
@@ -79,7 +79,7 @@ test.describe("Composer", () => {
});
test("only sends when you press Control+Enter", async ({ page }) => {
const composer = page.getByRole("textbox", { name: "Send a message…" });
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
// Type a message and press Enter
await composer.pressSequentially("my message 3");
await composer.press("Enter");

View File

@@ -91,10 +91,10 @@ test.describe("Key backup reset from elsewhere", () => {
await csAPI.deleteBackupVersion(backupInfo.version);
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession");
await page.getByRole("button", { name: "Send message" }).click();
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
await page.getByRole("textbox", { name: "Send a message…" }).fill("Message with broken key backup");
await page.getByRole("button", { name: "Send message" }).click();
// Should be the message we sent plus the room creation event

View File

@@ -154,10 +154,12 @@ test.describe("Cryptography", function () {
await app.client.bootstrapCrossSigning(aliceCredentials);
await startDMWithBob(page, bob);
// send first message
await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!");
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter");
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!");
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
await checkDMRoom(page);
const bobRoomId = await bobJoin(page, bob);
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toMatchScreenshot("composer-e2e-icon-normal.png");
await testMessages(page, bob, bobRoomId);
await verify(app, bob);

View File

@@ -124,6 +124,10 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
const toasts = new Toasts(page);
await toasts.rejectToast("Notifications");
await toasts.assertNoToasts();
// There may still be a `/sendToDevice/m.secret.request` in flight, which will later throw an error and cause
// a *subsequent* test to fail. Tell playwright to ignore any errors resulting from in-flight routes.
await page.unrouteAll({ behavior: "ignoreErrors" });
});
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
@@ -205,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
const dialog = page.locator(".mx_Dialog");
// 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.getByTitle("Recovery key").pressSequentially(recoveryKey);
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click();

View File

@@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
await useSecurityKey.click();
}
// Fill in the recovery key
await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
await page.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click();
}
@@ -263,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("textarea").fill(securityKey);
await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
await app.page.getByRole("button", { name: "Done" }).click();
await app.settings.closeDialog();

View File

@@ -30,6 +30,10 @@ test.describe("Lazy Loading", () => {
});
test.beforeEach(async ({ page, homeserver, user, bot, app }) => {
// The charlies were running off the bottom of the screen.
// We no longer overscan the member list so the result is they are not in the dom.
// Increase the viewport size to ensure they are.
await page.setViewportSize({ width: 1000, height: 1000 });
for (let i = 1; i <= 10; i++) {
const displayName = `Charly #${i}`;
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });

View File

@@ -35,8 +35,8 @@ test.describe("Header section of the room list", () => {
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
// New message should open the direct messages dialog
await page.getByRole("menuitem", { name: "New message" }).click();
// Start chat should open the direct messages dialog
await page.getByRole("menuitem", { name: "Start chat" }).click();
await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
await app.closeDialog();

View File

@@ -13,7 +13,7 @@ import { type Locator, type Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
async function sendMessage(page: Page, message: string): Promise<Locator> {
await page.getByRole("textbox", { name: "Send a message…" }).fill(message);
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message);
await page.getByRole("button", { name: "Send message" }).click();
const msgTile = page.locator(".mx_EventTile_last");
@@ -22,7 +22,7 @@ async function sendMessage(page: Page, message: string): Promise<Locator> {
}
async function sendMultilineMessages(page: Page, messages: string[]) {
await page.getByRole("textbox", { name: "Send a message…" }).focus();
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus();
for (let i = 0; i < messages.length; i++) {
await page.keyboard.type(messages[i]);
if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
@@ -40,7 +40,7 @@ async function replyMessage(page: Page, message: Locator, replyMessage: string):
await line.hover();
await line.getByRole("button", { name: "Reply", exact: true }).click();
await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage);
await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage);
await page.getByRole("button", { name: "Send message" }).click();
const msgTile = page.locator(".mx_EventTile_last");

View File

@@ -29,7 +29,7 @@ test.describe("Pills", () => {
// send a message using the built-in room mention functionality (autocomplete)
await page
.getByRole("textbox", { name: "Send a message…" })
.getByRole("textbox", { name: "Send an unencrypted message…" })
.pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
await page.locator(".mx_Autocomplete_Completion_title").click();
await page.getByRole("button", { name: "Send message" }).click();

View File

@@ -63,9 +63,7 @@ test.describe("FilePanel", () => {
await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
// Assert that the audio player is rendered
await expect(
roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
).toBeVisible();
await expect(roomViewBody.getByRole("region", { name: "Audio player" })).toBeVisible();
// Assert that the file button exists
await expect(
@@ -97,9 +95,7 @@ test.describe("FilePanel", () => {
await expect(image.locator("img[alt='riot.png']")).toBeVisible();
// Detect the audio file
const audio = filePanelMessageList.locator(
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
);
const audio = filePanelMessageList.getByRole("region", { name: "Audio player" });
// Assert that the play button is rendered
await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
@@ -130,7 +126,7 @@ test.describe("FilePanel", () => {
// Take a snapshot of file tiles list on FilePanel
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
// Exclude timestamps & flaky seek bar from snapshot
mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")],
mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")],
});
});
@@ -138,21 +134,19 @@ test.describe("FilePanel", () => {
// Upload an image file
await uploadFile(page, "playwright/sample-files/1sec.ogg");
const audioBody = page.locator(
".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
);
const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" });
// Assert that the audio player is rendered
// Assert that the audio file information is rendered
const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
// Assert that the audio file information is rendered;
await expect(audioBody.getByText("1sec.ogg")).toBeVisible(); // extension
await expect(audioBody.getByRole("time")).toHaveText("00:01"); // duration
await expect(audioBody.getByText("(3.56 KB)")).toBeVisible(); // actual size;
// Assert that the duration counter is 00:01 before clicking the play button
await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();
await expect(audioBody.getByRole("time")).toHaveText("00:01");
// Assert that the counter is zero before clicking the play button
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
// Click the play button
await audioBody.getByRole("button", { name: "Play" }).click();
@@ -161,7 +155,7 @@ test.describe("FilePanel", () => {
await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
// Assert that the timer is reset when the audio file finished playing
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
// Assert that the play button is rendered
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();

View File

@@ -11,6 +11,32 @@ import { Bot } from "../../pages/bot";
const ROOM_NAME = "Test room";
const NAME = "Alice";
async function setupRoomWithMembers(
app: any,
page: any,
homeserver: any,
roomName: string,
memberNames: string[],
): Promise<string> {
const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public);
const id = await app.client.createRoom({ name: roomName, visibility });
const bots: Bot[] = [];
for (let i = 0; i < memberNames.length; i++) {
const displayName = memberNames[i];
const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false });
if (displayName === "Susan") {
await bot.prepareClient();
await app.client.inviteUser(id, bot.credentials?.userId);
} else {
await bot.joinRoom(id);
}
bots.push(bot);
}
return id;
}
test.use({
synapseConfig: {
presence: {
@@ -25,17 +51,8 @@ test.use({
test.describe("Memberlist", () => {
test.beforeEach(async ({ app, user, page, homeserver }, testInfo) => {
testInfo.setTimeout(testInfo.timeout + 30_000);
const id = await app.client.createRoom({ name: ROOM_NAME });
const newBots: Bot[] = [];
const names = ["Bob", "Bob", "Susan"];
for (let i = 0; i < 3; i++) {
const displayName = names[i];
const autoAcceptInvites = displayName !== "Susan";
const bot = new Bot(page, homeserver, { displayName, startClient: true, autoAcceptInvites });
await bot.prepareClient();
await app.client.inviteUser(id, bot.credentials?.userId);
newBots.push(bot);
}
await setupRoomWithMembers(app, page, homeserver, ROOM_NAME, names);
});
test("Renders correctly", { tag: "@screenshot" }, async ({ page, app }) => {
@@ -45,4 +62,37 @@ test.describe("Memberlist", () => {
await expect(memberlist.getByText("Invited")).toHaveCount(1);
await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
});
test("should handle scroll and click to view member profile", async ({ page, app, homeserver }) => {
// Create a room with many members to enable scrolling
const memberNames = Array.from({ length: 15 }, (_, i) => `Member${i.toString()}`);
await setupRoomWithMembers(app, page, homeserver, "Large Room", memberNames);
// Navigate to the room and open member list
await app.viewRoomByName("Large Room");
const memberlist = await app.toggleMemberlistPanel();
// Get the scrollable container
const memberListContainer = memberlist.locator(".mx_AutoHideScrollbar");
// Scroll down to the bottom of the member list
await app.scrollListToBottom(memberListContainer);
// Wait for the target member to be visible after scrolling
const targetName = "Member14";
const targetMember = memberlist.locator(".mx_MemberTileView_name").filter({ hasText: targetName });
await targetMember.waitFor({ state: "visible" });
// Verify Alice is not visible at this point
await expect(memberlist.locator(".mx_MemberTileView_name").filter({ hasText: "Alice" })).toHaveCount(0);
// Click on a member near the bottom of the list
await expect(targetMember).toBeVisible();
await targetMember.click();
// Verify that the user info screen is shown and hasn't scrolled back to top
await expect(page.locator(".mx_UserInfo")).toBeVisible();
await expect(page.locator(".mx_UserInfo_profile").getByText(targetName)).toBeVisible();
});
});

View File

@@ -35,7 +35,7 @@ test.describe("Share dialog", () => {
const rightPanel = await app.toggleRoomInfoPanel();
await rightPanel.getByRole("menuitem", { name: "People" }).click();
await rightPanel.getByRole("button", { name: `${user.userId} (power 100)` }).click();
await rightPanel.getByRole("option", { name: user.displayName }).click();
await rightPanel.getByRole("button", { name: "Share profile" }).click();
const dialog = page.getByRole("dialog", { name: "Share User" });

View File

@@ -23,7 +23,7 @@ async function openSpaceContextMenu(page: Page, app: ElementAppPage, spaceName:
return page.locator(".mx_SpacePanel_contextMenu");
}
function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
function spaceCreateOptions(serverName: string, spaceName: string, roomIds: string[] = []): ICreateRoomOpts {
return {
creation_content: {
type: "m.space",
@@ -35,17 +35,21 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
name: spaceName,
},
},
...roomIds.map((r) => spaceChildInitialState(r)),
...roomIds.map((r) => spaceChildInitialState(serverName, r)),
],
};
}
function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
function spaceChildInitialState(
serverName: string,
roomId: string,
order?: string,
): ICreateRoomOpts["initial_state"]["0"] {
return {
type: "m.space.child",
state_key: roomId,
content: {
via: [roomId.split(":")[1]],
via: [serverName],
order,
},
};
@@ -240,7 +244,7 @@ test.describe("Spaces", () => {
});
await expect(await app.getSpacePanelButton("My Space")).toBeVisible();
const roomId = await bot.createRoom(spaceCreateOptions("Space Space"));
const roomId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Space Space"));
await bot.inviteUser(roomId, user.userId);
// Assert that `Space Space` is above `My Space` due to it being an invite
@@ -260,7 +264,10 @@ test.describe("Spaces", () => {
const spaceName = "Spacey Mc. Space Space";
await app.client.createSpace({
name: spaceName,
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
initial_state: [
spaceChildInitialState(user.homeServer, roomId1),
spaceChildInitialState(user.homeServer, roomId2),
],
});
await app.viewSpaceHomeByName(spaceName);
@@ -287,7 +294,7 @@ test.describe("Spaces", () => {
});
await app.client.createSpace({
name: "Root Space",
initial_state: [spaceChildInitialState(childSpaceId)],
initial_state: [spaceChildInitialState(user.homeServer, childSpaceId)],
});
// Find collapsed Space panel
@@ -323,7 +330,7 @@ test.describe("Spaces", () => {
name: "Test Room",
topic: "This is a topic https://github.com/matrix-org/matrix-react-sdk/pull/10060 with a link",
});
const spaceId = await bot.createRoom(spaceCreateOptions("Test Space", [roomId]));
const spaceId = await bot.createRoom(spaceCreateOptions(user.homeServer, "Test Space", [roomId]));
await bot.inviteUser(spaceId, user.userId);
await expect(await app.getSpacePanelButton("Test Space")).toBeVisible();
@@ -361,9 +368,9 @@ test.describe("Spaces", () => {
await app.client.createSpace({
name: "Root Space",
initial_state: [
spaceChildInitialState(childSpaceId1, "a"),
spaceChildInitialState(childSpaceId2, "b"),
spaceChildInitialState(childSpaceId3, "c"),
spaceChildInitialState(user.homeServer, childSpaceId1, "a"),
spaceChildInitialState(user.homeServer, childSpaceId2, "b"),
spaceChildInitialState(user.homeServer, childSpaceId3, "c"),
],
});
await app.viewSpaceByName("Root Space");

View File

@@ -30,7 +30,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
await result.first().click();
// send first message to start DM
const locator = page.getByRole("textbox", { name: "Send a message…" });
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
await expect(locator).toBeFocused();
await locator.fill("Hey!");
await locator.press("Enter");
@@ -260,7 +260,7 @@ test.describe("Spotlight", () => {
// Send first message to actually start DM
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
const locator = page.getByRole("textbox", { name: "Send a message…" });
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
await locator.fill("Hey!");
await locator.press("Enter");

View File

@@ -43,7 +43,7 @@ test.describe("Threads", () => {
const roomViewLocator = page.locator(".mx_RoomView_body");
// User sends message
const textbox = roomViewLocator.getByRole("textbox", { name: "Send a message…" });
const textbox = roomViewLocator.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill("Hello Mr. Bot");
await textbox.press("Enter");
@@ -108,7 +108,7 @@ test.describe("Threads", () => {
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
// User responds in thread
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
await locator.fill("Test");
await locator.press("Enter");
@@ -262,7 +262,7 @@ test.describe("Threads", () => {
await locator.locator(".mx_EventTile_line").click();
// User responds & asserts
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
await locator.fill("Great!");
await locator.press("Enter");
@@ -335,8 +335,8 @@ test.describe("Threads", () => {
// Send message
const locator = page.locator(".mx_RoomView_body");
await locator.getByRole("textbox", { name: "Send a message…" }).fill("Hello Mr. Bot");
await locator.getByRole("textbox", { name: "Send a message…" }).press("Enter");
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
// Create thread
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
await locator2.hover();
@@ -366,7 +366,7 @@ test.describe("Threads", () => {
let locator = page.locator(".mx_RoomView_body");
// User sends message
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill("Hello Mr. Bot");
await textbox.press("Enter");
// Wait for message to send, get its ID and save as @threadId
@@ -395,7 +395,7 @@ test.describe("Threads", () => {
locator = page.locator(".mx_ThreadView");
await locator.locator(".mx_EventTile_last").hover();
await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click();
textbox = locator.getByRole("textbox", { name: "Reply to thread…" });
textbox = locator.getByRole("textbox", { name: "Reply to unencrypted thread…" });
await textbox.fill("Please come here");
await textbox.press("Enter");
// Wait until the reply is sent
@@ -414,7 +414,7 @@ test.describe("Threads", () => {
// Send message
let locator = page.locator(".mx_RoomView_body");
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill("Hello Mr. Bot");
await textbox.press("Enter");
// Create thread
@@ -425,7 +425,7 @@ test.describe("Threads", () => {
// Send message to thread
locator = page.locator(".mx_ThreadPanel");
textbox = locator.getByRole("textbox", { name: "Send a message…" });
textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill("Hello Mr. User");
await textbox.press("Enter");
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
@@ -456,7 +456,7 @@ test.describe("Threads", () => {
*/
const sendMessage = async (message: string) => {
const messageComposer = page.getByRole("region", { name: "Message composer" });
const textbox = messageComposer.getByRole("textbox", { name: "Send a message…" });
const textbox = messageComposer.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill(message);
await textbox.press("Enter");
};
@@ -478,7 +478,7 @@ test.describe("Threads", () => {
// Send a message in the thread
const threadPanel = page.locator(".mx_ThreadPanel");
const textbox = threadPanel.getByRole("textbox", { name: "Send a message…" });
const textbox = threadPanel.getByRole("textbox", { name: "Send an unencrypted message…" });
await textbox.fill(threadMessage);
await textbox.press("Enter");
await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible();

View File

@@ -461,11 +461,11 @@ test.describe("Timeline", () => {
// Send a emote
await page
.locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" })
.getByRole("textbox", { name: "Send an unencrypted message…" })
.fill("/me says hello to Mr. Bot");
await page
.locator(".mx_RoomView_body")
.getByRole("textbox", { name: "Send a message…" })
.getByRole("textbox", { name: "Send an unencrypted message…" })
.press("Enter");
// Check inline start margin of its avatar
// Here --right-padding is for the avatar on the message line

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 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: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
const TAG = "develop@sha256:8c2d9a93dd209a79d3e5e50cd18addfe52d80bea0ffe48a5d3e15836032eeb9d";
const TAG = "develop@sha256:a0d8db97e39321166959cecdc4c684daff36fefdc7968de47be7e0fdb1cb4541";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,

View File

@@ -95,7 +95,6 @@
@import "./structures/auth/_Registration.pcss";
@import "./structures/auth/_SessionLockStolenView.pcss";
@import "./structures/auth/_SetupEncryptionBody.pcss";
@import "./views/audio_messages/_AudioPlayer.pcss";
@import "./views/audio_messages/_PlayPauseButton.pcss";
@import "./views/audio_messages/_PlaybackContainer.pcss";
@import "./views/audio_messages/_SeekBar.pcss";

View File

@@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details.
display: grid;
justify-content: left;
align-items: center;
grid-gap: $spacing-8;
gap: $spacing-8;
grid-template-columns: auto auto auto;
grid-template-rows: auto;
cursor: pointer;

View File

@@ -22,7 +22,7 @@ Please see LICENSE files in the repository root for full details.
display: grid;
justify-content: left;
align-items: center;
grid-gap: $spacing-8;
gap: $spacing-8;
grid-template-columns: min-content 1fr min-content;
grid-template-rows: auto;
}
@@ -47,7 +47,7 @@ Please see LICENSE files in the repository root for full details.
.mx_PollListItemEnded_answers {
display: grid;
grid-gap: $spacing-8;
gap: $spacing-8;
margin-top: $spacing-12;
}

View File

@@ -19,7 +19,7 @@ Please see LICENSE files in the repository root for full details.
.mx_DeviceDetailHeading_renameForm {
display: grid;
grid-gap: $spacing-16;
gap: $spacing-16;
justify-content: left;
grid-template-columns: 100%;
}

View File

@@ -23,7 +23,7 @@ Please see LICENSE files in the repository root for full details.
border-bottom: 1px solid $quinary-content;
display: grid;
grid-gap: $spacing-24;
gap: $spacing-24;
justify-content: left;
grid-template-columns: 100%;

View File

@@ -35,7 +35,7 @@ Please see LICENSE files in the repository root for full details.
.mx_DeviceTile_actions {
display: grid;
grid-gap: $spacing-8;
gap: $spacing-8;
grid-auto-flow: column;
margin-left: $spacing-8;
}

View File

@@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details.
.mx_FilteredDeviceList_list {
list-style-type: none;
display: grid;
grid-gap: $spacing-16;
gap: $spacing-16;
margin: 0;
padding: 0 $spacing-16;
}

View File

@@ -39,7 +39,7 @@ Please see LICENSE files in the repository root for full details.
.mx_SettingsSubsection_content {
width: 100%;
display: grid;
grid-gap: $spacing-8;
gap: $spacing-8;
/* setting minwidth 0 makes columns definitely sized fixing horizontal overflow */
grid-template-columns: minmax(0, 1fr);
justify-items: flex-start;

View File

@@ -1,59 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 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.
*/
.mx_MediaBody.mx_AudioPlayer_container {
padding: 16px 12px 12px 12px;
.mx_AudioPlayer_primaryContainer {
display: flex;
.mx_PlayPauseButton {
margin-right: 8px;
}
.mx_AudioPlayer_mediaInfo {
flex: 1;
overflow: hidden; /* makes the ellipsis on the file name work */
& > * {
display: block;
}
.mx_AudioPlayer_mediaName {
color: $primary-content;
font-size: $font-15px;
line-height: $font-15px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-bottom: 4px; /* mimics the line-height differences in the Figma */
}
.mx_AudioPlayer_byline {
font-size: $font-12px;
line-height: $font-12px;
}
}
}
.mx_AudioPlayer_seek {
display: flex;
align-items: center;
.mx_SeekBar {
flex: 1;
}
.mx_Clock {
min-width: $font-42px; /* for flexbox */
padding-left: $spacing-4; /* isolate from seek bar */
text-align: justify;
white-space: nowrap;
}
}
}

View File

@@ -15,6 +15,23 @@ Please see LICENSE files in the repository root for full details.
}
.mx_AccessSecretStorageDialog_primaryContainer {
.mx_AccessSecretStorageDialog_recoveryKeyEntry {
/*
* Be specific here to avoid "margin: 9px" from _common.pcss
*/
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) {
input {
/*
* From figma: https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=102-43729&t=QmewENUd7f6Tmw9U-1
*/
width: 448px;
height: 70px;
margin: 0px;
border: 1px solid;
}
}
}
.mx_AccessSecretStorageDialog_recoveryKeyFeedback {
&::before {
content: "";

View File

@@ -60,7 +60,7 @@ Please see LICENSE files in the repository root for full details.
.mx_MPollBody_allOptions {
display: grid;
grid-gap: $spacing-16;
gap: $spacing-16;
margin-bottom: $spacing-8;
max-width: 550px;
}

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