diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 34431799f4..bf8ea6e6ef 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,6 +20,7 @@ # Ignore translations as those will be updated by GHA for Localazy download /src/i18n/strings /src/i18n/strings/en_EN.json @element-hq/element-web-reviewers -# Ignore the synapse plugin as this is updated by GHA for docker image updating +# Ignore the synapse & mas plugins as this is updated by GHA for docker image updating /playwright/testcontainers/synapse.ts +/playwright/testcontainers/mas.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68b9f4e703..7d3cfcf72f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: diff --git a/.github/workflows/build_debian.yaml b/.github/workflows/build_debian.yaml index 247e5604ee..42c0dec22c 100644 --- a/.github/workflows/build_debian.yaml +++ b/.github/workflows/build_debian.yaml @@ -14,7 +14,7 @@ jobs: R2_URL: ${{ vars.CF_R2_S3_API }} VERSION: ${{ github.ref_name }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Download package run: | diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 9550bf9139..5a7bf7630d 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -26,7 +26,7 @@ jobs: R2_URL: ${{ vars.CF_R2_S3_API }} R2_PUBLIC_URL: "https://element-web-develop.element.io" steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b52e6764c..55542bea78 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,7 +34,7 @@ jobs: env: SITE: ${{ inputs.site || 'staging.element.io' }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Load GPG key run: | diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index e7d06e75cf..fe38d06326 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -20,7 +20,7 @@ jobs: env: TEST_TAG: vectorim/element-web:test steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 # needed for docker-package to be able to calculate the version diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e7d69cf477..456fa4761a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,18 +17,18 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Fetch element-desktop - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: repository: element-hq/element-desktop path: element-desktop - name: Fetch element-web - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: path: element-web - name: Fetch matrix-js-sdk - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: repository: matrix-org/matrix-js-sdk path: matrix-js-sdk diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/end-to-end-tests-netlify.yaml index 90a8c3d24b..1cd257b34d 100644 --- a/.github/workflows/end-to-end-tests-netlify.yaml +++ b/.github/workflows/end-to-end-tests-netlify.yaml @@ -25,7 +25,7 @@ jobs: actions: read steps: - name: Download HTML report - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index 5901d7a700..8e790d0fde 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -50,7 +50,7 @@ jobs: runners-matrix: ${{ steps.runner-vars.outputs.matrix }} steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: repository: element-hq/element-web @@ -129,13 +129,13 @@ jobs: - runAllTests: false project: Pinecone steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: persist-credentials: false repository: element-hq/element-web - name: 📥 Download artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: webapp path: webapp @@ -154,7 +154,7 @@ 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@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 id: playwright-cache with: path: ~/.cache/ms-playwright @@ -201,7 +201,7 @@ jobs: if: always() runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 if: inputs.skip != true with: persist-credentials: false @@ -219,7 +219,7 @@ jobs: - name: Download blob reports from GitHub Actions Artifacts if: inputs.skip != true - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: pattern: all-blob-reports-* path: all-blob-reports diff --git a/.github/workflows/netlify.yaml b/.github/workflows/netlify.yaml index a7909265c7..05f7dbf3b1 100644 --- a/.github/workflows/netlify.yaml +++ b/.github/workflows/netlify.yaml @@ -28,7 +28,7 @@ jobs: Exercise caution. Use test accounts. - name: 📥 Download artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml index 4cbdb17bbd..f9488eff64 100644 --- a/.github/workflows/playwright-image-updates.yaml +++ b/.github/workflows/playwright-image-updates.yaml @@ -10,7 +10,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Update synapse image run: | @@ -21,6 +21,15 @@ jobs: env: IMAGE: ghcr.io/element-hq/synapse:develop + - name: Update MAS image + run: | + docker pull "$IMAGE" + INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE") + DIGEST=${INSPECT#*@} + sed -i "s/const TAG.*/const TAG = \"main@$DIGEST\";/" playwright/testcontainers/mas.ts + env: + IMAGE: ghcr.io/element-hq/matrix-authentication-service:main + - name: Create Pull Request id: cpr uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7 diff --git a/.github/workflows/release_prepare.yml b/.github/workflows/release_prepare.yml index 2f36644c2e..5517f2171d 100644 --- a/.github/workflows/release_prepare.yml +++ b/.github/workflows/release_prepare.yml @@ -41,7 +41,7 @@ jobs: REPOS: matrix-js-sdk element-web element-desktop steps: - name: Checkout Element Desktop - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 if: inputs.matrix-js-sdk with: repository: matrix-org/matrix-js-sdk diff --git a/.github/workflows/shared-component-visual-tests-netlify.yaml b/.github/workflows/shared-component-visual-tests-netlify.yaml index eadaffbea5..3313ecf03a 100644 --- a/.github/workflows/shared-component-visual-tests-netlify.yaml +++ b/.github/workflows/shared-component-visual-tests-netlify.yaml @@ -27,7 +27,7 @@ jobs: run: "sudo apt-get install -y tree" - name: Download Diffs - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/shared-component-visual-tests.yaml b/.github/workflows/shared-component-visual-tests.yaml index 98d258349e..180d6bcbab 100644 --- a/.github/workflows/shared-component-visual-tests.yaml +++ b/.github/workflows/shared-component-visual-tests.yaml @@ -21,7 +21,7 @@ jobs: issues: read pull-requests: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: persist-credentials: false repository: element-hq/element-web @@ -39,7 +39,7 @@ 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@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 id: playwright-cache with: path: ~/.cache/ms-playwright diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index d63e0da8ed..c4aa773070 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -23,7 +23,7 @@ jobs: name: "Typescript Syntax Check" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -52,12 +52,13 @@ jobs: error|misconfigured welcome_to_element devtools|settings|elementCallUrl + labs|sliding_sync_description rethemendex_lint: name: "Rethemendex Check" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - run: ./res/css/rethemendex.sh @@ -67,7 +68,7 @@ jobs: name: "ESLint" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -85,7 +86,7 @@ jobs: name: "Style Lint" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -103,7 +104,7 @@ jobs: name: "Workflow Lint" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -121,7 +122,7 @@ jobs: name: "Analyse Dead Code" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a17e457252..a6f3b3ae70 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: runner: [1, 2] steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }} @@ -55,7 +55,7 @@ jobs: JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }} - name: Jest Cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 with: path: /tmp/jest_cache key: ${{ hashFiles('**/yarn.lock') }} diff --git a/.github/workflows/update-jitsi.yml b/.github/workflows/update-jitsi.yml index da386c544d..0a764699e7 100644 --- a/.github/workflows/update-jitsi.yml +++ b/.github/workflows/update-jitsi.yml @@ -9,7 +9,7 @@ jobs: update: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 36dea524a0..01fa572689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +Changes in [1.11.109](https://github.com/element-hq/element-web/releases/tag/v1.11.109) (2025-08-11) +==================================================================================================== +This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms. + +## ✨ Features + +* [Backport staging] Allow /upgraderoom command without developer mode enabled ([#30529](https://github.com/element-hq/element-web/pull/30529)). Contributed by @RiotRobot. +* [Backport staging] Support for creator/owner power level ([#30526](https://github.com/element-hq/element-web/pull/30526)). Contributed by @RiotRobot. +* New room list: change icon and label of menu item for to start a DM ([#30470](https://github.com/element-hq/element-web/pull/30470)). Contributed by @florianduros. +* Implement the member list with virtuoso ([#29869](https://github.com/element-hq/element-web/pull/29869)). Contributed by @langleyd. +* Add labs option for history sharing on invite ([#30313](https://github.com/element-hq/element-web/pull/30313)). Contributed by @richvdh. +* Bump wysiwyg to 2.39.0 adding support for pasting rich text content in the Rich Text Edtior ([#30421](https://github.com/element-hq/element-web/pull/30421)). Contributed by @langleyd. +* Support `EventShieldReason.MISMATCHED_SENDER` ([#30403](https://github.com/element-hq/element-web/pull/30403)). Contributed by @richvdh. +* Change unencrypted and public pills to blue ([#30399](https://github.com/element-hq/element-web/pull/30399)). Contributed by @florianduros. +* Change color of public room icon ([#30390](https://github.com/element-hq/element-web/pull/30390)). Contributed by @florianduros. +* Script for updating storybook screenshots ([#30340](https://github.com/element-hq/element-web/pull/30340)). Contributed by @dbkr. +* Add toggle to hide empty state in devtools ([#30352](https://github.com/element-hq/element-web/pull/30352)). Contributed by @toger5. + +## 🐛 Bug Fixes + +* [Backport staging] Use userId to filter users in non-federated rooms when showing the InviteDialog ([#30537](https://github.com/element-hq/element-web/pull/30537)). Contributed by @RiotRobot. +* [Backport staging] Catch error when encountering invalid m.room.pinned\_events event ([#30536](https://github.com/element-hq/element-web/pull/30536)). Contributed by @RiotRobot. +* Update for compatibility with v12 rooms ([#30452](https://github.com/element-hq/element-web/pull/30452)). Contributed by @dbkr. +* New room list: fix tooltip on presence ([#30474](https://github.com/element-hq/element-web/pull/30474)). Contributed by @florianduros. +* New room list: add tooltip for presence and room status ([#30472](https://github.com/element-hq/element-web/pull/30472)). Contributed by @florianduros. +* Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view ([#30455](https://github.com/element-hq/element-web/pull/30455)). Contributed by @langleyd. +* Put the 'decrypting' tooltip back ([#30446](https://github.com/element-hq/element-web/pull/30446)). Contributed by @dbkr. +* Use server name explicitly for via. ([#30362](https://github.com/element-hq/element-web/pull/30362)). Contributed by @Half-Shot. +* fix: replace hardcoded string in poll history dialog ([#30402](https://github.com/element-hq/element-web/pull/30402)). Contributed by @florianduros. +* fix: replace hardcoded string on qr code back button ([#30401](https://github.com/element-hq/element-web/pull/30401)). Contributed by @florianduros. +* Fix color of icon button with outline ([#30361](https://github.com/element-hq/element-web/pull/30361)). Contributed by @florianduros. + + Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30) ==================================================================================================== ## 🐛 Bug Fixes diff --git a/Dockerfile b/Dockerfile index 172a03b229..a4599cc7f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5 # Builder -FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder +FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:9e34ba52e1f3c31ed9bd4d0bcf784f5909db17cda61c220e29c8d7a8ebfb402e 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:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35 +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:ea6c4b8b568824ea94cd1fabd47e1c4e7c0c04744f344a3793f7e9c8ac3a3636 # Need root user to install packages & manipulate the usr directory USER root diff --git a/jest.config.ts b/jest.config.ts index 459bcf5f08..3403ad6a0c 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -40,8 +40,6 @@ const config: Config = { "^!!raw-loader!.*": "jest-raw-loader", "recorderWorkletFactory": "/__mocks__/empty.js", "^fetch-mock$": "/node_modules/fetch-mock", - // Requires ESM which is incompatible with our current Jest setup - "^@element-hq/element-web-module-api$": "/__mocks__/empty.js", }, transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"], collectCoverageFrom: [ diff --git a/package.json b/package.json index a4ef8e9c33..aa21689838 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.108", + "version": "1.11.109", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { @@ -75,7 +75,7 @@ "resolutions": { "**/pretty-format/react-is": "19.1.1", "@playwright/test": "1.54.2", - "@types/react": "19.1.9", + "@types/react": "19.1.10", "@types/react-dom": "19.1.7", "oidc-client-ts": "3.3.0", "jwt-decode": "4.0.0", @@ -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", @@ -96,7 +96,6 @@ "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^10.0.0", "@types/png-chunks-extract": "^1.0.2", - "@types/react-virtualized": "^9.21.30", "@vector-im/compound-design-tokens": "^6.0.0", "@vector-im/compound-web": "^8.1.2", "@vector-im/matrix-wysiwyg": "2.39.0", @@ -143,7 +142,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.257.0", + "posthog-js": "1.260.1", "qrcode": "1.5.4", "re-resizable": "6.11.2", "react": "^19.0.0", @@ -153,8 +152,7 @@ "react-focus-lock": "^2.5.1", "react-string-replace": "^1.1.1", "react-transition-group": "^4.4.1", - "react-virtualized": "^9.22.5", - "react-virtuoso": "^4.12.6", + "react-virtuoso": "^4.14.0", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", "sanitize-html": "2.17.0", @@ -187,7 +185,7 @@ "@babel/runtime": "^7.12.5", "@casualbot/jest-sonar-reporter": "2.2.7", "@element-hq/element-call-embedded": "0.14.1", - "@element-hq/element-web-playwright-common": "^1.4.4", + "@element-hq/element-web-playwright-common": "^1.4.6", "@peculiar/webcrypto": "^1.4.3", "@playwright/test": "^1.50.1", "@principalstudio/html-webpack-inject-preload": "^1.2.7", @@ -223,7 +221,7 @@ "@types/node-fetch": "^2.6.2", "@types/pako": "^2.0.0", "@types/qrcode": "^1.3.5", - "@types/react": "19.1.9", + "@types/react": "19.1.10", "@types/react-beautiful-dnd": "^13.0.0", "@types/react-dom": "19.1.7", "@types/react-transition-group": "^4.4.0", diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts index 2602f1d02e..668fd20021 100644 --- a/playwright/e2e/crypto/crypto.spec.ts +++ b/playwright/e2e/crypto/crypto.spec.ts @@ -158,7 +158,8 @@ test.describe("Cryptography", function () { 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"); + // We no longer show the grey badge in the composer, check that it is not there. + await expect(page.locator(".mx_MessageComposer_e2eIcon")).toHaveCount(0); await testMessages(page, bob, bobRoomId); await verify(app, bob); diff --git a/playwright/e2e/crypto/event-shields.spec.ts b/playwright/e2e/crypto/event-shields.spec.ts index e577a66467..326ee2521f 100644 --- a/playwright/e2e/crypto/event-shields.spec.ts +++ b/playwright/e2e/crypto/event-shields.spec.ts @@ -58,108 +58,108 @@ test.describe("Cryptography", function () { await app.client.network.setupRoute(); }); - test("should show the correct shield on e2e events", async ({ - page, - app, - bot: bob, - homeserver, - }, workerInfo) => { - // Bob has a second, not cross-signed, device - const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); + test( + "should show the correct shield on e2e events", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver }, workerInfo) => { + // Bob has a second, not cross-signed, device + const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); - // 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(); + // 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", - ciphertext: "the bird is in the hand", - }); + await bob.sendEvent(testRoomId, null, "m.room.encrypted", { + algorithm: "m.megolm.v1.aes-sha2", + ciphertext: "the bird is in the hand", + }); - const last = page.locator(".mx_EventTile_last"); - await expect(last).toContainText("Unable to decrypt message"); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "This message could not be decrypted", - ); + const last = page.locator(".mx_EventTile_last"); + await expect(last).toContainText("Unable to decrypt message"); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); + await lastE2eIcon.focus(); + await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( + "This message could not be decrypted", + ); - /* Should show a red padlock for an unencrypted message in an e2e room */ - await bob.evaluate( - (cli, testRoomId) => - cli.http.authedRequest( - window.matrixcs.Method.Put, - `/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`, - undefined, - { - msgtype: "m.text", - body: "test unencrypted", - }, - ), - testRoomId, - ); + /* Should show a red padlock for an unencrypted message in an e2e room */ + await bob.evaluate( + (cli, testRoomId) => + cli.http.authedRequest( + window.matrixcs.Method.Put, + `/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`, + undefined, + { + msgtype: "m.text", + body: "test unencrypted", + }, + ), + testRoomId, + ); - await expect(last).toContainText("test unencrypted"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted"); + await expect(last).toContainText("test unencrypted"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png"); + await lastE2eIcon.focus(); + await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted"); - /* Should show no padlock for an unverified user */ - // bob sends a valid event - await bob.sendMessage(testRoomId, "test encrypted 1"); + /* Should show no padlock for an unverified user */ + // bob sends a valid event + await bob.sendMessage(testRoomId, "test encrypted 1"); - // the message should appear, decrypted, with no warning, but also no "verified" - const lastTile = page.locator(".mx_EventTile_last"); - const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); - await expect(lastTile).toContainText("test encrypted 1"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); + // the message should appear, decrypted, with no warning, but also no "verified" + const lastTile = page.locator(".mx_EventTile_last"); + const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); + await expect(lastTile).toContainText("test encrypted 1"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); - /* Now verify Bob */ - await verify(app, bob); + /* Now verify Bob */ + await verify(app, bob); - /* Existing message should be updated when user is verified. */ - await expect(last).toContainText("test encrypted 1"); - // still no e2e icon - await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); + /* Existing message should be updated when user is verified. */ + await expect(last).toContainText("test encrypted 1"); + // still no e2e icon + await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); - /* should show no padlock, and be verified, for a message from a verified device */ - await bob.sendMessage(testRoomId, "test encrypted 2"); + /* should show no padlock, and be verified, for a message from a verified device */ + await bob.sendMessage(testRoomId, "test encrypted 2"); - await expect(lastTile).toContainText("test encrypted 2"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); + await expect(lastTile).toContainText("test encrypted 2"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); - /* should show red padlock for a message from an unverified device */ - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); - await expect(lastTile).toContainText("test encrypted from unverified"); - await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastTileE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText( - "Encrypted by a device not verified by its owner.", - ); + /* should show red padlock for a message from an unverified device */ + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); + await expect(lastTile).toContainText("test encrypted from unverified"); + await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await lastTileE2eIcon.focus(); + await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText( + "Encrypted by a device not verified by its owner.", + ); - /* Should show a red padlock for a message from an unverified device. - * Rust crypto remembers the verification state of the sending device, so it will know that the device was - * unverified, even if it gets deleted. */ - // bob deletes his second device - await bobSecondDevice.evaluate((cli) => cli.logout(true)); + /* Should show a red padlock for a message from an unverified device. + * Rust crypto remembers the verification state of the sending device, so it will know that the device was + * unverified, even if it gets deleted. */ + // bob deletes his second device + await bobSecondDevice.evaluate((cli) => cli.logout(true)); - // wait for the logout to propagate. - await waitForDevices(app, bob.credentials.userId, 1); + // wait for the logout to propagate. + await waitForDevices(app, bob.credentials.userId, 1); - // close and reopen the room, to get the shield to update. - await app.viewRoomByName("Bob"); - await app.viewRoomByName("TestRoom"); + // close and reopen the room, to get the shield to update. + await app.viewRoomByName("Bob"); + await app.viewRoomByName("TestRoom"); - await expect(last).toContainText("test encrypted from unverified"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "Encrypted by a device not verified by its owner.", - ); - }); + await expect(last).toContainText("test encrypted from unverified"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await lastE2eIcon.focus(); + await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( + "Encrypted by a device not verified by its owner.", + ); + }, + ); test("Should show a grey padlock for a key restored from backup", async ({ page, diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index 0e92e734c5..09dc010d3b 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -68,7 +68,7 @@ test.describe("Room list filters and sort", () => { 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" }); + const oldRoomTile = roomListView.getByRole("option", { name: "Open room Old Room" }); await expect(oldRoomTile).toBeVisible(); /* @@ -139,8 +139,9 @@ test.describe("Room list filters and sort", () => { // Open the non-favourite room const roomListView = getRoomList(page); - const tile = roomListView.getByRole("gridcell", { name: "Open room room-non-fav" }); - await tile.scrollIntoViewIfNeeded(); + const tile = roomListView.getByRole("option", { name: "Open room room-non-fav" }); + // item may not be in the DOM using scrollListToBottom rather than scrollIntoViewIfNeeded + await app.scrollListToBottom(roomListView); await tile.click(); // Enable Favourite filter @@ -151,7 +152,7 @@ test.describe("Room list filters and sort", () => { // Ensure the room list is not scrolled const isScrolledDown = await page - .getByRole("grid", { name: "Room list" }) + .getByRole("listbox", { name: "Room list", exact: true }) .evaluate((e) => e.scrollTop !== 0); expect(isScrolledDown).toStrictEqual(false); }); @@ -227,37 +228,37 @@ test.describe("Room list filters and sort", () => { 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(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(4); + await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").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(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(2); + await expect(roomList.getByRole("option", { name: "unread dm" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").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(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(5); + await expect(roomList.getByRole("option", { name: "unread room" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "empty room" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible(); + await expect(roomList.getByRole("option", { name: "Low prio room" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").count()).toBe(5); await getFilterExpandButton(page).click(); await primaryFilters.getByRole("option", { name: "Favourite" }).click(); - await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1); + await expect(roomList.getByRole("option", { name: "favourite room" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").count()).toBe(1); await primaryFilters.getByRole("option", { name: "Mentions" }).click(); - await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1); + await expect(roomList.getByRole("option", { name: "room with mention" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").count()).toBe(1); await primaryFilters.getByRole("option", { name: "Invites" }).click(); - await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible(); - await expect.poll(() => roomList.locator("role=gridcell").count()).toBe(1); + await expect(roomList.getByRole("option", { name: "invited room" })).toBeVisible(); + await expect.poll(() => roomList.locator("role=option").count()).toBe(1); await getFilterCollapseButton(page).click(); await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites"); @@ -268,6 +269,7 @@ test.describe("Room list filters and sort", () => { { tag: "@screenshot" }, async ({ page, app, bot }) => { const roomListView = getRoomList(page); + const primaryFilters = getPrimaryFilters(page); // Let's configure unread dm room so that we only get notification for mentions and keywords await app.viewRoomById(unReadDmId); @@ -276,20 +278,20 @@ test.describe("Room list filters and sort", () => { 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(); + await roomListView.getByRole("option", { 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(); + await primaryFilters.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" }); + const unreadDm = roomListView.getByRole("option", { 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(); + await expect(roomListView.getByRole("option", { name: "Open room unread dm" })).not.toBeVisible(); }, ); @@ -299,7 +301,7 @@ test.describe("Room list filters and sort", () => { await getRoomOptionsMenu(page).click(); await page.getByRole("menuitemradio", { name: "A-Z" }).click(); - await expect(roomListView.getByRole("gridcell").first()).toHaveText(/empty room/); + await expect(roomListView.getByRole("option").first()).toHaveText(/empty room/); }); test("should move room to the top on message when sorting by activity", async ({ page, bot }) => { @@ -307,7 +309,7 @@ test.describe("Room list filters and sort", () => { await bot.sendMessage(unReadDmId, "Hello!"); - await expect(roomListView.getByRole("gridcell").first()).toHaveText(/unread dm/); + await expect(roomListView.getByRole("option").first()).toHaveText(/unread dm/); }); }); diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts index d0503e2caf..bc1387cbce 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts @@ -38,7 +38,7 @@ test.describe("Room list panel", () => { test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => { const roomListView = getRoomListView(page); // Wait for the last room to be visible - await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible(); + await expect(roomListView.getByRole("option", { name: "Open room room19" })).toBeVisible(); await expect(roomListView).toMatchScreenshot("room-list-panel.png"); }); diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index a91a0c38d0..2d62737b85 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -43,31 +43,35 @@ test.describe("Room list", () => { 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.getByRole("option", { 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(); + await roomListView.getByRole("option", { name: "Open room room29" }).click(); // Scroll to the end of the room list - await app.scrollListToBottom(page.locator(".mx_RoomList_List")); + await app.scrollListToBottom(roomListView); + + // scrollListToBottom seems to leave the mouse hovered over the list, move it away. + await page.getByRole("button", { name: "User menu" }).hover(); + 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 roomListView.getByRole("option", { name: "Open room room29" }).click(); await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible(); }); test("should open the context menu", { tag: "@screenshot" }, async ({ page, app, user }) => { const roomListView = getRoomList(page); - await roomListView.getByRole("gridcell", { name: "Open room room29" }).click({ button: "right" }); + await roomListView.getByRole("option", { name: "Open room room29" }).click({ button: "right" }); await expect(page.getByRole("menu", { name: "More Options" })).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" }); + const roomItem = roomListView.getByRole("option", { name: "Open room room29" }); await roomItem.hover(); await expect(roomItem).toMatchScreenshot("room-list-item-hover.png"); @@ -97,7 +101,7 @@ test.describe("Room list", () => { 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" }); + const roomItem = roomListView.getByRole("option", { name: "Open room room29" }); await roomItem.hover(); await expect(roomItem).toMatchScreenshot("room-list-item-hover.png"); @@ -117,10 +121,10 @@ test.describe("Room list", () => { await expect(roomItem.getByTestId("notification-decoration")).not.toBeVisible(); // Put focus on the room list - await roomListView.getByRole("gridcell", { name: "Open room room28" }).click(); + await roomListView.getByRole("option", { name: "Open room room28" }).click(); // Scroll to the end of the room list - await app.scrollListToBottom(page.locator(".mx_RoomList_List")); + await app.scrollListToBottom(roomListView); // The room decoration should have the muted icon await expect(roomItem.getByTestId("notification-decoration")).toBeVisible(); @@ -139,25 +143,25 @@ test.describe("Room list", () => { 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(); + await roomListView.getByRole("option", { name: "Open room room29" }).click(); // Scroll to the end of the room list - await app.scrollListToBottom(page.locator(".mx_RoomList_List")); + await app.scrollListToBottom(roomListView); - await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible(); - await roomListView.getByRole("gridcell", { name: "Open room room0" }).click(); + await expect(roomListView.getByRole("option", { name: "Open room room0" })).toBeVisible(); + await roomListView.getByRole("option", { 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 expect(roomListView.getByRole("option", { name: "Open room room0" })).not.toBeVisible(); await filters.getByRole("option", { name: "People" }).click(); - await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible(); + await expect(roomListView.getByRole("option", { 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 roomListView.getByRole("option", { name: "Open room room29" }).click(); await page.keyboard.press("Alt+ArrowDown"); await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible(); @@ -165,7 +169,7 @@ test.describe("Room list", () => { test("should select the previous room", async ({ page, app, user }) => { const roomListView = getRoomList(page); - await roomListView.getByRole("gridcell", { name: "Open room room28" }).click(); + await roomListView.getByRole("option", { name: "Open room room28" }).click(); await page.keyboard.press("Alt+ArrowUp"); await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible(); @@ -173,7 +177,7 @@ test.describe("Room list", () => { test("should select the last room", async ({ page, app, user }) => { const roomListView = getRoomList(page); - await roomListView.getByRole("gridcell", { name: "Open room room29" }).click(); + await roomListView.getByRole("option", { name: "Open room room29" }).click(); await page.keyboard.press("Alt+ArrowUp"); await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible(); @@ -187,7 +191,7 @@ test.describe("Room list", () => { await bot.joinRoom(roomId); await bot.sendMessage(roomId, "I am a robot. Beep."); - await roomListView.getByRole("gridcell", { name: "Open room room20" }).click(); + await roomListView.getByRole("option", { name: "Open room room20" }).click(); await page.keyboard.press("Alt+Shift+ArrowDown"); @@ -199,8 +203,8 @@ test.describe("Room list", () => { 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" }); + const room29 = roomListView.getByRole("option", { name: "Open room room29" }); + const room28 = roomListView.getByRole("option", { name: "Open room room28" }); // open the room await room29.click(); @@ -219,7 +223,7 @@ test.describe("Room list", () => { 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 room29 = roomListView.getByRole("option", { name: "Open room room29" }); const moreButton = room29.getByRole("button", { name: "More options" }); const notificationButton = room29.getByRole("button", { name: "Notification options" }); @@ -258,7 +262,7 @@ test.describe("Room list", () => { await page.getByRole("button", { name: "User menu" }).focus(); const roomListView = getRoomList(page); - const publicRoom = roomListView.getByRole("gridcell", { name: "public room" }); + const publicRoom = roomListView.getByRole("option", { name: "public room" }); await expect(publicRoom).toBeVisible(); await expect(publicRoom).toMatchScreenshot("room-list-item-public.png"); @@ -268,7 +272,7 @@ test.describe("Room list", () => { // @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" }); + const publicRoom = roomListView.getByRole("option", { name: "low priority room" }); // Make room low priority await publicRoom.hover(); @@ -293,7 +297,7 @@ test.describe("Room list", () => { await page.getByRole("button", { name: "Create video room" }).click(); const roomListView = getRoomList(page); - const videoRoom = roomListView.getByRole("gridcell", { name: "video room" }); + const videoRoom = roomListView.getByRole("option", { name: "video room" }); // focus the user menu to avoid to have hover decoration await page.getByRole("button", { name: "User menu" }).focus(); @@ -312,7 +316,7 @@ test.describe("Room list", () => { invite: [user.userId], is_direct: true, }); - const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" }); + const invitedRoom = roomListView.getByRole("option", { name: "invited room" }); await expect(invitedRoom).toBeVisible(); await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png"); }); @@ -327,7 +331,7 @@ test.describe("Room list", () => { 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" }); + const room = roomListView.getByRole("option", { name: "2 notifications" }); await expect(room).toBeVisible(); await expect(room.getByTestId("notification-decoration")).toHaveText("2"); await expect(room).toMatchScreenshot("room-list-item-notification.png"); @@ -358,7 +362,7 @@ test.describe("Room list", () => { ); await bot.sendMessage(roomId, "I am a robot. Beep."); - const room = roomListView.getByRole("gridcell", { name: "mention" }); + const room = roomListView.getByRole("option", { name: "mention" }); await expect(room).toBeVisible(); await expect(room).toMatchScreenshot("room-list-item-mention.png"); }); @@ -379,7 +383,7 @@ test.describe("Room list", () => { await bot.joinRoom(roomId); await bot.sendMessage(roomId, "I am a robot. Beep."); - const room = roomListView.getByRole("gridcell", { name: "activity" }); + const room = roomListView.getByRole("option", { name: "activity" }); await expect(room.getByText("I am a robot. Beep.")).toBeVisible(); await expect(room).toMatchScreenshot("room-list-item-message-preview.png"); }); @@ -406,7 +410,7 @@ test.describe("Room list", () => { await app.viewRoomById(otherRoomId); await bot.sendMessage(roomId, "I am a robot. Beep."); - const room = roomListView.getByRole("gridcell", { name: "activity" }); + const room = roomListView.getByRole("option", { name: "activity" }); await expect(room.getByTestId("notification-decoration")).toBeVisible(); await expect(room).toMatchScreenshot("room-list-item-activity.png"); }); @@ -418,7 +422,7 @@ test.describe("Room list", () => { await app.client.inviteUser(roomId, bot.credentials.userId); await bot.joinRoom(roomId); - const room = roomListView.getByRole("gridcell", { name: "mark as unread" }); + const room = roomListView.getByRole("option", { name: "mark as unread" }); await room.hover(); await room.getByRole("button", { name: "More Options" }).click(); await page.getByRole("menuitem", { name: "mark as unread" }).click(); @@ -441,7 +445,7 @@ test.describe("Room list", () => { await page.getByText("Off").click(); await app.settings.closeDialog(); - const room = roomListView.getByRole("gridcell", { name: "silent" }); + const room = roomListView.getByRole("option", { name: "silent" }); await expect(room.getByTestId("notification-decoration")).toBeVisible(); await expect(room).toMatchScreenshot("room-list-item-silent.png"); }); diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts index 8b49942dd3..acb23c0a81 100644 --- a/playwright/e2e/oidc/oidc-native.spec.ts +++ b/playwright/e2e/oidc/oidc-native.spec.ts @@ -95,10 +95,6 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { const result = await mas.manage("kill-sessions", userId); expect(result.output).toContain("Ended 1 active OAuth 2.0 session"); - // Workaround for Synapse's 2 minute cache on MAS token validity - // (https://github.com/element-hq/synapse/pull/18231) - await homeserver.restart(); - await page.goto("http://localhost:8080"); await expect( page.getByText("For security, this session has been signed out. Please sign in again."), diff --git a/playwright/e2e/right-panel/file-panel.spec.ts b/playwright/e2e/right-panel/file-panel.spec.ts index f6d89511b7..5a45beb095 100644 --- a/playwright/e2e/right-panel/file-panel.spec.ts +++ b/playwright/e2e/right-panel/file-panel.spec.ts @@ -6,7 +6,7 @@ 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 Download, type Page } from "@playwright/test"; +import { type Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { viewRoomSummaryByName } from "./utils"; @@ -189,23 +189,13 @@ test.describe("FilePanel", () => { const link = imageBody.locator(".mx_MFileBody_download a"); - const newPagePromise = context.waitForEvent("page"); - - const downloadPromise = new Promise((resolve) => { - page.once("download", resolve); - }); + const downloadPromise = page.waitForEvent("download"); // Click the anchor link (not the image itself) await link.click(); - const newPage = await newPagePromise; - // XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading - await expect(newPage) - .toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/) - .catch(async () => { - const download = await downloadPromise; - expect(download.suggestedFilename()).toBe("riot.png"); - }); + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe("riot.png"); }); }); }); diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts index 78e37cd4d2..5f14bfc85f 100644 --- a/playwright/e2e/room/room-header.spec.ts +++ b/playwright/e2e/room/room-header.spec.ts @@ -37,11 +37,8 @@ test.describe("Room Header", () => { await expect(header.locator(".mx_FacePile")).toBeVisible(); // There should be both a voice and a video call button - // but they'll be disabled - const callButtons = header.getByRole("button", { name: "There's no one here to call" }); - await expect(callButtons).toHaveCount(2); - await expect(callButtons.first()).toBeVisible(); - await expect(callButtons.last()).toBeVisible(); + await expect(header.getByRole("button", { name: "Video call" })).toBeVisible(); + await expect(header.getByRole("button", { name: "Voice call" })).toBeVisible(); await expect(header.getByRole("button", { name: "Threads" })).toBeVisible(); await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible(); diff --git a/playwright/e2e/share-dialog/share-by-url.spec.ts b/playwright/e2e/share-dialog/share-by-url.spec.ts index c5b9174782..49024d79d9 100644 --- a/playwright/e2e/share-dialog/share-by-url.spec.ts +++ b/playwright/e2e/share-dialog/share-by-url.spec.ts @@ -18,13 +18,14 @@ test.describe("share from URL", () => { test("should share message when users navigates to share URL", async ({ page, user, room, app }) => { await page.goto("/#/share?msg=Hello+world"); + const dialog = page.getByRole("dialog", { name: "Forward message" }); // 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 dialog.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click(); + await dialog.getByRole("button", { name: "Close" }).click(); await app.viewRoomByName("A test room"); const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last"); await expect(lastMessage).toBeVisible(); diff --git a/playwright/plugins/homeserver/synapse/masHomeserver.ts b/playwright/plugins/homeserver/synapse/masHomeserver.ts index 342737d80d..84d73018fc 100644 --- a/playwright/plugins/homeserver/synapse/masHomeserver.ts +++ b/playwright/plugins/homeserver/synapse/masHomeserver.ts @@ -1,38 +1,49 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024-2025 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 { MatrixAuthenticationServiceContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers"; - +import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts"; import { type Fixtures } from "../../../element-web-test.ts"; export const masHomeserver: Fixtures = { mas: [ async ({ _homeserver: homeserver, logger, network, postgres, mailpit }, use) => { - const config = { - clients: [ - { - client_id: "0000000000000000000SYNAPSE", - client_auth_method: "client_secret_basic", - client_secret: "SomeRandomSecret", - }, - ], - matrix: { - homeserver: "localhost", - secret: "AnotherRandomSecret", - endpoint: "http://homeserver:8008", - }, - }; + const secret = "AnotherRandomSecret"; + const limits = { burst: 10, per_second: 10 }; const container = await new MatrixAuthenticationServiceContainer(postgres) .withNetwork(network) .withNetworkAliases("mas") .withLogConsumer(logger.getConsumer("mas")) - .withConfig(config) + .withConfig({ + matrix: { + kind: "synapse", + homeserver: "localhost", + secret, + endpoint: "http://homeserver:8008", + }, + rate_limiting: { + login: { + per_ip: limits, + per_account: limits, + }, + registration: limits, + email_authentication: { + per_ip: limits, + per_address: limits, + emails_per_session: limits, + attempt_per_session: limits, + }, + account_recovery: { + per_ip: limits, + per_address: limits, + }, + }, + }) .start(); homeserver.withConfig({ @@ -40,16 +51,10 @@ export const masHomeserver: Fixtures = { enable_registration_without_verification: undefined, disable_msisdn_registration: undefined, password_config: undefined, - experimental_features: { - msc3861: { - enabled: true, - issuer: `http://mas:8080/`, - introspection_endpoint: "http://mas:8080/oauth2/introspect", - client_id: config.clients[0].client_id, - client_auth_method: config.clients[0].client_auth_method, - client_secret: config.clients[0].client_secret, - admin_token: config.matrix.secret, - }, + matrix_authentication_service: { + enabled: true, + endpoint: "http://mas:8080/", + secret, }, }); @@ -59,28 +64,6 @@ export const masHomeserver: Fixtures = { { scope: "worker" }, ], - config: async ({ homeserver, context, mas }, use) => { - const issuer = `${mas.baseUrl}/`; - const wellKnown = { - "m.homeserver": { - base_url: homeserver.baseUrl, - }, - "org.matrix.msc2965.authentication": { - issuer, - account: `${issuer}account`, - }, - }; - - // Ensure org.matrix.msc2965.authentication is in well-known - await context.route("https://localhost/.well-known/matrix/client", async (route) => { - await route.fulfill({ json: wellKnown }); - }); - - await use({ - default_server_config: wellKnown, - }); - }, - context: async ({ homeserverType, context }, use, testInfo) => { testInfo.skip(homeserverType !== "synapse", "does not yet support MAS"); await use(context); diff --git a/playwright/snapshots/crypto/crypto.spec.ts/composer-e2e-icon-normal-linux.png b/playwright/snapshots/crypto/crypto.spec.ts/composer-e2e-icon-normal-linux.png deleted file mode 100644 index 70a3795fc9..0000000000 Binary files a/playwright/snapshots/crypto/crypto.spec.ts/composer-e2e-icon-normal-linux.png and /dev/null differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-warning-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-warning-linux.png new file mode 100644 index 0000000000..ef893148ad Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-warning-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png index 86cb11aad9..f463282be7 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png index 13577e0a1b..6968a4f134 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png index 1e0add7b56..3795176be2 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png index 0e363b3747..f21e92a373 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png index bc1aa9f4f1..ac0ee1ad6c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index 1315363e7e..a5403f2d01 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png index b400beac7c..963caeacda 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png index 44d90bac34..b024c0729c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png index fe5ef29ecf..ce53a39e88 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png index 86032973a3..ee778c6871 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png index b134c90d3a..4b3c7fc1d1 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png index 8970c20cb5..7042a7078e 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png index 0c99720d01..4a92165c46 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index 511d6b246b..4c29ce00e2 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index 85b47b0f12..648222b325 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 185b1581ba..2b53b40113 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png index 65683ec463..ad64e0c526 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png index cf8bb5a058..d9deb6cb1c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png index e6d3d50e93..c3d6f5f952 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png index 8fab88fc7f..896af9eff7 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png index 85a30b02f2..81f3af0cb6 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png differ diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png index b7101622f5..39cee36ed7 100644 Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png differ diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png index 66e755231f..857047d319 100644 Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png differ diff --git a/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png b/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png index c4c7cb9b0d..d33c711add 100644 Binary files a/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png and b/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png differ diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png index ec0207a227..a9f601e822 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png differ diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png index cb0bc78e00..526797f712 100644 Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png index 663bd9c9a7..020c4cd17c 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png index 416edb59f8..88e30a9ef5 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png index 28ecd15484..37145a59b5 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png index 86179e2de2..73d7f4a53f 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png index 474dd2e1d7..f8a651c958 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png index d13de053ff..9ba34dea00 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png index 35215eab83..e4ec380f87 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png index 3dc1539094..45c1c5f4d9 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png index d210cc58a9..945e5d074b 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png index 5290094b1f..41c0c6010c 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png index c65216a418..cd2b806409 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png index 9b3c124739..7cb6c886e3 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png index c66b0fd8c5..548d2b0f92 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png index 64c6586e22..360a316068 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png index 19b75f03b8..fd2a51857c 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png index 07399c19e3..d5a1b0cbcc 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png index 37683b4a2f..5e4e7dadf0 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png index 9b995aaf17..c0b5f579f4 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png index c65216a418..cd2b806409 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png index 386940c6d9..bf8a5ee3fb 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png index 9c3d9782ef..d41a9f6935 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png index 633ee3bacf..cdeb0a6c0b 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png index 6235ecb8cf..32a680f065 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png differ diff --git a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png index a4f6a476f6..df1a991e9a 100644 Binary files a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png and b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png differ diff --git a/playwright/testcontainers/mas.ts b/playwright/testcontainers/mas.ts new file mode 100644 index 0000000000..1063b91f7d --- /dev/null +++ b/playwright/testcontainers/mas.ts @@ -0,0 +1,24 @@ +/* +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 { + MatrixAuthenticationServiceContainer as BaseMatrixAuthenticationServiceContainer, + type StartedPostgreSqlContainer, +} from "@element-hq/element-web-playwright-common/lib/testcontainers"; + +const TAG = "main@sha256:e1004d0213a985064766c99df344b0ac799869aff503be5aba1a720478258873"; + +/** + * MatrixAuthenticationServiceContainer which freezes the docker digest to + * stabilise tests, updated periodically by the `playwright-image-updates.yaml` + * workflow. + */ +export class MatrixAuthenticationServiceContainer extends BaseMatrixAuthenticationServiceContainer { + public constructor(db: StartedPostgreSqlContainer) { + super(db, `ghcr.io/element-hq/matrix-authentication-service:${TAG}`); + } +} diff --git a/playwright/testcontainers/synapse.ts b/playwright/testcontainers/synapse.ts index e0129de139..ed3d5a273d 100644 --- a/playwright/testcontainers/synapse.ts +++ b/playwright/testcontainers/synapse.ts @@ -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:b67c77a7ccb360afa16f037243d52920a47fc0648f7bbdc76ff551ec75fc2b6f"; +const TAG = "develop@sha256:78b32934c4a7a616ada0a0af6e6ba3a102f97ff747fc0d2d1e3bd930e16e529e"; /** * SynapseContainer which freezes the docker digest to stabilise tests, diff --git a/res/css/_components.pcss b/res/css/_components.pcss index b140fd1d7e..602885546e 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -142,6 +142,7 @@ @import "./views/dialogs/_GenericFeatureFeedbackDialog.pcss"; @import "./views/dialogs/_IncomingSasDialog.pcss"; @import "./views/dialogs/_InviteDialog.pcss"; +@import "./views/dialogs/_InviteProgressBody.pcss"; @import "./views/dialogs/_JoinRuleDropdown.pcss"; @import "./views/dialogs/_LeaveSpaceDialog.pcss"; @import "./views/dialogs/_LocationViewDialog.pcss"; diff --git a/res/css/_font-sizes.pcss b/res/css/_font-sizes.pcss index 528cc3c462..98ebf28af0 100644 --- a/res/css/_font-sizes.pcss +++ b/res/css/_font-sizes.pcss @@ -12,31 +12,39 @@ Please see LICENSE files in the repository root for full details. * These are defined in `rem` so that they scale with the `font-size` of the root element (which is adjustable via the * "Font size" setting). They exist to make the job of converting designs (which tend to be based in pixels) into CSS * easier. + */ + +/* + * These variables are now *deprecated* and should not be used in new code; instead Compound typographic tokens + * should be used. Direct equivalents for these old font size tokens are listed below; where no equivalent exists, + * that suggests that the design is using a non-standard font size and should be updated. * + * In fact, modern Figma designs should actually use a named Typography style such as "Web/font/heading/sm/semibold", + * translates directly to `font: var(--cpd-font-heading-sm-semibold)`. */ $font-1px: 0.0625rem; $font-8px: 0.5rem; $font-9px: 0.5625rem; $font-10px: 0.625rem; $font-10-4px: 0.6275rem; -$font-11px: 0.6875rem; +$font-11px: 0.6875rem; /* Compound equivalent: --cpd-font-size-body-xs */ $font-12px: 0.75rem; -$font-13px: 0.8125rem; +$font-13px: 0.8125rem; /* Compound equivalent: --cpd-font-size-body-sm */ $font-14px: 0.875rem; -$font-15px: 0.9375rem; +$font-15px: 0.9375rem; /* Compound equivalent: --cpd-font-size-body-md */ $font-16px: 1rem; -$font-17px: 1.0625rem; +$font-17px: 1.0625rem; /* Compound equivalent: --cpd-font-size-body-lg */ $font-18px: 1.125rem; -$font-20px: 1.25rem; +$font-20px: 1.25rem; /* Compound equivalent: --cpd-font-size-heading-sm */ $font-22px: 1.375rem; $font-23px: 1.4375rem; -$font-24px: 1.5rem; +$font-24px: 1.5rem; /* Compound equivalent: --cpd-font-size-heading-md */ $font-25px: 1.5625rem; $font-26px: 1.625rem; -$font-28px: 1.75rem; +$font-28px: 1.75rem; /* Compound equivalent: --cpd-font-size-heading-lg */ $font-29px: 1.8125rem; $font-30px: 1.875rem; -$font-32px: 2rem; +$font-32px: 2rem; /* Compound equivalent: --cpd-font-size-heading-xl */ $font-34px: 2.125rem; $font-35px: 2.1875rem; $font-39px: 2.4375rem; diff --git a/res/css/views/dialogs/_InviteDialog.pcss b/res/css/views/dialogs/_InviteDialog.pcss index 70a8cdc608..0f952049cf 100644 --- a/res/css/views/dialogs/_InviteDialog.pcss +++ b/res/css/views/dialogs/_InviteDialog.pcss @@ -63,17 +63,6 @@ Please see LICENSE files in the repository root for full details. height: 25px; line-height: $font-25px; } - - .mx_InviteDialog_buttonAndSpinner { - .mx_Spinner { - /* Width and height are required to trick the layout engine. */ - width: 20px; - height: 20px; - margin-inline-start: 5px; - display: inline-block; - vertical-align: middle; - } - } } .mx_InviteDialog_section { @@ -218,6 +207,10 @@ Please see LICENSE files in the repository root for full details. flex-direction: column; flex-grow: 1; overflow: hidden; + + .mx_InviteProgressBody { + margin-top: var(--cpd-space-12x); + } } .mx_InviteDialog_transfer { diff --git a/res/css/views/dialogs/_InviteProgressBody.pcss b/res/css/views/dialogs/_InviteProgressBody.pcss new file mode 100644 index 0000000000..e3069a133c --- /dev/null +++ b/res/css/views/dialogs/_InviteProgressBody.pcss @@ -0,0 +1,16 @@ +/* +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. +*/ + +.mx_InviteProgressBody { + text-align: center; + font: var(--cpd-font-body-lg-regular); + + h1 { + color: var(--cpd-color-text-primary); + font: var(--cpd-font-heading-sm-semibold); + } +} diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss index ac58a69bef..06ffe532d7 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss @@ -15,40 +15,44 @@ * |-------------------------------------------------------| */ .mx_RoomListItemView { - all: unset; + /* Remove button default style */ + background: unset; + border: none; + padding: 0; + text-align: unset; + cursor: pointer; + height: 48px; + width: 100%; - .mx_RoomListItemView_container { - padding-left: var(--cpd-space-3x); - font: var(--cpd-font-body-md-regular); + padding-left: var(--cpd-space-3x); + font: var(--cpd-font-body-md-regular); + + .mx_RoomListItemView_content { height: 100%; + flex: 1; + /* The border is only under the room name and the future hover menu */ + border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary); + box-sizing: border-box; + min-width: 0; + padding-right: var(--cpd-space-5x); - .mx_RoomListItemView_content { - height: 100%; - flex: 1; - /* The border is only under the room name and the future hover menu */ - border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary); - box-sizing: border-box; + .mx_RoomListItemView_text { min-width: 0; - padding-right: var(--cpd-space-5x); + } - .mx_RoomListItemView_text { - min-width: 0; - } + .mx_RoomListItemView_roomName { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } - .mx_RoomListItemView_roomName { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .mx_RoomListItemView_messagePreview { - font: var(--cpd-font-body-sm-regular); - color: var(--cpd-color-text-secondary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + .mx_RoomListItemView_messagePreview { + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } } @@ -57,7 +61,7 @@ background-color: var(--cpd-color-bg-action-secondary-hovered); } -.mx_RoomListItemView_menu_open .mx_RoomListItemView_container .mx_RoomListItemView_content { +.mx_RoomListItemView_menu_open .mx_RoomListItemView_content { /** * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331 * the icon size of the menu is 18px instead of 20px with a different internal padding diff --git a/res/css/views/rooms/_E2EIcon.pcss b/res/css/views/rooms/_E2EIcon.pcss index 024b6f637a..7dd0aa476d 100644 --- a/res/css/views/rooms/_E2EIcon.pcss +++ b/res/css/views/rooms/_E2EIcon.pcss @@ -55,7 +55,8 @@ Please see LICENSE files in the repository root for full details. background-color: var(--cpd-color-icon-tertiary); } -.mx_E2EIcon_verified { +.mx_E2EIcon_verified, +.mx_E2EIcon_warning { .mx_E2EIcon_normal::after { background-color: white; } diff --git a/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss b/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss index ceacb22c27..872decadc8 100644 --- a/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss +++ b/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss @@ -68,5 +68,28 @@ display: flex; flex-direction: column; gap: var(--cpd-space-8x); + + .mx_KeyForm_password { + > input[name="recoveryKey"] { + /* + * From figma https://www.figma.com/design/qTWRfItpO3RdCjnTKPu4mL/Settings?node-id=375-77506&t=d82NdRBDoKsUe1C9-4 + */ + height: 70px; + padding: var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-4x); + border: var(--cpd-border-width-1) solid; + border-radius: 8px; + margin: 0px; + } + + > button { + /* + * See figma https://www.figma.com/design/qTWRfItpO3RdCjnTKPu4mL/Settings?node-id=375-77506&t=d82NdRBDoKsUe1C9-4 + * Avoid stretching the hide/show symbol to the height of the input, and centre it vertically. + */ + height: 24.5px; + padding: var(--cpd-space-1x); + align-self: center; + } + } } } diff --git a/res/css/views/verification/_VerificationShowSas.pcss b/res/css/views/verification/_VerificationShowSas.pcss index 1a24519cbf..9e4d1f138b 100644 --- a/res/css/views/verification/_VerificationShowSas.pcss +++ b/res/css/views/verification/_VerificationShowSas.pcss @@ -47,7 +47,7 @@ Please see LICENSE files in the repository root for full details. .mx_VerificationShowSas_emojiSas_label { font-size: $font-12px; - word-break: break-all; + word-break: break-word; } .mx_VerificationShowSas_emojiSas_break { diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index c1a64848d2..05dd437f94 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -477,6 +477,8 @@ export default abstract class BasePlatform { // The redirect URL has to exactly match that registered at the OIDC server, so // ensure that the fragment part of the URL is empty. url.hash = ""; + // Set no_universal_links=true to prevent the callback being handled by Element X installed on macOS Apple Silicon + url.searchParams.set("no_universal_links", "true"); return url; } diff --git a/src/Keyboard.ts b/src/Keyboard.ts index de7ab059c6..5a7e7b59f1 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -79,3 +79,12 @@ export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent) return ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey; } } + +/** + * Checks if the given keyboard event is a modified key event (i.e., if any modifier keys are active). + * @param ev The keyboard event to check + * @returns True if the event is a modified key event, false otherwise + */ +export function isModifiedKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean { + return ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey; +} diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx index 70aabed3de..feefdf7244 100644 --- a/src/RoomInvite.tsx +++ b/src/RoomInvite.tsx @@ -7,10 +7,9 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ComponentProps } from "react"; -import { type Room, type MatrixEvent, type MatrixClient, type User, EventType } from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; +import { EventType, type MatrixClient, type MatrixEvent, type Room, type User } from "matrix-js-sdk/src/matrix"; -import MultiInviter, { type CompletionStates } from "./utils/MultiInviter"; +import MultiInviter, { type CompletionStates, type MultiInviterOptions } from "./utils/MultiInviter"; import Modal from "./Modal"; import { _t } from "./languageHandler"; import InviteDialog from "./components/views/dialogs/InviteDialog"; @@ -30,18 +29,20 @@ export interface IInviteResult { * * Simpler interface to {@link MultiInviter}. * + * Any failures are returned via the `states` in the result. + * * @param {string} roomId The ID of the room to invite to * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids. - * @param {function} progressCallback optional callback, fired after each invite. + * @param options Options object. * @returns {Promise} Promise */ export async function inviteMultipleToRoom( client: MatrixClient, roomId: string, addresses: string[], - progressCallback?: () => void, + options: MultiInviterOptions = {}, ): Promise { - const inviter = new MultiInviter(client, roomId, progressCallback); + const inviter = new MultiInviter(client, roomId, options); return { states: await inviter.invite(addresses), inviter }; } @@ -89,26 +90,6 @@ export function isValid3pidInvite(event: MatrixEvent): boolean { return true; } -export function inviteUsersToRoom( - client: MatrixClient, - roomId: string, - userIds: string[], - progressCallback?: () => void, -): Promise { - return inviteMultipleToRoom(client, roomId, userIds, progressCallback) - .then((result) => { - const room = client.getRoom(roomId)!; - showAnyInviteErrors(result.states, room, result.inviter); - }) - .catch((err) => { - logger.error(err.stack); - Modal.createDialog(ErrorDialog, { - title: _t("invite|failed_title"), - description: err?.message ?? _t("invite|failed_generic"), - }); - }); -} - export function showAnyInviteErrors( states: CompletionStates, room: Room, diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 604dd2142f..60ada112e0 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -572,8 +572,11 @@ function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX: const senderName = getSenderName(event); const roomId = event.getRoomId()!; - const pinned = event.getContent<{ pinned: string[] }>().pinned ?? []; - const previouslyPinned: string[] = event.getPrevContent().pinned ?? []; + const content = event.getContent<{ pinned: string[] }>(); + const prevContent = event.getPrevContent(); + + const pinned = Array.isArray(content.pinned) ? content.pinned : []; + const previouslyPinned: string[] = Array.isArray(prevContent.pinned) ? prevContent.pinned : []; const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0); const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0); diff --git a/src/Unread.ts b/src/Unread.ts index e8f4769e25..d6a80a8f97 100644 --- a/src/Unread.ts +++ b/src/Unread.ts @@ -39,7 +39,12 @@ export function eventTriggersUnreadCount(client: MatrixClient, ev: MatrixEvent): } if (ev.isRedacted()) return false; - return haveRendererForEvent(ev, client, false /* hidden messages should never trigger unread counts anyways */); + try { + return haveRendererForEvent(ev, client, false /* hidden messages should never trigger unread counts anyways */); + } catch (e) { + console.warn("Error determining if event should trigger unread count", e); + return false; // If we can't determine if the event should trigger an unread count, assume it does not. + } } export function doesRoomHaveUnreadMessages(room: Room, includeThreads: boolean): boolean { diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 94a5d34d60..f05fd58d82 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -34,6 +34,7 @@ import { Action } from "../../dispatcher/actions"; import { type XOR } from "../../@types/common"; import ExtensionsCard from "../views/right_panel/ExtensionsCard"; import MemberListView from "../views/rooms/MemberList/MemberListView"; +import { _t } from "../../languageHandler"; interface BaseProps { overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView) @@ -64,6 +65,7 @@ interface IState { export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; declare public context: React.ContextType; + private ref = React.createRef(); public constructor(props: Props) { super(props); @@ -82,6 +84,7 @@ export default class RightPanel extends React.Component { public componentDidMount(): void { this.context.on(RoomStateEvent.Members, this.onRoomStateMember); RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); + this.ref.current?.focus(); } public componentWillUnmount(): void { @@ -119,7 +122,13 @@ export default class RightPanel extends React.Component { }; private onRightPanelStoreUpdate = (): void => { - this.setState({ ...(RightPanel.getDerivedStateFromProps(this.props) as IState) }); + const oldPhase = this.state.phase; + const newState = RightPanel.getDerivedStateFromProps(this.props) as IState; + this.setState({ ...newState }); + + if (oldPhase !== newState.phase) { + this.ref.current?.focus(); + } }; private onClose = (): void => { @@ -282,7 +291,14 @@ export default class RightPanel extends React.Component { } return ( -