diff --git a/.github/workflows/build_desktop_and_deploy.yaml b/.github/workflows/build_desktop_and_deploy.yaml index 5611b2d4fd..1bddf5f635 100644 --- a/.github/workflows/build_desktop_and_deploy.yaml +++ b/.github/workflows/build_desktop_and_deploy.yaml @@ -289,7 +289,7 @@ jobs: id-token: write # This is required for requesting the JWT steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6 + uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6 with: role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI role-session-name: githubaction-run-${{ github.run_id }} diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 246363b1df..717676bd5e 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -111,7 +111,7 @@ jobs: running-workflow-name: "Build & Deploy develop.element.io" repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 10 - check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify|Report).)*$ + check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify|Report|deploy).)*$ # We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier # as the expires after 24h and requires auth to download. diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 46800f02f0..d03f08487d 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -31,7 +31,7 @@ jobs: persist-credentials: false - name: Install Cosign - uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: Set up QEMU uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 97726eb6ef..cbca8ebdc3 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: Install Cosign - uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 if: github.event_name != 'pull_request' - name: Set up QEMU diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f365f39531..332798676e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,7 @@ jobs: - name: Get number of CPU cores id: cpu-cores - uses: SimenB/github-actions-cpu-cores@97ba232459a8e02ff6121db9362b09661c875ab8 # v2 + uses: SimenB/github-actions-cpu-cores@97330871fe1b7d3529392ea000e3d2c4b357e403 # v3 - name: Run tests working-directory: apps/web diff --git a/.github/workflows/triage-assigned.yml b/.github/workflows/triage-assigned.yml index b16f626c15..93545e74e1 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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 d81322bc8c..508a8c51d1 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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 582207dc14..3f56e9f784 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -61,7 +61,7 @@ jobs: contains(github.event.issue.labels.*.name, 'X-Needs-Info') steps: - id: add_to_project - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: ${{ env.PROJECT_URL }} github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -84,7 +84,7 @@ jobs: contains(github.event.issue.labels.*.name, 'Z-Flaky-Test') steps: - id: add_to_project - uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: ${{ env.PROJECT_URL }} github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} @@ -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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 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@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 + - uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0 with: project-url: https://github.com/orgs/element-hq/projects/101 github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/apps/desktop/dockerbuild/Dockerfile b/apps/desktop/dockerbuild/Dockerfile index f9521745ba..c529c5619b 100644 --- a/apps/desktop/dockerbuild/Dockerfile +++ b/apps/desktop/dockerbuild/Dockerfile @@ -1,7 +1,7 @@ # Docker image to facilitate building Element Desktop's native bits using a glibc version (2.31) # with broader compatibility, down to Debian bullseye & Ubuntu focal. -FROM rust:bullseye@sha256:949b0903defbfc4e374dc85f947b153859e9ee0104e425cd9a74d94474a9a335 +FROM rust:bullseye@sha256:85f9d38ab80fa5752a6fd5bff34c953a59ce2c7ccb0d47fb678d3c0300b8a331 ENV DEBIAN_FRONTEND=noninteractive diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 84db56e160..81f93a4947 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -83,12 +83,12 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "catalog:", - "app-builder-lib": "26.9.0", + "app-builder-lib": "26.9.1", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", - "electron": "41.2.2", - "electron-builder": "26.9.0", - "electron-builder-squirrel-windows": "26.9.0", + "electron": "42.0.0", + "electron-builder": "26.9.1", + "electron-builder-squirrel-windows": "26.9.1", "electron-devtools-installer": "^4.0.0", "eslint": "^8.26.0", "eslint-config-google": "^0.14.0", @@ -112,7 +112,7 @@ "hakDependencies": { "matrix-seshat": "4.2.0" }, - "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", + "packageManager": "pnpm@10.33.3+sha512.a19744364a7e248b92657a4ca5973f9354d21caf982579674b1c539f32c7420c47138ad8b1254df07aba9bc782d9b3029e3db34d5dbff974326eb74dac8ff489", "nx": { "includedScripts": [] } diff --git a/apps/desktop/src/build-config.test.ts b/apps/desktop/src/build-config.test.ts new file mode 100644 index 0000000000..2979254c3c --- /dev/null +++ b/apps/desktop/src/build-config.test.ts @@ -0,0 +1,35 @@ +/* +Copyright 2026 Element Creations 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 { expect, describe, it, beforeEach, vi } from "vitest"; +import { fs as memfs, vol } from "memfs"; + +import { getBuildConfig } from "./build-config.js"; + +vi.mock("node:fs", () => ({ default: memfs })); + +beforeEach(() => { + // Reset the state of the in-memory fs + vol.reset(); +}); + +describe("getBuildConfig", () => { + it("should read fields from package.json correctly", () => { + vol.fromJSON({ + "package.json": JSON.stringify({ + electron_appId: "app.id", + electron_protocol: "proto", + electron_windows_cert_sn: "subject.name", + }), + }); + + const config = getBuildConfig(); + expect(config.appId).toBe("app.id"); + expect(config.protocol).toBe("proto"); + expect(config.windowsCertSubjectName).toBe("subject.name"); + }); +}); diff --git a/apps/desktop/src/icon.test.ts b/apps/desktop/src/icon.test.ts new file mode 100644 index 0000000000..35df79e070 --- /dev/null +++ b/apps/desktop/src/icon.test.ts @@ -0,0 +1,44 @@ +/* +Copyright 2026 Element Creations 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 { expect, describe, it, beforeEach, vi } from "vitest"; +import { fs as memfs, vol } from "memfs"; +import path from "node:path"; + +import { getIconPath } from "./icon.js"; + +vi.mock("node:fs/promises", () => ({ default: memfs.promises })); + +beforeEach(() => { + // Reset the state of the in-memory fs + vol.reset(); +}); + +describe("getIconPath", () => { + beforeEach(() => { + vol.fromJSON( + { + "build/icon.png": "png", + "build/icon.ico": "ico", + }, + "../webapp", + ); + }); + + it("should use .ico on Windows", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + await expect(getIconPath()).resolves.toEqual(path.resolve("../build/icon.ico")); + }); + it("should use .png on macOS", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + await expect(getIconPath()).resolves.toEqual(path.resolve("../build/icon.png")); + }); + it("should use .png on Linux", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("linux"); + await expect(getIconPath()).resolves.toEqual(path.resolve("../build/icon.png")); + }); +}); diff --git a/apps/desktop/src/utils.test.ts b/apps/desktop/src/utils.test.ts new file mode 100644 index 0000000000..aa099c6f65 --- /dev/null +++ b/apps/desktop/src/utils.test.ts @@ -0,0 +1,78 @@ +/* +Copyright 2026 Element Creations 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 { expect, describe, it, beforeEach, vi } from "vitest"; +import { fs as memfs, vol } from "memfs"; + +import { loadJsonFile, tryPaths, randomArray } from "./utils.js"; + +vi.mock("node:fs", () => ({ default: memfs })); +vi.mock("node:fs/promises", () => ({ default: memfs.promises })); + +beforeEach(() => { + // Reset the state of the in-memory fs + vol.reset(); +}); + +describe("randomArray", () => { + it("should return an array matching the requested size", async () => { + function toUnpaddedBase64Size(size: number): number { + return Math.ceil((4 * size) / 3); + } + + await expect(randomArray(100)).resolves.toHaveLength(toUnpaddedBase64Size(100)); + await expect(randomArray(32)).resolves.toHaveLength(toUnpaddedBase64Size(32)); + }); + + it("should return a unique random array", async () => { + const arr1 = await randomArray(60); + const arr2 = await randomArray(60); + expect(arr1).not.toEqual(arr2); + }); +}); + +describe("loadJsonFile", () => { + beforeEach(() => { + vol.fromJSON({ + "./file.json": JSON.stringify({ file1: true }), + "./nested/deep/file.json": JSON.stringify({ file2: true }), + }); + }); + + it("should load and parse a JSON file correctly", () => { + expect(loadJsonFile("file.json")).toStrictEqual({ file1: true }); + }); + + it("should use args as path segments", () => { + expect(loadJsonFile("nested", "deep", "file.json")).toStrictEqual({ file2: true }); + }); + + it("should return an empty object when file does not exist", () => { + expect(loadJsonFile("unknown-file.json")).toStrictEqual({}); + }); +}); + +describe("tryPaths", () => { + beforeEach(() => { + vol.fromNestedJSON({ + "./dirA/": {}, + "./dir/dirB/": {}, + }); + }); + + it("should find file relative to given root", async () => { + await expect(tryPaths("name", "dir", ["dirB"])).resolves.toEqual("dir/dirB/"); + }); + + it("should handle unknown paths", async () => { + await expect(tryPaths("name", ".", ["dirB", "dirA"])).resolves.toEqual("dirA/"); + }); + + it("should throw error if file does not exist", async () => { + await expect(tryPaths("name", "dir", ["a.json", "b.json"])).rejects.toThrow("Failed to find name path"); + }); +}); diff --git a/apps/desktop/src/utils.ts b/apps/desktop/src/utils.ts index 368211aba7..6adb91ffa8 100644 --- a/apps/desktop/src/utils.ts +++ b/apps/desktop/src/utils.ts @@ -10,6 +10,10 @@ import fs from "node:fs"; import path from "node:path"; import afs from "node:fs/promises"; +/** + * Returns a random array of a specified size in unpadded base64 + * @param size - the size of the underlying random array + */ export async function randomArray(size: number): Promise { return new Promise((resolve, reject) => { crypto.randomBytes(size, (err, buf) => { @@ -62,9 +66,9 @@ export async function tryPaths(name: string, root: string, rawPaths: string[]): return p + "/"; } catch {} } - console.log(`Couldn't find ${name} files in any of: `); + console.log(`Couldn't find '${name}' in any of: `); for (const p of paths) { console.log("\t" + path.resolve(p)); } - throw new Error(`Failed to find ${name} files`); + throw new Error(`Failed to find ${name} path`); } diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index a29297130e..8c0049fb07 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -2,7 +2,7 @@ # Context must be the root of the monorepo # Builder -FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:d2059a9c157c9f70739736979fa3635008bf3ca74560b30930dc181228bc427f AS builder +FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:2c00db8852d28215c6203fafe9f05046acd9fdd48bcfc42467f4cba39b42dab4 AS builder # Support custom branch of the js-sdk. This also helps us build images of element-web develop. ARG USE_CUSTOM_SDKS=false @@ -25,7 +25,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json # App -FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:360465db60105a4cbf5215cd9e5a2ba40ef956978dd94f99707e9674050e38ea +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:1df9285ed5bdaaad9ca503ac608e12fe1ba93136bb249fe976477989c1db4ede # Need root user to install packages & manipulate the usr directory USER root diff --git a/apps/web/jest.config.ts b/apps/web/jest.config.ts index 5d3d16f464..c1a8b6ad23 100644 --- a/apps/web/jest.config.ts +++ b/apps/web/jest.config.ts @@ -42,7 +42,6 @@ const config: Config = { "workers/(.+)Factory": "/__mocks__/workerFactoryMock.js", "^!!raw-loader!.*": "jest-raw-loader", "recorderWorkletFactory": "/__mocks__/empty.js", - "counterpart": "/../../node_modules/counterpart", "@vector-im/compound-web": "/../../node_modules/@vector-im/compound-web", }, transformIgnorePatterns: [ diff --git a/apps/web/package.json b/apps/web/package.json index c04ddac5e8..93c57ef6af 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -67,7 +67,7 @@ "emojibase-regex": "^17.0.0", "escape-html": "^1.0.3", "file-saver": "^2.0.5", - "filesize": "11.0.15", + "filesize": "11.0.17", "github-markdown-css": "^5.5.1", "glob-to-regexp": "^0.4.1", "highlight.js": "^11.3.1", @@ -89,7 +89,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.369.3", + "posthog-js": "1.372.8", "qrcode": "1.5.4", "re-resizable": "6.11.2", "react": "catalog:", @@ -124,7 +124,7 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", - "@casualbot/jest-sonar-reporter": "2.6.0", + "@casualbot/jest-sonar-reporter": "2.7.0", "@element-hq/element-call-embedded": "0.19.2", "@element-hq/element-web-playwright-common": "workspace:*", "@fetch-mock/jest": "^0.2.20", @@ -141,7 +141,6 @@ "@testing-library/user-event": "^14.5.2", "@types/commonmark": "^0.27.4", "@types/content-type": "^1.1.9", - "@types/counterpart": "^0.18.1", "@types/css-tree": "^2.3.8", "@types/diff-match-patch": "^1.0.32", "@types/escape-html": "^1.0.1", @@ -204,7 +203,7 @@ "mini-css-extract-plugin": "2.10.2", "modernizr": "^3.12.0", "playwright-core": "catalog:", - "postcss": "8.5.10", + "postcss": "8.5.14", "postcss-easings": "4.0.0", "postcss-hexrgba": "2.1.0", "postcss-import": "16.1.1", @@ -244,6 +243,6 @@ "engines": { "node": ">=22.18" }, - "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", + "packageManager": "pnpm@10.33.3+sha512.a19744364a7e248b92657a4ca5973f9354d21caf982579674b1c539f32c7420c47138ad8b1254df07aba9bc782d9b3029e3db34d5dbff974326eb74dac8ff489", "private": true } diff --git a/apps/web/playwright/e2e/app-loading/guest-registration.spec.ts b/apps/web/playwright/e2e/app-loading/guest-registration.spec.ts index d5679bc017..a36e97ac45 100644 --- a/apps/web/playwright/e2e/app-loading/guest-registration.spec.ts +++ b/apps/web/playwright/e2e/app-loading/guest-registration.spec.ts @@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details. * Tests for application startup with guest registration enabled on the server. */ +import type { Page } from "playwright-core"; import { expect, test } from "../../element-web-test"; test.use({ @@ -18,12 +19,28 @@ test.use({ }, }); +const screenshotOptions = (page?: Page) => ({ + // Hide the UserID + css: ` + span[data-testid="userId"] { + display: none !important; + } + `, +}); + test("Shows the welcome page by default", async ({ page }) => { await page.goto("/"); await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible(); await expect(page.getByRole("link", { name: "Sign in" })).toBeVisible(); }); +test("Shows the user menu for guests", { tag: ["@screenshot"] }, async ({ page, app }) => { + await page.goto("/#/room/!room:id"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + const menu = await app.openUserMenu(); + await expect(menu).toMatchScreenshot("guest-menu.png", screenshotOptions(page)); +}); + test("Room link correctly loads a room view", async ({ page }) => { await page.goto("/#/room/!room:id"); await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); diff --git a/apps/web/playwright/e2e/app-loading/stored-credentials.spec.ts b/apps/web/playwright/e2e/app-loading/stored-credentials.spec.ts index e47df68ede..ccc187c037 100644 --- a/apps/web/playwright/e2e/app-loading/stored-credentials.spec.ts +++ b/apps/web/playwright/e2e/app-loading/stored-credentials.spec.ts @@ -23,6 +23,18 @@ test("Shows the homepage by default", async ({ pageWithCredentials: page }) => { await expect(page.getByRole("heading", { name: "Welcome Boris", exact: true })).toBeVisible(); }); +test( + "Adjusts homepage button layout in thin viewport", + { tag: "@screenshot" }, + async ({ pageWithCredentials: page }) => { + await page.setViewportSize({ width: 920, height: 720 }); + await page.goto("/#/home"); + await page.waitForSelector(".mx_HomePage", { timeout: 30000 }); + + await expect(page.locator(".mx_HomePage")).toMatchScreenshot("home-thin-viewport.png"); + }, +); + test("Shows the last known page on reload", async ({ pageWithCredentials: page }) => { await page.goto("/"); await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); diff --git a/apps/web/playwright/e2e/crypto/logout.spec.ts b/apps/web/playwright/e2e/crypto/logout.spec.ts index 4eedca2942..6cf02f3408 100644 --- a/apps/web/playwright/e2e/crypto/logout.spec.ts +++ b/apps/web/playwright/e2e/crypto/logout.spec.ts @@ -23,7 +23,9 @@ test.describe("Logout tests", () => { await sendMessageInCurrentRoom(page, "Hello secret world"); const locator = await app.settings.openUserMenu(); - await locator.getByRole("menuitem", { name: "Remove this device", exact: true }).click(); + + await locator.getByRole("menuitem", { name: "All settings", exact: true }).click(); + await page.getByRole("button", { name: "Remove this device", exact: true }).click(); const currentDialogLocator = page.locator(".mx_Dialog"); @@ -41,7 +43,8 @@ test.describe("Logout tests", () => { await sendMessageInCurrentRoom(page, "Hello secret world"); const locator = await app.settings.openUserMenu(); - await locator.getByRole("menuitem", { name: "Remove this device", exact: true }).click(); + await locator.getByRole("menuitem", { name: "All settings", exact: true }).click(); + await page.getByRole("button", { name: "Remove this device", exact: true }).click(); const currentDialogLocator = page.locator(".mx_Dialog"); @@ -54,7 +57,8 @@ test.describe("Logout tests", () => { await sendMessageInCurrentRoom(page, "Hello public world!"); const locator = await app.settings.openUserMenu(); - await locator.getByRole("menuitem", { name: "Remove this device", exact: true }).click(); + await locator.getByRole("menuitem", { name: "All settings", exact: true }).click(); + await page.getByRole("button", { name: "Remove this device", exact: true }).click(); // Should have logged out directly await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible(); diff --git a/apps/web/playwright/e2e/crypto/utils.ts b/apps/web/playwright/e2e/crypto/utils.ts index ca51b7d9d8..f08863ccdb 100644 --- a/apps/web/playwright/e2e/crypto/utils.ts +++ b/apps/web/playwright/e2e/crypto/utils.ts @@ -252,7 +252,9 @@ export async function logIntoElementAndVerify(page: Page, credentials: Credentia */ export async function logOutOfElement(page: Page, discardKeys: boolean = false) { await page.getByRole("button", { name: "User menu" }).click(); - await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Remove this device" }).click(); + + await page.getByRole("menu", { name: "User menu" }).getByRole("menuitem", { name: "All settings" }).click(); + await page.getByRole("button", { name: "Remove this device" }).click(); if (discardKeys) { await page.getByRole("button", { name: "I don't want my encrypted messages" }).click(); } else { diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 66d905afb7..a782913f8b 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -328,8 +328,11 @@ test.describe("Room list", () => { const videoRoom = roomListView.getByRole("option", { name: "video room" }); await expect(videoRoom).toHaveAttribute("aria-selected", "true"); // wait for room list update + // Ensure we highlight the video + await videoRoom.click(); + // focus the user menu to avoid to have hover decoration - await page.getByRole("button", { name: "User menu" }).focus(); + await page.getByRole("button", { name: "User menu" }).hover(); await expect(videoRoom).toMatchScreenshot("room-list-item-video.png"); }); diff --git a/apps/web/playwright/e2e/login/login-consent.spec.ts b/apps/web/playwright/e2e/login/login-consent.spec.ts index 2dc33eaf21..23f10e39bb 100644 --- a/apps/web/playwright/e2e/login/login-consent.spec.ts +++ b/apps/web/playwright/e2e/login/login-consent.spec.ts @@ -340,11 +340,8 @@ test.describe("Login", () => { // Allow the outstanding requests queue to settle before logging out await page.waitForTimeout(2000); - - await page - .locator(".mx_UserMenu_contextMenu") - .getByRole("menuitem", { name: "Remove this device" }) - .click(); + await page.getByRole("menu", { name: "User menu" }).getByRole("menuitem", { name: "All settings" }).click(); + await page.getByRole("button", { name: "Remove this device" }).click(); await expect(page).toHaveURL(/\/#\/welcome$/); }); }); diff --git a/apps/web/playwright/e2e/login/logout_redirect_url.spec.ts b/apps/web/playwright/e2e/login/logout_redirect_url.spec.ts index df0b8983d5..57596de0f4 100644 --- a/apps/web/playwright/e2e/login/logout_redirect_url.spec.ts +++ b/apps/web/playwright/e2e/login/logout_redirect_url.spec.ts @@ -28,8 +28,9 @@ test.describe("logout with logout_redirect_url", () => { // give a change for the outstanding requests queue to settle before logging out await page.waitForTimeout(2000); + await page.getByRole("menu", { name: "User menu" }).getByRole("menuitem", { name: "All settings" }).click(); + await page.getByRole("button", { name: "Remove this device" }).click(); - await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Remove this device" }).click(); await expect(page).toHaveURL(/\/decoder-ring\/$/); }); }); diff --git a/apps/web/playwright/e2e/oidc/oidc-native.spec.ts b/apps/web/playwright/e2e/oidc/oidc-native.spec.ts index f81d6b71ff..21b79a1d13 100644 --- a/apps/web/playwright/e2e/oidc/oidc-native.spec.ts +++ b/apps/web/playwright/e2e/oidc/oidc-native.spec.ts @@ -74,7 +74,8 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { (request) => request.url() === revokeUri && request.postDataJSON()["token_type_hint"] === "refresh_token", ); const locator = await app.settings.openUserMenu(); - await locator.getByRole("menuitem", { name: "Remove this device", exact: true }).click(); + await locator.getByRole("menuitem", { name: "All settings", exact: true }).click(); + await page.getByRole("button", { name: "Remove this device", exact: true }).click(); await revokeAccessTokenPromise; await revokeRefreshTokenPromise; }); @@ -122,7 +123,8 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { // Allow the outstanding requests queue to settle before logging out await page.waitForTimeout(2000); - await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Remove this device" }).click(); + await page.getByRole("menu", { name: "User menu" }).getByRole("menuitem", { name: "All settings" }).click(); + await page.getByRole("button", { name: "Remove this device" }).click(); await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again @@ -155,10 +157,8 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { await page.getByRole("button", { name: "User menu" }).click(); await expect(page.getByText(userId, { exact: true })).toBeVisible(); await page.waitForTimeout(2000); - await page - .locator(".mx_UserMenu_contextMenu") - .getByRole("menuitem", { name: "Remove this device" }) - .click(); + await page.getByRole("menu", { name: "User menu" }).getByRole("menuitem", { name: "All settings" }).click(); + await page.getByRole("button", { name: "Remove this device" }).click(); await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again @@ -206,9 +206,10 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { await expect(page.getByText(userId, { exact: true })).toBeVisible(); await page.waitForTimeout(2000); await page - .locator(".mx_UserMenu_contextMenu") - .getByRole("menuitem", { name: "Remove this device" }) + .getByRole("menu", { name: "User menu" }) + .getByRole("menuitem", { name: "All settings" }) .click(); + await page.getByRole("button", { name: "Remove this device" }).click(); await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again diff --git a/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts index b3ec952b01..dbb83c6a18 100644 --- a/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/account-user-settings-tab.spec.ts @@ -6,11 +6,14 @@ 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 Route } from "@playwright/test"; + import { test, expect } from "../../element-web-test"; import { getSampleFilePath } from "../../sample-files"; const USER_NAME = "Bob"; const USER_NAME_NEW = "Alice"; +const EXTERNAL_ACCOUNT_MANAGEMENT_URL = "https://just.for.test.io/"; test.describe("Account user settings tab", () => { test.use({ @@ -79,6 +82,46 @@ test.describe("Account user settings tab", () => { await expect(uut).toMatchScreenshot("account-smallscreen.png"); }); + test.describe("with external account management", () => { + test.use({ + page: async ({ page }, runFixture) => { + const authMetadataHandler = async (route: Route): Promise => { + await route.fulfill({ + json: { + issuer: EXTERNAL_ACCOUNT_MANAGEMENT_URL, + authorization_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}authorize`, + token_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}token`, + revocation_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}revoke`, + response_types_supported: ["code"], + grant_types_supported: ["authorization_code"], + code_challenge_methods_supported: ["S256"], + account_management_uri: EXTERNAL_ACCOUNT_MANAGEMENT_URL, + }, + }); + }; + + await page.route("**/_matrix/client/v1/auth_metadata", authMetadataHandler); + await page.route("**/_matrix/client/unstable/org.matrix.msc2965/auth_metadata", authMetadataHandler); + await runFixture(page); + }, + }); + + test("should render the manage account button properly", { tag: "@screenshot" }, async ({ uut, axe }) => { + const manageAccountButton = uut.getByTestId("external-account-management-link"); + + await expect(manageAccountButton).toBeVisible(); + await expect(manageAccountButton).toHaveAttribute("href", EXTERNAL_ACCOUNT_MANAGEMENT_URL); + await expect(manageAccountButton).toHaveAttribute("target", "_blank"); + await expect(manageAccountButton).toHaveText(/Manage account/); + + const profileButtons = uut.locator(".mx_UserProfileSettings_profile_buttons"); + await profileButtons.scrollIntoViewIfNeeded(); + await expect(profileButtons).toMatchScreenshot("account-manage-account-button.png"); + + await expect(axe).toHaveNoViolations(); + }); + }); + test("should show tooltips on narrow screen", async ({ page, uut }) => { await page.setViewportSize({ width: 700, height: 600 }); await page.getByRole("tab", { name: "Account" }).hover(); @@ -129,7 +172,7 @@ test.describe("Account user settings tab", () => { await expect(accountPhoneNumbers.getByRole("button", { name: "Add" })).toBeVisible(); }); - test("should support changing a display name", async ({ uut, page, app }) => { + test("should support changing a display name", async ({ uut, page, app, user }) => { // Change the diaplay name to USER_NAME_NEW const displayNameInput = uut .locator(".mx_SettingsTab .mx_UserProfileSettings") @@ -140,7 +183,8 @@ test.describe("Account user settings tab", () => { await app.closeDialog(); // Assert the avatar's initial characters are set - await expect(page.locator(".mx_UserMenu .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice + const menu = await app.openUserMenu(); + await expect(menu.getByRole("img", { name: user.userId }).getByText("A")).toBeVisible(); // Alice await expect(page.locator(".mx_RoomView_wrapper .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice }); diff --git a/apps/web/playwright/e2e/spaces/spaces.spec.ts b/apps/web/playwright/e2e/spaces/spaces.spec.ts index 173130fd29..48bb513800 100644 --- a/apps/web/playwright/e2e/spaces/spaces.spec.ts +++ b/apps/web/playwright/e2e/spaces/spaces.spec.ts @@ -318,6 +318,9 @@ test.describe("Spaces", () => { await spaceTree.getByRole("button", { name: "Expand" }).click(); await expect(page.locator(".mx_SpacePanel:not(.collapsed)")).toBeVisible(); // TODO: replace :not() selector + // focus the quick settings button to ensure the spaces aren't being hovered over for consistent screenshots + await page.getByRole("button", { name: "Quick settings" }).focus(); + const item = page.locator(".mx_SpaceItem", { hasText: "Root Space" }); await expect(item).toBeVisible(); await expect(item.locator(".mx_SpaceItem", { hasText: "Child Space" })).toBeVisible(); diff --git a/apps/web/playwright/e2e/user-menu/user-menu.spec.ts b/apps/web/playwright/e2e/user-menu/user-menu.spec.ts index 1f67aa0be6..cfc4757c15 100644 --- a/apps/web/playwright/e2e/user-menu/user-menu.spec.ts +++ b/apps/web/playwright/e2e/user-menu/user-menu.spec.ts @@ -6,8 +6,18 @@ 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 { Page } from "playwright-core"; import { test, expect } from "../../element-web-test"; +const screenshotOptions = (page?: Page) => ({ + // Hide the UserID + css: ` + span[data-testid="userId"] { + display: none !important; + } + `, +}); + test.describe("User Menu", () => { test.use({ displayName: "Jeff" }); @@ -15,8 +25,8 @@ test.describe("User Menu", () => { await page.getByRole("button", { name: "User menu", exact: true }).click(); const menu = page.getByRole("menu"); - await expect(menu.locator(".mx_UserMenu_contextMenu_displayName", { hasText: user.displayName })).toBeVisible(); - await expect(menu.locator(".mx_UserMenu_contextMenu_userId", { hasText: user.userId })).toBeVisible(); - await expect(menu).toMatchScreenshot("user-menu.png"); + await expect(menu.getByText(user.displayName)).toBeVisible(); + await expect(menu.getByText(user.userId)).toBeVisible(); + await expect(menu).toMatchScreenshot("user-menu.png", screenshotOptions(page)); }); }); diff --git a/apps/web/playwright/pages/settings.ts b/apps/web/playwright/pages/settings.ts index 0ea8c50348..c871bbeeaf 100644 --- a/apps/web/playwright/pages/settings.ts +++ b/apps/web/playwright/pages/settings.ts @@ -17,8 +17,8 @@ export class Settings { * Open the top left user menu, returning a Locator to the resulting context menu. */ public async openUserMenu(): Promise { - const locator = this.page.locator(".mx_ContextualMenu"); - if (await locator.locator(".mx_UserMenu_contextMenu_header").isVisible()) return locator; + const locator = this.page.getByRole("menu", { name: "User menu" }); + if (await locator.isVisible()) return locator; await this.page.getByRole("button", { name: "User menu" }).click(); await locator.waitFor(); return locator; diff --git a/apps/web/playwright/snapshots/app-loading/guest-registration.spec.ts/guest-menu-linux.png b/apps/web/playwright/snapshots/app-loading/guest-registration.spec.ts/guest-menu-linux.png new file mode 100644 index 0000000000..441dfefba3 Binary files /dev/null and b/apps/web/playwright/snapshots/app-loading/guest-registration.spec.ts/guest-menu-linux.png differ diff --git a/apps/web/playwright/snapshots/app-loading/stored-credentials.spec.ts/home-thin-viewport-linux.png b/apps/web/playwright/snapshots/app-loading/stored-credentials.spec.ts/home-thin-viewport-linux.png new file mode 100644 index 0000000000..737a6dbb5b Binary files /dev/null and b/apps/web/playwright/snapshots/app-loading/stored-credentials.spec.ts/home-thin-viewport-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png index 531802ed1c..0c96750a50 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-default-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png index 3c63cdd84b..288db67450 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list-collapse.spec.ts/room-list-collapse-fully-collapsed-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index 77345fe6fe..89cf0d7f64 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index 489c080f55..d0025d1fed 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 9e5528b3e5..74b18c088b 100644 Binary files a/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/apps/web/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-manage-account-button-linux.png b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-manage-account-button-linux.png new file mode 100644 index 0000000000..40f79c05b3 Binary files /dev/null and b/apps/web/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-manage-account-button-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png index e5294b4392..47048b97b1 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png index 0a3580977e..f5c34f1c8a 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png index c90c4fc54f..5d36c31906 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png differ diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png index dc175017bb..19e7689a64 100644 Binary files a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png and b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-collapsed-linux.png b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-collapsed-linux.png index 4ca008798f..7b3063a2df 100644 Binary files a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-collapsed-linux.png and b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-collapsed-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-expanded-linux.png b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-expanded-linux.png index b6c4e65be2..f187b3cab3 100644 Binary files a/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-expanded-linux.png and b/apps/web/playwright/snapshots/spaces/spaces.spec.ts/space-panel-expanded-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-button-expanded-linux.png b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-button-expanded-linux.png index 005dae5da0..f3e182ae4d 100644 Binary files a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-button-expanded-linux.png and b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-button-expanded-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png index 005dae5da0..596a9c30b1 100644 Binary files a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png and b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png differ diff --git a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png index 40f1243619..4952185cd1 100644 Binary files a/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png and b/apps/web/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png differ diff --git a/apps/web/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png b/apps/web/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png index d32108d254..64fc5585ec 100644 Binary files a/apps/web/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png and b/apps/web/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png index 2cf31a3569..1faf65f023 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png index c52a3824f1..fda6c35a6a 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png index 66c62eb96e..96b096905d 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-checked-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-checked-linux.png index 4a0c736fd4..711d748767 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-checked-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-checked-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-unchecked-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-unchecked-linux.png index 99cf03d9d3..431b137108 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-unchecked-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-video-toast-unchecked-linux.png differ diff --git a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png index 63b6b79ebd..974b14344a 100644 Binary files a/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png and b/apps/web/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png differ diff --git a/apps/web/playwright/testcontainers/mas.ts b/apps/web/playwright/testcontainers/mas.ts index 311922acf2..ef9449bc51 100644 --- a/apps/web/playwright/testcontainers/mas.ts +++ b/apps/web/playwright/testcontainers/mas.ts @@ -11,7 +11,7 @@ import { } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/matrix-authentication-service:main@sha256:c765fb602f78e77eccaa8e020e56c39eef99eccbabc9cb0df2c5705f60ca899e"; + "ghcr.io/element-hq/matrix-authentication-service:main@sha256:4b32f35c0c3367d0884abf039f3741fd55ff2dca683c04ee92bee351fd9a9403"; /** * MatrixAuthenticationServiceContainer which freezes the docker digest to diff --git a/apps/web/playwright/testcontainers/synapse.ts b/apps/web/playwright/testcontainers/synapse.ts index fbd78fc22c..6cc4bdbe0f 100644 --- a/apps/web/playwright/testcontainers/synapse.ts +++ b/apps/web/playwright/testcontainers/synapse.ts @@ -8,7 +8,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/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/synapse:develop@sha256:53b1c81dc161be1d999344ca727a520972b912fc24681dfafe25fca4b766b2a5"; + "ghcr.io/element-hq/synapse:develop@sha256:db0edf9064ca0e6da942eff82e328ac5aecbe64dff301ffb9f4fa6e03bd28e4d"; /** * SynapseContainer which freezes the docker digest to stabilise tests, diff --git a/apps/web/res/css/_components.pcss b/apps/web/res/css/_components.pcss index a175838d57..0c902fee3c 100644 --- a/apps/web/res/css/_components.pcss +++ b/apps/web/res/css/_components.pcss @@ -84,7 +84,6 @@ @import "./structures/_ThreadsActivityCentre.pcss"; @import "./structures/_ToastContainer.pcss"; @import "./structures/_UploadBar.pcss"; -@import "./structures/_UserMenu.pcss"; @import "./structures/_ViewSource.pcss"; @import "./structures/auth/_CompleteSecurity.pcss"; @import "./structures/auth/_ConfirmSessionLockTheftView.pcss"; @@ -223,8 +222,6 @@ @import "./views/messages/_CallEvent.pcss"; @import "./views/messages/_CreateEvent.pcss"; @import "./views/messages/_DisambiguatedProfile.pcss"; -@import "./views/messages/_HiddenBody.pcss"; -@import "./views/messages/_HiddenMediaPlaceholder.pcss"; @import "./views/messages/_LegacyCallEvent.pcss"; @import "./views/messages/_MFileBody.pcss"; @import "./views/messages/_MImageBody.pcss"; @@ -235,12 +232,10 @@ @import "./views/messages/_MStickerBody.pcss"; @import "./views/messages/_MediaBody.pcss"; @import "./views/messages/_MessageActionBar.pcss"; -@import "./views/messages/_MjolnirBody.pcss"; @import "./views/messages/_ReactionsRow.pcss"; @import "./views/messages/_RoomAvatarEvent.pcss"; @import "./views/messages/_TextualEvent.pcss"; @import "./views/messages/_ThreadActionBar.pcss"; -@import "./views/messages/_UnknownBody.pcss"; @import "./views/messages/_ViewSourceEvent.pcss"; @import "./views/messages/_common_CryptoEvent.pcss"; @import "./views/polls/pollHistory/_PollHistory.pcss"; diff --git a/apps/web/res/css/structures/_HomePage.pcss b/apps/web/res/css/structures/_HomePage.pcss index df374df4b5..c5b8774993 100644 --- a/apps/web/res/css/structures/_HomePage.pcss +++ b/apps/web/res/css/structures/_HomePage.pcss @@ -8,30 +8,27 @@ Please see LICENSE files in the repository root for full details. */ .mx_HomePage { - max-width: 960px; - width: 100%; + display: grid; + place-items: center; + width: min(100% - 50px, 960px); height: 100%; - margin-left: auto; - margin-right: auto; + margin: 0 auto; + container-type: inline-size; + container-name: homepage; } .mx_HomePage_default { text-align: center; - display: flex; - - .mx_HomePage_default_wrapper { - margin: auto; - } img { height: 48px; } h1 { + margin-bottom: 4px; font-weight: var(--cpd-font-weight-semibold); font-size: $font-32px; line-height: 1.375; - margin-bottom: 4px; } h2 { @@ -47,23 +44,24 @@ Please see LICENSE files in the repository root for full details. } .mx_HomePage_default_buttons { - display: flex; - margin: 60px auto 0; - width: fit-content; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 5px 40px; + width: 100%; + margin-top: 80px; + margin-bottom: 20px; .mx_AccessibleButton { - padding: 73px 8px 15px; /* top: 20px top padding + 40px icon + 13px margin */ - + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 13px; + box-sizing: border-box; width: 160px; min-height: 132px; - margin: 20px; - position: relative; - display: inline-block; + padding: 8px 15px; border-radius: 8px; - vertical-align: top; - word-break: break-word; - box-sizing: border-box; - font-weight: var(--cpd-font-weight-semibold); font-size: $font-15px; line-height: $font-20px; @@ -71,13 +69,26 @@ Please see LICENSE files in the repository root for full details. background-color: $accent; svg { - top: 20px; - left: 60px; /* (160px-40px)/2 */ - width: 40px; + display: block; + width: 100%; height: 40px; - position: absolute; + object-fit: contain; color: #fff; /* on all themes */ } } + + @container homepage (max-width: 559px) { + grid-template-columns: 1fr; + + .mx_AccessibleButton { + flex-direction: row; + width: 100%; + min-height: 0; + + svg { + width: 24px; + } + } + } } } diff --git a/apps/web/res/css/structures/_SpacePanel.pcss b/apps/web/res/css/structures/_SpacePanel.pcss index 5b6acf7596..a3c8b82701 100644 --- a/apps/web/res/css/structures/_SpacePanel.pcss +++ b/apps/web/res/css/structures/_SpacePanel.pcss @@ -339,26 +339,6 @@ Please see LICENSE files in the repository root for full details. mask-repeat: no-repeat; } } - - .mx_UserMenu { - padding-bottom: 12px; - border-bottom: 1px solid $separator; - margin: 12px 14px 4px 18px; - width: min-content; - max-width: 226px; - - /* Display the container and img here as block elements so they don't take - * up extra vertical space. - */ - .mx_UserMenu_userAvatar_BaseAvatar { - display: block; - } - } - - &.newUi .mx_UserMenu { - margin-top: var(--cpd-space-4x); - border-bottom: none; - } } .mx_SpacePanel_contextMenu { diff --git a/apps/web/res/css/structures/_ThreadsActivityCentre.pcss b/apps/web/res/css/structures/_ThreadsActivityCentre.pcss index a6e4627dd0..ecd5207df7 100644 --- a/apps/web/res/css/structures/_ThreadsActivityCentre.pcss +++ b/apps/web/res/css/structures/_ThreadsActivityCentre.pcss @@ -86,6 +86,12 @@ /* Arbitrary size, keep the TAC as the wanted width */ width: 202px; } + + /* Hide the notification badge on hover — compound's `nav-hint ~ *` rule would normally + * do this, but the app-web CSS layer overrides compound-web regardless of specificity. */ + &:hover .mx_NotificationBadge { + display: none; + } } } diff --git a/apps/web/res/css/structures/_UserMenu.pcss b/apps/web/res/css/structures/_UserMenu.pcss deleted file mode 100644 index b6c5b73e93..0000000000 --- a/apps/web/res/css/structures/_UserMenu.pcss +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 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_UserMenu { - box-sizing: border-box; - display: flex; - align-items: center; - - .mx_AccessibleButton { - display: flex; - align-items: center; - - .mx_UserMenu_userAvatar { - position: relative; - - .mx_BaseAvatar { - pointer-events: none; /* makes the avatar non-draggable */ - } - } - } - - .mx_UserMenu_contextMenuButton { - width: 100%; - } - - .mx_UserMenu_name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: var(--cpd-font-weight-semibold); - font-size: $font-15px; - line-height: $font-24px; - margin-left: 10px; - } -} - -.mx_IconizedContextMenu { - &.mx_UserMenu_contextMenu { - width: 258px; - } -} - -.mx_UserMenu_contextMenu { - &.mx_IconizedContextMenu .mx_IconizedContextMenu_optionList_red { - .mx_AccessibleButton { - padding-top: 16px; - padding-bottom: 16px; - } - } - - .mx_UserMenu_contextMenu_header { - padding: 20px; - - /* Create a flexbox to organize the header a bit easier */ - display: flex; - align-items: center; - - .mx_UserMenu_contextMenu_name { - /* Create another flexbox of columns to handle large user IDs */ - display: flex; - flex-direction: column; - width: calc(100% - 40px); /* 40px = 32px theme button + 8px margin to theme button */ - - .mx_UserMenu_contextMenu_displayName, - .mx_UserMenu_contextMenu_userId { - font: var(--cpd-font-body-lg-regular); - - /* Automatically grow subelements to fit the container */ - flex: 1; - width: 100%; - - /* Ellipsize text overflow */ - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .mx_UserMenu_contextMenu_displayName { - font-weight: var(--cpd-font-weight-semibold); - } - } - - .mx_UserMenu_contextMenu_themeButton { - flex-shrink: 0; - margin-left: 8px; - - /* to make alignment easier, create flexbox for the image */ - display: flex; - align-items: center; - justify-content: center; - - /* For enhanced visibility under contrast control */ - outline: 1px solid transparent; - - /* Compound overrides to match transitional designs */ - padding: var(--cpd-space-2x); - svg { - width: 16px; - height: 16px; - } - } - - &.mx_UserMenu_contextMenu_guestPrompts { - padding-top: 0; - display: inline-block; - - > span { - font-weight: var(--cpd-font-weight-semibold); - display: block; - - & + span { - margin-top: 8px; - } - } - } - } - - .mx_IconizedContextMenu_icon svg { - color: $icon-button-color; - } -} diff --git a/apps/web/res/css/views/messages/_HiddenBody.pcss b/apps/web/res/css/views/messages/_HiddenBody.pcss deleted file mode 100644 index 3644db16e6..0000000000 --- a/apps/web/res/css/views/messages/_HiddenBody.pcss +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_HiddenBody { - white-space: pre-wrap; - color: $muted-fg-color; - vertical-align: middle; - - svg { - height: 14px; - width: 14px; - display: inline-block; - margin-right: var(--cpd-space-1-5x); - color: $muted-fg-color; - vertical-align: -2px; - } -} diff --git a/apps/web/res/css/views/messages/_HiddenMediaPlaceholder.pcss b/apps/web/res/css/views/messages/_HiddenMediaPlaceholder.pcss deleted file mode 100644 index c7efe6ec7e..0000000000 --- a/apps/web/res/css/views/messages/_HiddenMediaPlaceholder.pcss +++ /dev/null @@ -1,29 +0,0 @@ -.mx_HiddenMediaPlaceholder { - border: none; - width: 100%; - height: 100%; - inset: 0; - - /* To center the text in the middle of the frame */ - display: flex; - align-items: center; - justify-content: center; - text-align: center; - - cursor: pointer; - background-color: $header-panel-bg-color; - - > div { - color: $accent; - /* Icon alignment */ - display: flex; - > svg { - margin-top: auto; - margin-bottom: auto; - } - } -} - -.mx_EventTile:hover .mx_HiddenMediaPlaceholder { - background-color: $background; -} diff --git a/apps/web/res/css/views/messages/_MjolnirBody.pcss b/apps/web/res/css/views/messages/_MjolnirBody.pcss deleted file mode 100644 index 825eb36af6..0000000000 --- a/apps/web/res/css/views/messages/_MjolnirBody.pcss +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019 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_MjolnirBody { - opacity: 0.4; -} diff --git a/apps/web/res/css/views/messages/_TextualEvent.pcss b/apps/web/res/css/views/messages/_TextualEvent.pcss index 3dbde24791..a1db28027f 100644 --- a/apps/web/res/css/views/messages/_TextualEvent.pcss +++ b/apps/web/res/css/views/messages/_TextualEvent.pcss @@ -7,12 +7,8 @@ Please see LICENSE files in the repository root for full details. */ .mx_TextualEvent { - overflow-y: hidden; - line-height: normal; - a { color: $accent; - cursor: pointer; } .mx_RoomView_searchResultsPanel & { diff --git a/apps/web/res/css/views/messages/_UnknownBody.pcss b/apps/web/res/css/views/messages/_UnknownBody.pcss deleted file mode 100644 index 9583f55734..0000000000 --- a/apps/web/res/css/views/messages/_UnknownBody.pcss +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2015, 2016 OpenMarket 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_UnknownBody { - white-space: pre-wrap; -} diff --git a/apps/web/res/manifest.json b/apps/web/res/manifest.json index 75eeab7b01..ff9b8edf30 100644 --- a/apps/web/res/manifest.json +++ b/apps/web/res/manifest.json @@ -21,7 +21,7 @@ "type": "image/png" }, { - "src": "/vector-icons/152png", + "src": "/vector-icons/152.png", "sizes": "152x152", "type": "image/png" }, diff --git a/apps/web/res/themes/light-high-contrast/css/_light-high-contrast.pcss b/apps/web/res/themes/light-high-contrast/css/_light-high-contrast.pcss index 483917fb0c..ce760ddcf0 100644 --- a/apps/web/res/themes/light-high-contrast/css/_light-high-contrast.pcss +++ b/apps/web/res/themes/light-high-contrast/css/_light-high-contrast.pcss @@ -82,10 +82,6 @@ $accent-1400: var(--cpd-color-green-1400); } } -.mx_UserMenu_contextMenu .mx_UserMenu_contextMenu_header .mx_UserMenu_contextMenu_themeButton { - background-color: $panel-actions !important; -} - .mx_ThemeChoicePanel_themeSelectors > .mx_StyledRadioButton input[type="radio"]:disabled + div { border-color: $primary-content; } diff --git a/apps/web/src/components/structures/FilePanel.tsx b/apps/web/src/components/structures/FilePanel.tsx index 7f80525e29..0cecad0a3e 100644 --- a/apps/web/src/components/structures/FilePanel.tsx +++ b/apps/web/src/components/structures/FilePanel.tsx @@ -34,6 +34,7 @@ import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import Measured from "../views/elements/Measured"; import EmptyState from "../views/right_panel/EmptyState"; import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx"; +import { EventPresentationContextProvider } from "../../utils/EventPresentationContextProvider"; interface IProps { roomId: string; @@ -286,15 +287,17 @@ class FilePanel extends React.Component { > - + + + ); diff --git a/apps/web/src/components/structures/RoomView.tsx b/apps/web/src/components/structures/RoomView.tsx index 49a61327f6..9483b05a9c 100644 --- a/apps/web/src/components/structures/RoomView.tsx +++ b/apps/web/src/components/structures/RoomView.tsx @@ -143,6 +143,7 @@ import { RoomStatusBarViewModel } from "../../viewmodels/room/RoomStatusBar.ts"; import { EncryptionEventViewModel } from "../../viewmodels/room/timeline/event-tile/EncryptionEventViewModel.ts"; import { ModuleApi } from "../../modules/Api.ts"; import { RoomUploadContextProvider } from "../../viewmodels/room/RoomUploadViewModel.tsx"; +import { EventPresentationContextProvider } from "../../utils/EventPresentationContextProvider"; const DEBUG = false; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; @@ -2571,32 +2572,34 @@ export class RoomView extends React.Component { let messagePanel: JSX.Element | undefined; if (!isRoomEncryptionLoading) { messagePanel = ( -