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 9eca8c8636..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 @@ -37,14 +37,14 @@ jobs: install: true - name: Login to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3 if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3 if: github.event_name != 'pull_request' with: registry: ghcr.io @@ -96,7 +96,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5 if: github.event_name != 'pull_request' with: images: | @@ -139,3 +139,16 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} repository: vectorim/element-web + + - name: Repository Dispatch + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3 + if: github.event_name != 'pull_request' + with: + repository: element-hq/element-web-pro + token: ${{ secrets.ELEMENT_BOT_TOKEN }} + event-type: image-built + # Stable way to determine the :version + client-payload: |- + { + "base-ref": "${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" + } 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/triage-assigned.yml b/.github/workflows/triage-assigned.yml index f190122a1c..b16f626c15 100644 --- a/.github/workflows/triage-assigned.yml +++ b/.github/workflows/triage-assigned.yml @@ -15,7 +15,7 @@ jobs: contains(github.event.issue.assignees.*.login, 'dbkr') || contains(github.event.issue.assignees.*.login, 'MidhunSureshR') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/67 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index b084b4d55e..d81322bc8c 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -10,7 +10,7 @@ jobs: automate-project-columns: runs-on: ubuntu-24.04 steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/120 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index e1849e0efc..3ddc1b65ae 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -112,7 +112,7 @@ jobs: contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'A11y')) steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/18 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -123,7 +123,7 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'X-Needs-Product') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/28 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -134,7 +134,7 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'A-New-Search-Experience') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/48 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -145,7 +145,7 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'Team: VoIP') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/41 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -156,7 +156,7 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'Team: Crypto') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/76 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -172,7 +172,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A-Testing') || contains(github.event.issue.labels.*.name, 'Z-Flaky-Test') steps: - - uses: actions/add-to-project@main + - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 with: project-url: https://github.com/orgs/element-hq/projects/101 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index d3bcda270b..0a07998948 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -9,7 +9,7 @@ jobs: name: Move PRs asking for design review to the design board runs-on: ubuntu-24.04 steps: - - uses: octokit/graphql-action@v2.x + - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2 id: find_team_members with: headers: '{"GraphQL-Features": "projects_next_graphql"}' @@ -52,7 +52,7 @@ jobs: fi env: TEAM: "design" - - uses: octokit/graphql-action@v2.x + - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2 id: add_to_project if: steps.any_matching_reviewers.outputs.match == 'true' with: @@ -76,7 +76,7 @@ jobs: name: Move PRs asking for design review to the design board runs-on: ubuntu-24.04 steps: - - uses: octokit/graphql-action@v2.x + - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2 id: find_team_members with: headers: '{"GraphQL-Features": "projects_next_graphql"}' @@ -119,7 +119,7 @@ jobs: fi env: TEAM: "product" - - uses: octokit/graphql-action@v2.x + - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2 id: add_to_project if: steps.any_matching_reviewers.outputs.match == 'true' with: 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/.stylelintrc.js b/.stylelintrc.js index ffc6c345b9..3244d122c5 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -70,5 +70,13 @@ module.exports = { ], }, ], + "property-no-deprecated": [ + true, + { + ignoreProperties: ["-webkit-box-orient", "word-wrap"], + }, + ], + "nesting-selector-no-missing-scoping-root": null, + "no-invalid-position-declaration": null, }, }; 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 ff9ec91f0a..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:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a 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:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232 +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:ea6c4b8b568824ea94cd1fabd47e1c4e7c0c04744f344a3793f7e9c8ac3a3636 # Need root user to install packages & manipulate the usr directory USER root diff --git a/README.md b/README.md index 0f8a721f90..6f6e3172fa 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Element has several tiers of support for different environments: - Best effort - Definition: - Issues **accepted**, regressions **do not block** the release - - The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers. + - The wider Element Products (including Element Call and the Enterprise Server Suite) do still not officially support these browsers. - The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function. - Last major release of Firefox ESR and Chrome/Edge Extended Stable - Community Supported 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 bbb0b176a1..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": { @@ -73,10 +73,10 @@ "test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot" }, "resolutions": { - "**/pretty-format/react-is": "19.1.0", - "@playwright/test": "1.54.1", - "@types/react": "19.1.8", - "@types/react-dom": "19.1.6", + "**/pretty-format/react-is": "19.1.1", + "@playwright/test": "1.54.2", + "@types/react": "19.1.10", + "@types/react-dom": "19.1.7", "oidc-client-ts": "3.3.0", "jwt-decode": "4.0.0", "caniuse-lite": "1.0.30001724", @@ -86,7 +86,7 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", - "@element-hq/element-web-module-api": "1.3.0", + "@element-hq/element-web-module-api": "1.4.1", "@fontsource/inconsolata": "^5", "@fontsource/inter": "^5", "@formatjs/intl-segmenter": "^11.5.7", @@ -94,10 +94,9 @@ "@matrix-org/emojibase-bindings": "^1.3.4", "@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/spec": "^1.7.0", - "@sentry/browser": "^9.0.0", + "@sentry/browser": "^10.0.0", "@types/png-chunks-extract": "^1.0.2", - "@types/react-virtualized": "^9.21.30", - "@vector-im/compound-design-tokens": "^5.0.0", + "@vector-im/compound-design-tokens": "^6.0.0", "@vector-im/compound-web": "^8.1.2", "@vector-im/matrix-wysiwyg": "2.39.0", "@zxcvbn-ts/core": "^3.0.4", @@ -128,9 +127,9 @@ "jsrsasign": "^11.0.0", "jszip": "^3.7.0", "katex": "^0.16.0", - "linkify-react": "4.3.1", - "linkify-string": "4.3.1", - "linkifyjs": "4.3.1", + "linkify-react": "4.3.2", + "linkify-string": "4.3.2", + "linkifyjs": "4.3.2", "lodash": "^4.17.21", "maplibre-gl": "^5.0.0", "matrix-encrypt-attachment": "^1.0.3", @@ -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,7 +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.14.0", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", "sanitize-html": "2.17.0", @@ -185,8 +184,8 @@ "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.5", "@casualbot/jest-sonar-reporter": "2.2.7", - "@element-hq/element-call-embedded": "0.13.1", - "@element-hq/element-web-playwright-common": "^1.4.4", + "@element-hq/element-call-embedded": "0.14.1", + "@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", @@ -222,9 +221,9 @@ "@types/node-fetch": "^2.6.2", "@types/pako": "^2.0.0", "@types/qrcode": "^1.3.5", - "@types/react": "19.1.8", + "@types/react": "19.1.10", "@types/react-beautiful-dnd": "^13.0.0", - "@types/react-dom": "19.1.6", + "@types/react-dom": "19.1.7", "@types/react-transition-group": "^4.4.0", "@types/sanitize-html": "2.16.0", "@types/semver": "^7.5.8", @@ -299,8 +298,8 @@ "semver": "^7.5.2", "source-map-loader": "^5.0.0", "storybook": "^9.0.12", - "stylelint": "^16.13.0", - "stylelint-config-standard": "^38.0.0", + "stylelint": "^16.23.0", + "stylelint-config-standard": "^39.0.0", "stylelint-scss": "^6.0.0", "stylelint-value-no-unknown-custom-properties": "^6.0.1", "terser-webpack-plugin": "^5.3.9", diff --git a/playwright/e2e/audio-player/audio-player.spec.ts b/playwright/e2e/audio-player/audio-player.spec.ts index a8cb15a5da..3e5e2ce079 100644 --- a/playwright/e2e/audio-player/audio-player.spec.ts +++ b/playwright/e2e/audio-player/audio-player.spec.ts @@ -19,6 +19,7 @@ const clickButtonReply = async (tile: Locator) => { await tile.hover(); await tile.getByRole("button", { name: "Reply", exact: true }).click(); }).toPass(); + await expect(tile.page().getByText("Replying", { exact: true })).toBeVisible(); }; test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { @@ -39,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { // wait for the tile to finish loading await expect( page - .locator(".mx_AudioPlayer_mediaName") + .getByTestId("audio-player-name") .last() .filter({ hasText: file.split("/").at(-1) }), ).toBeVisible(); @@ -54,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { // Check that the audio player is rendered and its button becomes visible const checkPlayerVisibility = async (locator: Locator) => { // Assert that the audio player and media information are visible - const mediaInfo = locator.locator( - ".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo", - ); - await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension - await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible(); - await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size + const mediaInfo = locator.getByRole("region", { name: "Audio player" }); + await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension + await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration + await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size; // Assert that the play button can be found and is visible await expect(locator.getByRole("button", { name: "Play" })).toBeVisible(); @@ -78,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { } // Check the status of the seek bar - expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0); + expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0); // Enable IRC layout await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC); @@ -100,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { display: none !important; } `, - mask: [page.locator(".mx_AudioPlayer_seek")], + mask: [page.getByTestId("audio-player-seek")], }; // Take a snapshot of mx_EventTile_last on IRC layout @@ -186,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Assert that the audio player is rendered - const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container"); + const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }); // Assert that the counter is zero before clicking the play button - await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(container.getByRole("timer")).toHaveText("00:00"); // Find and click "Play" button, the wait is to make the test less flaky await expect(container.getByRole("button", { name: "Play" })).toBeVisible(); @@ -198,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await expect(container.getByRole("button", { name: "Pause" })).toBeVisible(); // Assert that the timer is reset when the audio file finished playing - await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(container.getByRole("timer")).toHaveText("00:00"); // Assert that "Play" button can be found await expect(container.getByRole("button", { name: "Play" })).toBeVisible(); @@ -226,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Assert the audio player is rendered - await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible(); // Find and click "Reply" button on MessageActionBar const tile = page.locator(".mx_EventTile_last"); @@ -236,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/1sec.ogg"); // Assert that the audio player is rendered - await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible(); + await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible(); // Assert that replied audio file is rendered as file button inside ReplyChain const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']"); @@ -261,7 +260,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/upload-first.ogg"); // Assert that the audio player is rendered - await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + await expect( + page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }), + ).toBeVisible(); await clickButtonReply(tile); @@ -269,7 +270,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/upload-second.ogg"); // Assert that the audio player is rendered - await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + await expect( + page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }), + ).toBeVisible(); await clickButtonReply(tile); @@ -277,7 +280,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await uploadFile(page, "playwright/sample-files/upload-third.ogg"); // Assert that the audio player is rendered - await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible(); + await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible(); // Assert that there are two "mx_ReplyChain" elements await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2); @@ -313,7 +316,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { // On the main timeline const messageList = page.locator(".mx_RoomView_MessageList"); // Assert the audio player is rendered - await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible(); + await expect( + messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }), + ).toBeVisible(); // Find and click "Reply in thread" button await messageList.locator(".mx_EventTile_last").hover(); await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click(); @@ -321,10 +326,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { // On a thread const thread = page.locator(".mx_ThreadView"); const threadTile = thread.locator(".mx_EventTile_last"); - const audioPlayer = threadTile.locator(".mx_AudioPlayer_container"); + const audioPlayer = threadTile.getByRole("region", { name: "Audio player" }); // Assert that the counter is zero before clicking the play button - await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(audioPlayer.getByRole("timer")).toHaveText("00:00"); // Find and click "Play" button, the wait is to make the test less flaky await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible(); @@ -334,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible(); // Assert that the timer is reset when the audio file finished playing - await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(audioPlayer.getByRole("timer")).toHaveText("00:00"); // Assert that "Play" button can be found await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled(); diff --git a/playwright/e2e/composer/CIDER.spec.ts b/playwright/e2e/composer/CIDER.spec.ts index 03fc59cd0f..8214c8058b 100644 --- a/playwright/e2e/composer/CIDER.spec.ts +++ b/playwright/e2e/composer/CIDER.spec.ts @@ -28,7 +28,7 @@ test.describe("Composer", () => { test.describe("CIDER", () => { test("sends a message when you click send or press Enter", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); + const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" }); // Type a message await composer.pressSequentially("my message 0"); @@ -52,7 +52,7 @@ test.describe("Composer", () => { }); test("can write formatted text", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); + const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" }); await composer.pressSequentially("my bold"); await composer.press(`${CtrlOrMeta}+KeyB`); @@ -68,7 +68,7 @@ test.describe("Composer", () => { await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "šŸ˜‡" }).click(); await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker - await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message + await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message await expect(page.locator(".mx_EventTile_body", { hasText: "šŸ˜‡" })).toBeVisible(); }); @@ -79,7 +79,7 @@ test.describe("Composer", () => { }); test("only sends when you press Control+Enter", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); + const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" }); // Type a message and press Enter await composer.pressSequentially("my message 3"); await composer.press("Enter"); diff --git a/playwright/e2e/crypto/backups-mas.spec.ts b/playwright/e2e/crypto/backups-mas.spec.ts index 6a7fff6672..fda82c220d 100644 --- a/playwright/e2e/crypto/backups-mas.spec.ts +++ b/playwright/e2e/crypto/backups-mas.spec.ts @@ -91,10 +91,10 @@ test.describe("Key backup reset from elsewhere", () => { await csAPI.deleteBackupVersion(backupInfo.version); - await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession"); + await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession"); await page.getByRole("button", { name: "Send message" }).click(); - await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup"); + await page.getByRole("textbox", { name: "Send a message…" }).fill("Message with broken key backup"); await page.getByRole("button", { name: "Send message" }).click(); // Should be the message we sent plus the room creation event diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts index 6d01435546..668fd20021 100644 --- a/playwright/e2e/crypto/crypto.spec.ts +++ b/playwright/e2e/crypto/crypto.spec.ts @@ -154,11 +154,12 @@ test.describe("Cryptography", function () { await app.client.bootstrapCrossSigning(aliceCredentials); await startDMWithBob(page, bob); // send first message - await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!"); - await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); + await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!"); + await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); await checkDMRoom(page); const bobRoomId = await bobJoin(page, bob); - await expect(page.locator(".mx_MessageComposer_e2eIcon")).toMatchScreenshot("composer-e2e-icon-normal.png"); + // 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/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts index eb21dfc909..ab36c37a76 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -209,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { const dialog = page.locator(".mx_Dialog"); // We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems // (cf https://github.com/element-hq/element-web/issues/30089) - await dialog.locator("textarea").pressSequentially(recoveryKey); + await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey); await dialog.getByRole("button", { name: "Continue", disabled: false }).click(); await page.getByRole("button", { name: "Done" }).click(); 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/crypto/utils.ts b/playwright/e2e/crypto/utils.ts index 12640bfb3e..42bc4d643e 100644 --- a/playwright/e2e/crypto/utils.ts +++ b/playwright/e2e/crypto/utils.ts @@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur await useSecurityKey.click(); } // Fill in the recovery key - await page.locator(".mx_Dialog").locator("textarea").fill(securityKey); + await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey); await page.getByRole("button", { name: "Continue", disabled: false }).click(); await page.getByRole("button", { name: "Done" }).click(); } @@ -263,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) { const settings = await app.settings.openUserSettings("Encryption"); await settings.getByRole("button", { name: "Verify this device" }).click(); await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click(); - await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey); + await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey); await app.page.getByRole("button", { name: "Continue", disabled: false }).click(); await app.page.getByRole("button", { name: "Done" }).click(); await app.settings.closeDialog(); diff --git a/playwright/e2e/lazy-loading/lazy-loading.spec.ts b/playwright/e2e/lazy-loading/lazy-loading.spec.ts index 7c31c288fa..f6f098a079 100644 --- a/playwright/e2e/lazy-loading/lazy-loading.spec.ts +++ b/playwright/e2e/lazy-loading/lazy-loading.spec.ts @@ -30,6 +30,10 @@ test.describe("Lazy Loading", () => { }); test.beforeEach(async ({ page, homeserver, user, bot, app }) => { + // The charlies were running off the bottom of the screen. + // We no longer overscan the member list so the result is they are not in the dom. + // Increase the viewport size to ensure they are. + await page.setViewportSize({ width: 1000, height: 1000 }); for (let i = 1; i <= 10; i++) { const displayName = `Charly #${i}`; const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false }); 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-header.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts index daa8d3869f..ed7a24fff8 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts @@ -35,8 +35,8 @@ test.describe("Header section of the room list", () => { await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png"); - // New message should open the direct messages dialog - await page.getByRole("menuitem", { name: "New message" }).click(); + // Start chat should open the direct messages dialog + await page.getByRole("menuitem", { name: "Start chat" }).click(); await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible(); await app.closeDialog(); 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/messages/messages.spec.ts b/playwright/e2e/messages/messages.spec.ts index f430d6b18b..1a36ac97e9 100644 --- a/playwright/e2e/messages/messages.spec.ts +++ b/playwright/e2e/messages/messages.spec.ts @@ -13,7 +13,7 @@ import { type Locator, type Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; async function sendMessage(page: Page, message: string): Promise { - await page.getByRole("textbox", { name: "Send a message…" }).fill(message); + await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message); await page.getByRole("button", { name: "Send message" }).click(); const msgTile = page.locator(".mx_EventTile_last"); @@ -22,7 +22,7 @@ async function sendMessage(page: Page, message: string): Promise { } async function sendMultilineMessages(page: Page, messages: string[]) { - await page.getByRole("textbox", { name: "Send a message…" }).focus(); + await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus(); for (let i = 0; i < messages.length; i++) { await page.keyboard.type(messages[i]); if (i < messages.length - 1) await page.keyboard.press("Shift+Enter"); @@ -40,7 +40,7 @@ async function replyMessage(page: Page, message: Locator, replyMessage: string): await line.hover(); await line.getByRole("button", { name: "Reply", exact: true }).click(); - await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage); + await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage); await page.getByRole("button", { name: "Send message" }).click(); const msgTile = page.locator(".mx_EventTile_last"); 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/regression-tests/pills-click-in-app.spec.ts b/playwright/e2e/regression-tests/pills-click-in-app.spec.ts index 3670e64308..6856e70609 100644 --- a/playwright/e2e/regression-tests/pills-click-in-app.spec.ts +++ b/playwright/e2e/regression-tests/pills-click-in-app.spec.ts @@ -29,7 +29,7 @@ test.describe("Pills", () => { // send a message using the built-in room mention functionality (autocomplete) await page - .getByRole("textbox", { name: "Send a message…" }) + .getByRole("textbox", { name: "Send an unencrypted message…" }) .pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`); await page.locator(".mx_Autocomplete_Completion_title").click(); await page.getByRole("button", { name: "Send message" }).click(); diff --git a/playwright/e2e/right-panel/file-panel.spec.ts b/playwright/e2e/right-panel/file-panel.spec.ts index d69b7d4731..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"; @@ -63,9 +63,7 @@ test.describe("FilePanel", () => { await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible(); // Assert that the audio player is rendered - await expect( - roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"), - ).toBeVisible(); + await expect(roomViewBody.getByRole("region", { name: "Audio player" })).toBeVisible(); // Assert that the file button exists await expect( @@ -97,9 +95,7 @@ test.describe("FilePanel", () => { await expect(image.locator("img[alt='riot.png']")).toBeVisible(); // Detect the audio file - const audio = filePanelMessageList.locator( - ".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container", - ); + const audio = filePanelMessageList.getByRole("region", { name: "Audio player" }); // Assert that the play button is rendered await expect(audio.getByRole("button", { name: "Play" })).toBeVisible(); @@ -130,7 +126,7 @@ test.describe("FilePanel", () => { // Take a snapshot of file tiles list on FilePanel await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", { // Exclude timestamps & flaky seek bar from snapshot - mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")], + mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")], }); }); @@ -138,21 +134,19 @@ test.describe("FilePanel", () => { // Upload an image file await uploadFile(page, "playwright/sample-files/1sec.ogg"); - const audioBody = page.locator( - ".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container", - ); + const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" }); + // Assert that the audio player is rendered - // Assert that the audio file information is rendered - const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo"); - await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible(); - await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible(); - await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size + // Assert that the audio file information is rendered; + await expect(audioBody.getByText("1sec.ogg")).toBeVisible(); // extension + await expect(audioBody.getByRole("time")).toHaveText("00:01"); // duration + await expect(audioBody.getByText("(3.56 KB)")).toBeVisible(); // actual size; // Assert that the duration counter is 00:01 before clicking the play button - await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible(); + await expect(audioBody.getByRole("time")).toHaveText("00:01"); // Assert that the counter is zero before clicking the play button - await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(audioBody.getByRole("timer")).toHaveText("00:00"); // Click the play button await audioBody.getByRole("button", { name: "Play" }).click(); @@ -161,7 +155,7 @@ test.describe("FilePanel", () => { await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible(); // Assert that the timer is reset when the audio file finished playing - await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible(); + await expect(audioBody.getByRole("timer")).toHaveText("00:00"); // Assert that the play button is rendered await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible(); @@ -195,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/right-panel/memberlist.spec.ts b/playwright/e2e/right-panel/memberlist.spec.ts index cd22626575..71a4a3cada 100644 --- a/playwright/e2e/right-panel/memberlist.spec.ts +++ b/playwright/e2e/right-panel/memberlist.spec.ts @@ -11,6 +11,32 @@ import { Bot } from "../../pages/bot"; const ROOM_NAME = "Test room"; const NAME = "Alice"; +async function setupRoomWithMembers( + app: any, + page: any, + homeserver: any, + roomName: string, + memberNames: string[], +): Promise { + const visibility = await page.evaluate(() => (window as any).matrixcs.Visibility.Public); + const id = await app.client.createRoom({ name: roomName, visibility }); + const bots: Bot[] = []; + + for (let i = 0; i < memberNames.length; i++) { + const displayName = memberNames[i]; + const bot = new Bot(page, homeserver, { displayName, startClient: false, autoAcceptInvites: false }); + if (displayName === "Susan") { + await bot.prepareClient(); + await app.client.inviteUser(id, bot.credentials?.userId); + } else { + await bot.joinRoom(id); + } + bots.push(bot); + } + + return id; +} + test.use({ synapseConfig: { presence: { @@ -25,17 +51,8 @@ test.use({ test.describe("Memberlist", () => { test.beforeEach(async ({ app, user, page, homeserver }, testInfo) => { testInfo.setTimeout(testInfo.timeout + 30_000); - const id = await app.client.createRoom({ name: ROOM_NAME }); - const newBots: Bot[] = []; const names = ["Bob", "Bob", "Susan"]; - for (let i = 0; i < 3; i++) { - const displayName = names[i]; - const autoAcceptInvites = displayName !== "Susan"; - const bot = new Bot(page, homeserver, { displayName, startClient: true, autoAcceptInvites }); - await bot.prepareClient(); - await app.client.inviteUser(id, bot.credentials?.userId); - newBots.push(bot); - } + await setupRoomWithMembers(app, page, homeserver, ROOM_NAME, names); }); test("Renders correctly", { tag: "@screenshot" }, async ({ page, app }) => { @@ -45,4 +62,37 @@ test.describe("Memberlist", () => { await expect(memberlist.getByText("Invited")).toHaveCount(1); await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png"); }); + + test("should handle scroll and click to view member profile", async ({ page, app, homeserver }) => { + // Create a room with many members to enable scrolling + const memberNames = Array.from({ length: 15 }, (_, i) => `Member${i.toString()}`); + await setupRoomWithMembers(app, page, homeserver, "Large Room", memberNames); + + // Navigate to the room and open member list + await app.viewRoomByName("Large Room"); + + const memberlist = await app.toggleMemberlistPanel(); + + // Get the scrollable container + const memberListContainer = memberlist.locator(".mx_AutoHideScrollbar"); + + // Scroll down to the bottom of the member list + await app.scrollListToBottom(memberListContainer); + + // Wait for the target member to be visible after scrolling + const targetName = "Member14"; + const targetMember = memberlist.locator(".mx_MemberTileView_name").filter({ hasText: targetName }); + await targetMember.waitFor({ state: "visible" }); + + // Verify Alice is not visible at this point + await expect(memberlist.locator(".mx_MemberTileView_name").filter({ hasText: "Alice" })).toHaveCount(0); + + // Click on a member near the bottom of the list + await expect(targetMember).toBeVisible(); + await targetMember.click(); + + // Verify that the user info screen is shown and hasn't scrolled back to top + await expect(page.locator(".mx_UserInfo")).toBeVisible(); + await expect(page.locator(".mx_UserInfo_profile").getByText(targetName)).toBeVisible(); + }); }); 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/e2e/share-dialog/share-dialog.spec.ts b/playwright/e2e/share-dialog/share-dialog.spec.ts index 58574a46ff..a77e89fcdc 100644 --- a/playwright/e2e/share-dialog/share-dialog.spec.ts +++ b/playwright/e2e/share-dialog/share-dialog.spec.ts @@ -35,7 +35,7 @@ test.describe("Share dialog", () => { const rightPanel = await app.toggleRoomInfoPanel(); await rightPanel.getByRole("menuitem", { name: "People" }).click(); - await rightPanel.getByRole("button", { name: `${user.userId} (power 100)` }).click(); + await rightPanel.getByRole("option", { name: user.displayName }).click(); await rightPanel.getByRole("button", { name: "Share profile" }).click(); const dialog = page.getByRole("dialog", { name: "Share User" }); diff --git a/playwright/e2e/spotlight/spotlight.spec.ts b/playwright/e2e/spotlight/spotlight.spec.ts index 7a5f7d4ea8..542b537c13 100644 --- a/playwright/e2e/spotlight/spotlight.spec.ts +++ b/playwright/e2e/spotlight/spotlight.spec.ts @@ -30,7 +30,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise { // Send first message to actually start DM await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName); - const locator = page.getByRole("textbox", { name: "Send a message…" }); + const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" }); await locator.fill("Hey!"); await locator.press("Enter"); diff --git a/playwright/e2e/threads/threads.spec.ts b/playwright/e2e/threads/threads.spec.ts index 89cfe418ba..98039e1242 100644 --- a/playwright/e2e/threads/threads.spec.ts +++ b/playwright/e2e/threads/threads.spec.ts @@ -43,7 +43,7 @@ test.describe("Threads", () => { const roomViewLocator = page.locator(".mx_RoomView_body"); // User sends message - const textbox = roomViewLocator.getByRole("textbox", { name: "Send a message…" }); + const textbox = roomViewLocator.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill("Hello Mr. Bot"); await textbox.press("Enter"); @@ -108,7 +108,7 @@ test.describe("Threads", () => { await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group); // User responds in thread - locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" }); + locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" }); await locator.fill("Test"); await locator.press("Enter"); @@ -262,7 +262,7 @@ test.describe("Threads", () => { await locator.locator(".mx_EventTile_line").click(); // User responds & asserts - locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" }); + locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" }); await locator.fill("Great!"); await locator.press("Enter"); @@ -335,8 +335,8 @@ test.describe("Threads", () => { // Send message const locator = page.locator(".mx_RoomView_body"); - await locator.getByRole("textbox", { name: "Send a message…" }).fill("Hello Mr. Bot"); - await locator.getByRole("textbox", { name: "Send a message…" }).press("Enter"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Create thread const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" }); await locator2.hover(); @@ -366,7 +366,7 @@ test.describe("Threads", () => { let locator = page.locator(".mx_RoomView_body"); // User sends message - let textbox = locator.getByRole("textbox", { name: "Send a message…" }); + let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill("Hello Mr. Bot"); await textbox.press("Enter"); // Wait for message to send, get its ID and save as @threadId @@ -395,7 +395,7 @@ test.describe("Threads", () => { locator = page.locator(".mx_ThreadView"); await locator.locator(".mx_EventTile_last").hover(); await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click(); - textbox = locator.getByRole("textbox", { name: "Reply to thread…" }); + textbox = locator.getByRole("textbox", { name: "Reply to unencrypted thread…" }); await textbox.fill("Please come here"); await textbox.press("Enter"); // Wait until the reply is sent @@ -414,7 +414,7 @@ test.describe("Threads", () => { // Send message let locator = page.locator(".mx_RoomView_body"); - let textbox = locator.getByRole("textbox", { name: "Send a message…" }); + let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill("Hello Mr. Bot"); await textbox.press("Enter"); // Create thread @@ -425,7 +425,7 @@ test.describe("Threads", () => { // Send message to thread locator = page.locator(".mx_ThreadPanel"); - textbox = locator.getByRole("textbox", { name: "Send a message…" }); + textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill("Hello Mr. User"); await textbox.press("Enter"); await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached(); @@ -456,7 +456,7 @@ test.describe("Threads", () => { */ const sendMessage = async (message: string) => { const messageComposer = page.getByRole("region", { name: "Message composer" }); - const textbox = messageComposer.getByRole("textbox", { name: "Send a message…" }); + const textbox = messageComposer.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill(message); await textbox.press("Enter"); }; @@ -478,7 +478,7 @@ test.describe("Threads", () => { // Send a message in the thread const threadPanel = page.locator(".mx_ThreadPanel"); - const textbox = threadPanel.getByRole("textbox", { name: "Send a message…" }); + const textbox = threadPanel.getByRole("textbox", { name: "Send an unencrypted message…" }); await textbox.fill(threadMessage); await textbox.press("Enter"); await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible(); diff --git a/playwright/e2e/timeline/timeline.spec.ts b/playwright/e2e/timeline/timeline.spec.ts index e5bfa0c71b..d169afd66f 100644 --- a/playwright/e2e/timeline/timeline.spec.ts +++ b/playwright/e2e/timeline/timeline.spec.ts @@ -461,11 +461,11 @@ test.describe("Timeline", () => { // Send a emote await page .locator(".mx_RoomView_body") - .getByRole("textbox", { name: "Send a message…" }) + .getByRole("textbox", { name: "Send an unencrypted message…" }) .fill("/me says hello to Mr. Bot"); await page .locator(".mx_RoomView_body") - .getByRole("textbox", { name: "Send a message…" }) + .getByRole("textbox", { name: "Send an unencrypted message…" }) .press("Enter"); // Check inline start margin of its avatar // Here --right-padding is for the avatar on the message line 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/shared-component-snapshots/audio-audioplayerview--default-linux.png b/playwright/shared-component-snapshots/audio-audioplayerview--default-linux.png new file mode 100644 index 0000000000..e4d6a84d23 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-audioplayerview--default-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-audioplayerview--has-error-linux.png b/playwright/shared-component-snapshots/audio-audioplayerview--has-error-linux.png new file mode 100644 index 0000000000..36684675f7 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-audioplayerview--has-error-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-audioplayerview--no-media-name-linux.png b/playwright/shared-component-snapshots/audio-audioplayerview--no-media-name-linux.png new file mode 100644 index 0000000000..c46e59ac21 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-audioplayerview--no-media-name-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-audioplayerview--no-size-linux.png b/playwright/shared-component-snapshots/audio-audioplayerview--no-size-linux.png new file mode 100644 index 0000000000..928f6f0197 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-audioplayerview--no-size-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-clock--default-linux.png b/playwright/shared-component-snapshots/audio-clock--default-linux.png new file mode 100644 index 0000000000..be66f4b70c Binary files /dev/null and b/playwright/shared-component-snapshots/audio-clock--default-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png b/playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png new file mode 100644 index 0000000000..b4879e1a0c Binary files /dev/null and b/playwright/shared-component-snapshots/audio-clock--lot-of-seconds-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-playpausebutton--default-linux.png b/playwright/shared-component-snapshots/audio-playpausebutton--default-linux.png new file mode 100644 index 0000000000..8c8baa7a65 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-playpausebutton--default-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-playpausebutton--playing-linux.png b/playwright/shared-component-snapshots/audio-playpausebutton--playing-linux.png new file mode 100644 index 0000000000..53d58a4c2f Binary files /dev/null and b/playwright/shared-component-snapshots/audio-playpausebutton--playing-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-seekbar--default-linux.png b/playwright/shared-component-snapshots/audio-seekbar--default-linux.png new file mode 100644 index 0000000000..60e51020cf Binary files /dev/null and b/playwright/shared-component-snapshots/audio-seekbar--default-linux.png differ diff --git a/playwright/shared-component-snapshots/audio-seekbar--disabled-linux.png b/playwright/shared-component-snapshots/audio-seekbar--disabled-linux.png new file mode 100644 index 0000000000..128f7e2ee5 Binary files /dev/null and b/playwright/shared-component-snapshots/audio-seekbar--disabled-linux.png differ diff --git a/playwright/shared-component-snapshots/messagebody-mediabody--default-linux.png b/playwright/shared-component-snapshots/messagebody-mediabody--default-linux.png new file mode 100644 index 0000000000..56b8072d2d Binary files /dev/null and b/playwright/shared-component-snapshots/messagebody-mediabody--default-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png index 165033dbe9..17bc4bfc78 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png index f309d57bc0..30e0de96ce 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png index bd02a2f21a..f2fd0de114 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--dark-theme--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png index 16e0624b83..ab9b63960d 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png index 1e78930256..7e6137c4eb 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png index 6a43aac7ef..519a46371d 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--high-contrast--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png index 014b8dbaec..0a299b63d1 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png index 156d89053c..8307809872 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png index caf6e1e698..f57a1e27a6 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png index c9591ebf49..8a6dec9273 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png index 794ac11b01..b6a2ed0bfc 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png index 2b6475fbdf..c59ef184c4 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player--light-theme--monospace-font--irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png index 0f643ee43a..9d86393932 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png index bc9d6c88c3..470366cb9b 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png index 2b867170ae..2dac381c78 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png index 459ebd3584..b09c0ceed4 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-irc-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png index da97c28029..16f51d0262 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-group-layout-linux.png differ diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png index 009ea38f7b..1952c0681d 100644 Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-irc-layout-linux.png differ 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/default-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png index 45d2a775ea..d47d04e9a6 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png index 250712308d..ac3f26e529 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-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-header.spec.ts/room-list-header-compose-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-linux.png index 9f501a58d4..202a83c23a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-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 b802eb7888..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 0a4abba833..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 f00047fe84..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 3e78322813..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/right-panel/file-panel.spec.ts/file-tiles-list-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png index 1c75a92373..f9170b0cda 100644 Binary files a/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png and b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png differ diff --git a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png index a5db88aae6..ab5b0ea76c 100644 Binary files a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png and b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-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 60d6dc9e18..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 f217fe7094..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 e43e41dd79..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 cff1b27bd3..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 30fa37ab9e..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 c92780196d..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 4bad759050..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 f8135426c2..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 4d75b3b966..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 a6e31b89cd..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 8d3d7b09ed..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-tile-reply-chains-irc-modern-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tile-reply-chains-irc-modern-linux.png index ca4ce5933f..67a207c687 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tile-reply-chains-irc-modern-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tile-reply-chains-irc-modern-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 785681b28c..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 e5e312ac73..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 f79934e621..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 ff4fa7c1b9..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 f6840e8daf..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 6154a0a268..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 06853769d7..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 8d3d7b09ed..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 f1a95a8275..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 9d9dacd1bf..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 e0523d6eec..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 5c6d7710f6..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/timeline/timeline.spec.ts/search-aux-panel-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/search-aux-panel-linux.png index 125c47ee35..d46e898b9c 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/search-aux-panel-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/search-aux-panel-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..17420bd519 --- /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:430b1f00e74c3f89f078670f676b4333f6bbe5a339962344b3ae84e99e9bcd7f"; + +/** + * 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 1ea07a13b7..acbf16b6a3 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:29b212891be5f0e2c3cdbbf9274750e842f0aa747e61c3360b68dac237428014"; +const TAG = "develop@sha256:8f0926e3323c9221e99e04783e6ec873b623fa6372342d0f19e151e4affc6147"; /** * SynapseContainer which freezes the docker digest to stabilise tests, diff --git a/res/css/_components.pcss b/res/css/_components.pcss index bd002fb80c..602885546e 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -95,7 +95,6 @@ @import "./structures/auth/_Registration.pcss"; @import "./structures/auth/_SessionLockStolenView.pcss"; @import "./structures/auth/_SetupEncryptionBody.pcss"; -@import "./views/audio_messages/_AudioPlayer.pcss"; @import "./views/audio_messages/_PlayPauseButton.pcss"; @import "./views/audio_messages/_PlaybackContainer.pcss"; @import "./views/audio_messages/_SeekBar.pcss"; @@ -143,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/components/views/dialogs/polls/_PollListItem.pcss b/res/css/components/views/dialogs/polls/_PollListItem.pcss index 6cb46a21d2..cd24c75937 100644 --- a/res/css/components/views/dialogs/polls/_PollListItem.pcss +++ b/res/css/components/views/dialogs/polls/_PollListItem.pcss @@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details. display: grid; justify-content: left; align-items: center; - grid-gap: $spacing-8; + gap: $spacing-8; grid-template-columns: auto auto auto; grid-template-rows: auto; cursor: pointer; diff --git a/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss index 772b47c9a4..2eb7a185ac 100644 --- a/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss +++ b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss @@ -22,7 +22,7 @@ Please see LICENSE files in the repository root for full details. display: grid; justify-content: left; align-items: center; - grid-gap: $spacing-8; + gap: $spacing-8; grid-template-columns: min-content 1fr min-content; grid-template-rows: auto; } @@ -47,7 +47,7 @@ Please see LICENSE files in the repository root for full details. .mx_PollListItemEnded_answers { display: grid; - grid-gap: $spacing-8; + gap: $spacing-8; margin-top: $spacing-12; } diff --git a/res/css/components/views/settings/devices/_DeviceDetailHeading.pcss b/res/css/components/views/settings/devices/_DeviceDetailHeading.pcss index 789efa9e7f..82b19b9ff1 100644 --- a/res/css/components/views/settings/devices/_DeviceDetailHeading.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetailHeading.pcss @@ -19,7 +19,7 @@ Please see LICENSE files in the repository root for full details. .mx_DeviceDetailHeading_renameForm { display: grid; - grid-gap: $spacing-16; + gap: $spacing-16; justify-content: left; grid-template-columns: 100%; } diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss index d3635710f3..4b311d1c7c 100644 --- a/res/css/components/views/settings/devices/_DeviceDetails.pcss +++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss @@ -23,7 +23,7 @@ Please see LICENSE files in the repository root for full details. border-bottom: 1px solid $quinary-content; display: grid; - grid-gap: $spacing-24; + gap: $spacing-24; justify-content: left; grid-template-columns: 100%; diff --git a/res/css/components/views/settings/devices/_DeviceTile.pcss b/res/css/components/views/settings/devices/_DeviceTile.pcss index e4096329d6..07ee70792d 100644 --- a/res/css/components/views/settings/devices/_DeviceTile.pcss +++ b/res/css/components/views/settings/devices/_DeviceTile.pcss @@ -35,7 +35,7 @@ Please see LICENSE files in the repository root for full details. .mx_DeviceTile_actions { display: grid; - grid-gap: $spacing-8; + gap: $spacing-8; grid-auto-flow: column; margin-left: $spacing-8; } diff --git a/res/css/components/views/settings/devices/_FilteredDeviceList.pcss b/res/css/components/views/settings/devices/_FilteredDeviceList.pcss index aac5986280..06f5a80b65 100644 --- a/res/css/components/views/settings/devices/_FilteredDeviceList.pcss +++ b/res/css/components/views/settings/devices/_FilteredDeviceList.pcss @@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details. .mx_FilteredDeviceList_list { list-style-type: none; display: grid; - grid-gap: $spacing-16; + gap: $spacing-16; margin: 0; padding: 0 $spacing-16; } diff --git a/res/css/components/views/settings/shared/_SettingsSubsection.pcss b/res/css/components/views/settings/shared/_SettingsSubsection.pcss index 0d03a12b1d..3b22c679c5 100644 --- a/res/css/components/views/settings/shared/_SettingsSubsection.pcss +++ b/res/css/components/views/settings/shared/_SettingsSubsection.pcss @@ -39,7 +39,7 @@ Please see LICENSE files in the repository root for full details. .mx_SettingsSubsection_content { width: 100%; display: grid; - grid-gap: $spacing-8; + gap: $spacing-8; /* setting minwidth 0 makes columns definitely sized fixing horizontal overflow */ grid-template-columns: minmax(0, 1fr); justify-items: flex-start; diff --git a/res/css/views/audio_messages/_AudioPlayer.pcss b/res/css/views/audio_messages/_AudioPlayer.pcss deleted file mode 100644 index 51e97611f5..0000000000 --- a/res/css/views/audio_messages/_AudioPlayer.pcss +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_MediaBody.mx_AudioPlayer_container { - padding: 16px 12px 12px 12px; - - .mx_AudioPlayer_primaryContainer { - display: flex; - - .mx_PlayPauseButton { - margin-right: 8px; - } - - .mx_AudioPlayer_mediaInfo { - flex: 1; - overflow: hidden; /* makes the ellipsis on the file name work */ - - & > * { - display: block; - } - - .mx_AudioPlayer_mediaName { - color: $primary-content; - font-size: $font-15px; - line-height: $font-15px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - padding-bottom: 4px; /* mimics the line-height differences in the Figma */ - } - - .mx_AudioPlayer_byline { - font-size: $font-12px; - line-height: $font-12px; - } - } - } - - .mx_AudioPlayer_seek { - display: flex; - align-items: center; - - .mx_SeekBar { - flex: 1; - } - - .mx_Clock { - min-width: $font-42px; /* for flexbox */ - padding-left: $spacing-4; /* isolate from seek bar */ - text-align: justify; - white-space: nowrap; - } - } -} 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/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index 9d81097d60..2c78a62f8d 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -15,6 +15,23 @@ Please see LICENSE files in the repository root for full details. } .mx_AccessSecretStorageDialog_primaryContainer { + .mx_AccessSecretStorageDialog_recoveryKeyEntry { + /* + * Be specific here to avoid "margin: 9px" from _common.pcss + */ + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) { + input { + /* + * From figma: https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=102-43729&t=QmewENUd7f6Tmw9U-1 + */ + width: 448px; + height: 70px; + margin: 0px; + border: 1px solid; + } + } + } + .mx_AccessSecretStorageDialog_recoveryKeyFeedback { &::before { content: ""; diff --git a/res/css/views/messages/_MPollBody.pcss b/res/css/views/messages/_MPollBody.pcss index 33144083ea..9889bb81bb 100644 --- a/res/css/views/messages/_MPollBody.pcss +++ b/res/css/views/messages/_MPollBody.pcss @@ -60,7 +60,7 @@ Please see LICENSE files in the repository root for full details. .mx_MPollBody_allOptions { display: grid; - grid-gap: $spacing-16; + gap: $spacing-16; margin-bottom: $spacing-8; max-width: 550px; } diff --git a/res/css/views/polls/pollHistory/_PollHistoryList.pcss b/res/css/views/polls/pollHistory/_PollHistoryList.pcss index 95d54192f9..b732545178 100644 --- a/res/css/views/polls/pollHistory/_PollHistoryList.pcss +++ b/res/css/views/polls/pollHistory/_PollHistoryList.pcss @@ -21,12 +21,12 @@ Please see LICENSE files in the repository root for full details. flex: 1 1 0; align-content: flex-start; display: grid; - grid-gap: $spacing-20; + gap: $spacing-20; padding-right: $spacing-64; margin: $spacing-32 0; &.mx_PollHistoryList_list_ENDED { - grid-gap: $spacing-32; + gap: $spacing-32; } } 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/_NotificationSettings2.pcss b/res/css/views/settings/_NotificationSettings2.pcss index d579c22b95..285282c89c 100644 --- a/res/css/views/settings/_NotificationSettings2.pcss +++ b/res/css/views/settings/_NotificationSettings2.pcss @@ -30,7 +30,7 @@ Please see LICENSE files in the repository root for full details. .mx_SettingsSubsection_content { margin-top: 12px; - grid-gap: 12px; + gap: 12px; justify-items: stretch; justify-content: stretch; } @@ -40,7 +40,7 @@ Please see LICENSE files in the repository root for full details. } .mx_NotificationSettings2_flags { - grid-gap: 4px; + gap: 4px; } .mx_StyledRadioButton_content { diff --git a/res/css/views/settings/_Notifications.pcss b/res/css/views/settings/_Notifications.pcss index e4e450fd58..a97d7529d5 100644 --- a/res/css/views/settings/_Notifications.pcss +++ b/res/css/views/settings/_Notifications.pcss @@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details. display: grid; grid-template-columns: auto repeat(3, 62px); place-items: center center; - grid-gap: 8px; + gap: 8px; /* Override StyledRadioButton default styles */ .mx_StyledRadioButton { 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/settings/tabs/_SettingsSection.pcss b/res/css/views/settings/tabs/_SettingsSection.pcss index 997343190d..ce3c9266c3 100644 --- a/res/css/views/settings/tabs/_SettingsSection.pcss +++ b/res/css/views/settings/tabs/_SettingsSection.pcss @@ -34,7 +34,7 @@ Please see LICENSE files in the repository root for full details. .mx_SettingsSection_subSections { display: grid; grid-template-columns: minmax(0, 1fr); - grid-gap: $spacing-32; + gap: $spacing-32; padding: $spacing-16 0; } diff --git a/res/css/views/settings/tabs/_SettingsTab.pcss b/res/css/views/settings/tabs/_SettingsTab.pcss index e0abf08e83..99690d5657 100644 --- a/res/css/views/settings/tabs/_SettingsTab.pcss +++ b/res/css/views/settings/tabs/_SettingsTab.pcss @@ -84,7 +84,7 @@ Please see LICENSE files in the repository root for full details. .mx_SettingsTab_sections { display: grid; grid-template-columns: 1fr; - grid-gap: $spacing-32; + gap: $spacing-32; padding-bottom: $spacing-16; } diff --git a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.pcss index b3251f3e3c..44452be744 100644 --- a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.pcss @@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details. padding: 0; width: 100%; display: grid; - grid-gap: $spacing-4; + gap: $spacing-4; } .mx_KeyboardShortcut_shortcutRow, 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/sonar-project.properties b/sonar-project.properties index 23333a43cc..31ce8d776f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,7 +6,7 @@ sonar.organization=element-hq sonar.sources=src,res sonar.tests=test,playwright,src -sonar.test.inclusions=test/*,playwright/*,src/**/*.test.tsx +sonar.test.inclusions=test/*,playwright/*,src/**/*.test.* sonar.exclusions=__mocks__,docs,element.io,nginx sonar.cpd.exclusions=src/i18n/strings/*.json 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/DateUtils.ts b/src/DateUtils.ts index e788ca09bf..a5e22c0891 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -13,6 +13,8 @@ import { type Optional } from "matrix-events-sdk"; import { _t, getUserLanguage } from "./languageHandler"; import { getUserTimezone } from "./TimezoneHandler"; +export { formatSeconds } from "./shared-components/utils/DateUtils"; + export const MINUTE_MS = 60000; export const HOUR_MS = MINUTE_MS * 60; export const DAY_MS = HOUR_MS * 24; @@ -180,31 +182,6 @@ export function formatTime(date: Date, showTwelveHour = false, locale?: string): }).format(date); } -export function formatSeconds(inSeconds: number): string { - const isNegative = inSeconds < 0; - inSeconds = Math.abs(inSeconds); - - const hours = Math.floor(inSeconds / (60 * 60)) - .toFixed(0) - .padStart(2, "0"); - const minutes = Math.floor((inSeconds % (60 * 60)) / 60) - .toFixed(0) - .padStart(2, "0"); - const seconds = Math.floor((inSeconds % (60 * 60)) % 60) - .toFixed(0) - .padStart(2, "0"); - - let output = ""; - if (hours !== "00") output += `${hours}:`; - output += `${minutes}:${seconds}`; - - if (isNegative) { - output = "-" + output; - } - - return output; -} - export function formatTimeLeft(inSeconds: number): string { const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0); const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0); 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 a02530a1cf..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"; @@ -26,22 +25,24 @@ export interface IInviteResult { } /** - * Invites multiple addresses to a room - * Simpler interface to utils/MultiInviter but with - * no option to cancel. + * Invites multiple addresses to a room. + * + * 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/SlashCommands.tsx b/src/SlashCommands.tsx index ed2382c3ff..f0d9085507 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -148,7 +148,7 @@ export const Commands = [ command: "upgraderoom", args: "", description: _td("slash_command|upgraderoom"), - isEnabled: (cli) => !isCurrentLocalRoom(cli) && SettingsStore.getValue("developerMode"), + isEnabled: (cli) => !isCurrentLocalRoom(cli), runFn: function (cli, roomId, threadId, args) { if (args) { const room = cli.getRoom(roomId); diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index b63e5b2a00..60ada112e0 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -36,9 +36,9 @@ import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog"; import AccessibleButton from "./components/views/elements/AccessibleButton"; import RightPanelStore from "./stores/right-panel/RightPanelStore"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; -import { ElementCall } from "./models/Call"; import { getSenderName } from "./utils/event/getSenderName"; import PosthogTrackers from "./PosthogTrackers.ts"; +import { ElementCallEventType } from "./call-types.ts"; function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string { const roomId = event.getRoomId(); @@ -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); @@ -922,7 +925,7 @@ for (const evType of ALL_RULE_TYPES) { } // Add both stable and unstable m.call events -for (const evType of ElementCall.CALL_EVENT_TYPE.names) { +for (const evType of ElementCallEventType.names) { stateHandlers[evType] = textForCallEvent; } 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/audio/Playback.ts b/src/audio/Playback.ts index 54d2c710d0..96930d43af 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -15,7 +15,7 @@ import { arrayFastResample } from "../utils/arrays"; import { type IDestroyable } from "../utils/IDestroyable"; import { PlaybackClock } from "./PlaybackClock"; import { createAudioContext, decodeOgg } from "./compat"; -import { clamp } from "../utils/numbers"; +import { clamp } from "../shared-components/utils/numbers"; import { DEFAULT_WAVEFORM, PLAYBACK_WAVEFORM_SAMPLES } from "./consts"; import { PlaybackEncoder } from "../PlaybackEncoder"; diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts index ec4a143c4e..5f96e686ce 100644 --- a/src/audio/RecorderWorklet.ts +++ b/src/audio/RecorderWorklet.ts @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts"; -import { percentageOf } from "../utils/numbers"; +import { percentageOf } from "../shared-components/utils/numbers"; // from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope declare const currentTime: number; diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index bde86f9dd7..2c5788ef21 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -19,7 +19,7 @@ import { PayloadEvent, WORKLET_NAME } from "./consts"; import { UPDATE_EVENT } from "../stores/AsyncStore"; import { createAudioContext } from "./compat"; import { FixedRollingArray } from "../utils/FixedRollingArray"; -import { clamp } from "../utils/numbers"; +import { clamp } from "../shared-components/utils/numbers"; import recorderWorkletFactory from "./recorderWorkletFactory"; const CHANNELS = 1; // stereo isn't important diff --git a/src/call-types.ts b/src/call-types.ts index 6586bcf3b9..9ee870945f 100644 --- a/src/call-types.ts +++ b/src/call-types.ts @@ -6,6 +6,9 @@ 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 { EventType } from "matrix-js-sdk/src/matrix"; +import { NamespacedValue } from "matrix-js-sdk/src/NamespacedValue"; + export const JitsiCallMemberEventType = "io.element.video.member"; export interface JitsiCallMemberContent { @@ -14,3 +17,9 @@ export interface JitsiCallMemberContent { // Time at which this state event should be considered stale expires_ts: number; } + +// Element Call no longer sends this event type; it only exists to support timeline rendering of +// group calls from a previous iteration of the group VoIP MSCs (MSC3401) which used it. +export const ElementCallEventType = new NamespacedValue(null, EventType.GroupCallPrefix); + +export const ElementCallMemberEventType = new NamespacedValue(null, EventType.GroupCallMemberPrefix); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 26e127f21f..1769a96400 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -111,6 +111,7 @@ interface IState { backgroundImage?: string; } +const NEW_ROOM_LIST_MIN_WIDTH = 224; /** * This is what our MatrixChat shows when we are logged in. The precise view is * determined by the page_type property. @@ -261,10 +262,15 @@ class LoggedInView extends React.Component { let panelCollapsed: boolean; const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel - const toggleSize = useNewRoomList ? 224 : 206 - 50; + const toggleSize = useNewRoomList ? NEW_ROOM_LIST_MIN_WIDTH : 206 - 50; + const collapseConfig: ICollapseConfig = { toggleSize, onCollapsed: (collapsed) => { + if (useNewRoomList) { + // The new room list does not support collapsing. + return; + } panelCollapsed = collapsed; if (collapsed) { dis.dispatch({ action: "hide_left_panel" }); @@ -281,11 +287,13 @@ class LoggedInView extends React.Component { this.props.resizeNotifier.startResizing(); }, onResizeStop: () => { - if (!panelCollapsed) window.localStorage.setItem("mx_lhs_size", "" + panelSize); + // Always save the lhs size for the new room list. + if (useNewRoomList || !panelCollapsed) window.localStorage.setItem("mx_lhs_size", "" + panelSize); this.props.resizeNotifier.stopResizing(); }, isItemCollapsed: (domNode) => { - return domNode.classList.contains("mx_LeftPanel_minimized"); + // New rooms list does not support collapsing. + return !useNewRoomList && domNode.classList.contains("mx_LeftPanel_minimized"); }, handler: this.resizeHandler.current ?? undefined, }; @@ -299,8 +307,11 @@ class LoggedInView extends React.Component { } private loadResizerPreferences(): void { + const useNewRoomList = SettingsStore.getValue("feature_new_room_list"); let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size")!, 10); - if (isNaN(lhsSize)) { + // If the user has not set a size, or for the new room list if the size is less than the minimum width, + // set a default size. + if (isNaN(lhsSize) || (useNewRoomList && lhsSize < NEW_ROOM_LIST_MIN_WIDTH)) { lhsSize = 350; } this.resizer?.forHandleWithId("lp-resizer")?.resize(lhsSize); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index e61713ca69..39c0147da0 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -141,6 +141,7 @@ import { type OpenForwardDialogPayload } from "../../dispatcher/payloads/OpenFor import { ShareFormat, type SharePayload } from "../../dispatcher/payloads/SharePayload"; import Markdown from "../../Markdown"; import { sanitizeHtmlParams } from "../../Linkify"; +import { isOnlyAdmin } from "../../utils/membership"; // legacy export export { default as Views } from "../../Views"; @@ -1255,29 +1256,22 @@ export default class MatrixChat extends React.PureComponent { const client = MatrixClientPeg.get(); if (client && roomToLeave) { - const plEvent = roomToLeave.currentState.getStateEvents(EventType.RoomPowerLevels, ""); - const plContent = plEvent ? plEvent.getContent() : {}; - const userLevels = plContent.users || {}; - const currentUserLevel = userLevels[client.getUserId()!]; - const userLevelValues = Object.values(userLevels); - if (userLevelValues.every((x) => typeof x === "number")) { + // If the user is the only user with highest power level + if (isOnlyAdmin(roomToLeave)) { + const userLevelValues = roomToLeave.getJoinedMembers().map((m) => m.powerLevel); + const maxUserLevel = Math.max(...(userLevelValues as number[])); - // If the user is the only user with highest power level - if ( - maxUserLevel === currentUserLevel && - userLevelValues.lastIndexOf(maxUserLevel) == userLevelValues.indexOf(maxUserLevel) - ) { - const warning = - maxUserLevel >= 100 - ? _t("leave_room_dialog|room_leave_admin_warning") - : _t("leave_room_dialog|room_leave_mod_warning"); - warnings.push( - - {" " /* Whitespace, otherwise the sentences get smashed together */} - {warning} - , - ); - } + + const warning = + maxUserLevel >= 100 + ? _t("leave_room_dialog|room_leave_admin_warning") + : _t("leave_room_dialog|room_leave_mod_warning"); + warnings.push( + + {" " /* Whitespace, otherwise the sentences get smashed together */} + {warning} + , + ); } } 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 ( -