From 3b30c56803c3c5594924197fa10ca97b53a67ead Mon Sep 17 00:00:00 2001 From: lehnerja Date: Mon, 27 Apr 2026 15:39:23 +0200 Subject: [PATCH 01/49] test(registration): add test for terms of use before registration This commit implements TC-1621 to ensure users are properly informed about the terms of use when creating a personal account via the SSO login flow. The implemented scenario verifies: - Entering an unregistered email on the SSO sign-in page. - Navigating to the personal account registration page. - The visibility of the Terms of Use link. - Opening the link and validating the correct routing to the `/legal#terms` URL in a new tab. Refs: WPB-24831 --- .../specs/Registration/registration.spec.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts index 4d6d5a25264..96d2253a341 100644 --- a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts @@ -32,6 +32,30 @@ test.describe('Registration', () => { addCreatedUser(userA); }); + test( + 'I want to be informed about terms of personal account before registration', + {tag: ['@TC-1621', '@regression']}, + async ({pageManager}) => { + const newUser = getUser(); + const unregisteredEmail = newUser.email; + + await pageManager.openMainPage(); + const {pages} = pageManager.webapp; + + await goToPersonalRegistration(pageManager, unregisteredEmail); + + await expect(pages.registration().termsLabel).toBeVisible(); + + const pagePromise = pageManager.page.context().waitForEvent('page'); + + await pages.registration().termsLabel.click(); + + const newTab = await pagePromise; + await newTab.waitForLoadState(); + await expect(newTab).toHaveURL(/legal#terms/); + }, + ); + test( 'I want to be notified if the email address I entered during registration has already been registered', {tag: ['@TC-1623', '@TC-1640', '@regression']}, From e482bdcefcde1d0fb645a5858a78e5b852ba84f1 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Mon, 27 Apr 2026 15:53:40 +0200 Subject: [PATCH 02/49] test(registration): refactor popup handling for terms of use test The test now uses `Promise.all` with `page.waitForEvent('popup')` instead of `context().waitForEvent('page')` to handle the new tab creation. Refs: WPB-24831 --- .../e2e_tests/specs/Registration/registration.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts index 96d2253a341..e03d0b6ca00 100644 --- a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts @@ -46,12 +46,11 @@ test.describe('Registration', () => { await expect(pages.registration().termsLabel).toBeVisible(); - const pagePromise = pageManager.page.context().waitForEvent('page'); + const [newTab] = await Promise.all([ + pageManager.page.waitForEvent('popup'), + pages.registration().termsLabel.click(), + ]); - await pages.registration().termsLabel.click(); - - const newTab = await pagePromise; - await newTab.waitForLoadState(); await expect(newTab).toHaveURL(/legal#terms/); }, ); From af7d2e8074e62596053a15db035420a2e0bb550e Mon Sep 17 00:00:00 2001 From: Otto the Bot Date: Mon, 27 Apr 2026 17:58:55 +0200 Subject: [PATCH 03/49] chore: merge master into dev after pull request #21159 (#21160) * chore: Update translations * chore: use github hoster runners for jobs (#20910) * chore: trigger push * chore(E2E): Updated calling service API client to use 1Password [WPB-24489] (#20905) * chore: Update translations * chore: Update translations --------- Co-authored-by: Amir Ghezelbash Co-authored-by: Immad Abdul Jabbar Co-authored-by: Ivan Skvortsov Co-authored-by: Christian Rackerseder From f9c9f5da3d7e59992adb73c5f630f67541e29011 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:06:37 +0000 Subject: [PATCH 04/49] chore(deps): update dependency @formatjs/cli to v6.14.3 --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 70c45b2cb53..d7f476df560 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -105,7 +105,7 @@ "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", "@emotion/eslint-plugin": "11.12.0", - "@formatjs/cli": "6.14.2", + "@formatjs/cli": "6.14.3", "@nx/webpack": "22.6.5", "@playwright/test": "1.59.1", "@roamhq/wrtc": "0.10.0", diff --git a/yarn.lock b/yarn.lock index eb895016fdb..af78ffd1974 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3636,9 +3636,9 @@ __metadata: languageName: node linkType: hard -"@formatjs/cli@npm:6.14.2": - version: 6.14.2 - resolution: "@formatjs/cli@npm:6.14.2" +"@formatjs/cli@npm:6.14.3": + version: 6.14.3 + resolution: "@formatjs/cli@npm:6.14.3" peerDependencies: "@glimmer/syntax": ^0.84.3 || ^0.95.0 "@vue/compiler-core": ^3.5.0 @@ -3661,7 +3661,7 @@ __metadata: optional: true bin: formatjs: bin/formatjs - checksum: 10/d4735f90c7d51fe732bdee329eaaa6079daddbb135658bbadad8312be2def1e1a9ccd8c258abaf3788351d7ebd43af960e7a6056cc6bfaf585c79da1bfdf60f5 + checksum: 10/e5e150dedcc09f1cfc62600f871f5c1bc9aba9ee2208cf895a34cd157c5986eebc0dc9fd96523ee5cd0a45eb5ec050349a3cdc3a0653d3578c9dadc84fff03b6 languageName: node linkType: hard @@ -9677,7 +9677,7 @@ __metadata: "@datadog/browser-rum": "npm:5.35.1" "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" - "@formatjs/cli": "npm:6.14.2" + "@formatjs/cli": "npm:6.14.3" "@formkit/auto-animate": "npm:0.9.0" "@lexical/code": "npm:0.27.2" "@lexical/history": "npm:0.27.2" From 7401bf757e4f5c924a62344057d42ab1884c7804 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:34:36 +0200 Subject: [PATCH 05/49] chore(deps): update dependency @nx/jest to v22.7.0 (#21163) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 769 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 601 insertions(+), 170 deletions(-) diff --git a/package.json b/package.json index 95ecd0b9193..85197897f29 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@faker-js/faker": "9.9.0", "@ls-lint/ls-lint": "2.3.1", - "@nx/jest": "22.6.5", + "@nx/jest": "22.7.0", "@nx/node": "22.6.5", "@nx/workspace": "22.6.5", "@stylistic/eslint-plugin": "5.10.0", diff --git a/yarn.lock b/yarn.lock index eb895016fdb..6fabf9c1f7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3236,6 +3236,16 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:1.4.5": + version: 1.4.5 + resolution: "@emnapi/core@npm:1.4.5" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.4" + tslib: "npm:^2.4.0" + checksum: 10/412322102dc861e8aa78123ae20560ac980362a220c736fe59ddea3228d490757780ea4cdc3bd54903a5ca2a92085f119e42f2c07f60e2aec2c0b8a69ea094c0 + languageName: node + linkType: hard + "@emnapi/core@npm:^1.1.0, @emnapi/core@npm:^1.4.3": version: 1.7.1 resolution: "@emnapi/core@npm:1.7.1" @@ -3246,6 +3256,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:1.4.5": + version: 1.4.5 + resolution: "@emnapi/runtime@npm:1.4.5" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/1d6f406ff116d2363e60aef3ed49eb8d577387f4941abea508ba376900d8831609d5cce92a58076b1a9613f8e83c75c2e3fea71e4fbcdbe06019876144c2559b + languageName: node + linkType: hard + "@emnapi/runtime@npm:^1.1.0, @emnapi/runtime@npm:^1.4.3": version: 1.7.1 resolution: "@emnapi/runtime@npm:1.7.1" @@ -3255,6 +3274,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/wasi-threads@npm:1.0.4": + version: 1.0.4 + resolution: "@emnapi/wasi-threads@npm:1.0.4" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/86688f416095b59d8d3e5ea2d8b5574a7c180257fe0c067c7a492f3de2cf5ebc2c8b00af17d6341c7555c614266d3987f332015d7ce6e88b234a9a314e66f396 + languageName: node + linkType: hard + "@emnapi/wasi-threads@npm:1.1.0": version: 1.1.0 resolution: "@emnapi/wasi-threads@npm:1.1.0" @@ -5314,6 +5342,23 @@ __metadata: languageName: node linkType: hard +"@nx/devkit@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/devkit@npm:22.7.0" + dependencies: + "@zkochan/js-yaml": "npm:0.0.7" + ejs: "npm:5.0.1" + enquirer: "npm:~2.3.6" + minimatch: "npm:10.2.4" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + peerDependencies: + nx: ">= 21 <= 23 || ^22.0.0-0" + checksum: 10/7dbcfe535d71082e00582d581bc94c6e262fc8e9754874c40dfa3bd7ff758d6873037d868a4501b315d2f93e515ff1e6ea1baecc82e134155a91ad66c73c9d6f + languageName: node + linkType: hard + "@nx/docker@npm:22.6.5": version: 22.6.5 resolution: "@nx/docker@npm:22.6.5" @@ -5367,6 +5412,29 @@ __metadata: languageName: node linkType: hard +"@nx/jest@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/jest@npm:22.7.0" + dependencies: + "@jest/reporters": "npm:^30.0.2" + "@jest/test-result": "npm:^30.0.2" + "@nx/devkit": "npm:22.7.0" + "@nx/js": "npm:22.7.0" + "@phenomnomnominal/tsquery": "npm:~6.1.4" + identity-obj-proxy: "npm:3.0.0" + jest-config: "npm:^30.0.2" + jest-resolve: "npm:^30.0.2" + jest-util: "npm:^30.0.2" + minimatch: "npm:10.2.4" + picocolors: "npm:^1.1.0" + resolve.exports: "npm:2.0.3" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + checksum: 10/16e8b4cfbeba8372c6b8271c7613a87c9f12a4bead2e9a8b867d5b044c3be7e2925d9a24ccbf2088e03a3e2f851065da9c0690e77e5b6f8cbd8517364e17d33b + languageName: node + linkType: hard + "@nx/js@npm:22.6.5": version: 22.6.5 resolution: "@nx/js@npm:22.6.5" @@ -5406,6 +5474,45 @@ __metadata: languageName: node linkType: hard +"@nx/js@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/js@npm:22.7.0" + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/plugin-proposal-decorators": "npm:^7.22.7" + "@babel/plugin-transform-class-properties": "npm:^7.22.5" + "@babel/plugin-transform-runtime": "npm:^7.23.2" + "@babel/preset-env": "npm:^7.23.2" + "@babel/preset-typescript": "npm:^7.22.5" + "@babel/runtime": "npm:^7.22.6" + "@nx/devkit": "npm:22.7.0" + "@nx/workspace": "npm:22.7.0" + "@zkochan/js-yaml": "npm:0.0.7" + babel-plugin-const-enum: "npm:^1.0.1" + babel-plugin-macros: "npm:^3.1.0" + babel-plugin-transform-typescript-metadata: "npm:^0.3.1" + chalk: "npm:^4.1.0" + columnify: "npm:^1.6.0" + detect-port: "npm:^1.5.1" + ignore: "npm:^5.0.4" + js-tokens: "npm:^4.0.0" + jsonc-parser: "npm:3.2.0" + npm-run-path: "npm:^4.0.1" + picocolors: "npm:^1.1.0" + picomatch: "npm:4.0.4" + semver: "npm:^7.6.3" + source-map-support: "npm:0.5.19" + tinyglobby: "npm:^0.2.12" + tslib: "npm:^2.3.0" + peerDependencies: + verdaccio: ^6.0.5 + peerDependenciesMeta: + verdaccio: + optional: true + checksum: 10/251a671260a322e6bebccb1a45333cb237dc20c99197063e4da66695eb9a021dc1a5033806c25b84b3cfefb0d66b3666acd76179e9469bfbf37bc6397c09d071 + languageName: node + linkType: hard + "@nx/node@npm:22.6.5": version: 22.6.5 resolution: "@nx/node@npm:22.6.5" @@ -5429,6 +5536,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-arm64@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-darwin-arm64@npm:22.7.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-darwin-x64@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-darwin-x64@npm:22.6.5" @@ -5436,6 +5550,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-x64@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-darwin-x64@npm:22.7.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-freebsd-x64@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-freebsd-x64@npm:22.6.5" @@ -5443,6 +5564,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-freebsd-x64@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-freebsd-x64@npm:22.7.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-linux-arm-gnueabihf@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.6.5" @@ -5450,6 +5578,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm-gnueabihf@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@nx/nx-linux-arm64-gnu@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-linux-arm64-gnu@npm:22.6.5" @@ -5457,6 +5592,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-gnu@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-arm64-musl@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-linux-arm64-musl@npm:22.6.5" @@ -5464,6 +5606,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-musl@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-linux-arm64-musl@npm:22.7.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-linux-x64-gnu@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-linux-x64-gnu@npm:22.6.5" @@ -5471,6 +5620,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-gnu@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-linux-x64-gnu@npm:22.7.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-x64-musl@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-linux-x64-musl@npm:22.6.5" @@ -5478,6 +5634,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-musl@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-linux-x64-musl@npm:22.7.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-win32-arm64-msvc@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-win32-arm64-msvc@npm:22.6.5" @@ -5485,6 +5648,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-arm64-msvc@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-win32-x64-msvc@npm:22.6.5": version: 22.6.5 resolution: "@nx/nx-win32-x64-msvc@npm:22.6.5" @@ -5492,6 +5662,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-x64-msvc@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/nx-win32-x64-msvc@npm:22.7.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@nx/webpack@npm:22.6.5": version: 22.6.5 resolution: "@nx/webpack@npm:22.6.5" @@ -5553,6 +5730,23 @@ __metadata: languageName: node linkType: hard +"@nx/workspace@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/workspace@npm:22.7.0" + dependencies: + "@nx/devkit": "npm:22.7.0" + "@zkochan/js-yaml": "npm:0.0.7" + chalk: "npm:^4.1.0" + enquirer: "npm:~2.3.6" + nx: "npm:22.7.0" + picomatch: "npm:4.0.4" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + checksum: 10/5ca9e145a115242abbffe8a7abc34120125b394337865c2c9890f5b19f9037f26cb9ec89238ecab589c73e42250d1345188b61203f09b05ef6658e3a40cfe666 + languageName: node + linkType: hard + "@open-draft/deferred-promise@npm:^2.2.0": version: 2.2.0 resolution: "@open-draft/deferred-promise@npm:2.2.0" @@ -7633,21 +7827,21 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.0": - version: 0.10.1 - resolution: "@tybys/wasm-util@npm:0.10.1" +"@tybys/wasm-util@npm:0.9.0, @tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10/7fe0d239397aebb002ac4855d30c197c06a05ea8df8511350a3a5b1abeefe26167c60eda8a5508337571161e4c4b53d7c1342296123f9607af8705369de9fa7f + checksum: 10/aa58e64753a420ad1eefaf7bacef3dda61d74f9336925943d9244132d5b48d9242f734f1e707fd5ccfa6dd1d8ec8e6debc234b4dedb3a5b0d8486d1f373350b2 languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.9.0": - version: 0.9.0 - resolution: "@tybys/wasm-util@npm:0.9.0" +"@tybys/wasm-util@npm:^0.10.0": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10/aa58e64753a420ad1eefaf7bacef3dda61d74f9336925943d9244132d5b48d9242f734f1e707fd5ccfa6dd1d8ec8e6debc234b4dedb3a5b0d8486d1f373350b2 + checksum: 10/7fe0d239397aebb002ac4855d30c197c06a05ea8df8511350a3a5b1abeefe26167c60eda8a5508337571161e4c4b53d7c1342296123f9607af8705369de9fa7f languageName: node linkType: hard @@ -9834,7 +10028,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/lockfile@npm:^1.1.0": +"@yarnpkg/lockfile@npm:1.1.0, @yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" checksum: 10/cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed @@ -10107,7 +10301,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.1": +"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: 10/43d6e2fc7b1c6e4dc373de708ee76311ec2e0433e7e8bd3194e7ff123ea6a747428fc61afdcf5969da5be3a5f0fd054602bec56fc0ebe249ce2fcde6e649e3c2 @@ -10162,6 +10356,15 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:4.3.0, ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -10171,15 +10374,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff - languageName: node - linkType: hard - "ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" @@ -10279,6 +10473,13 @@ __metadata: languageName: node linkType: hard +"argparse@npm:2.0.1, argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef + languageName: node + linkType: hard + "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -10288,13 +10489,6 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef - languageName: node - linkType: hard - "aria-hidden@npm:^1.2.3": version: 1.2.6 resolution: "aria-hidden@npm:1.2.6" @@ -10522,7 +10716,7 @@ __metadata: languageName: node linkType: hard -"asynckit@npm:^0.4.0": +"asynckit@npm:0.4.0, asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" checksum: 10/3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 @@ -10900,6 +11094,13 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:4.0.3": + version: 4.0.3 + resolution: "balanced-match@npm:4.0.3" + checksum: 10/e9e2177f1eb7db0af2375715e6f992f15d41518921e14f5029de50766ff1b3da824e47e1d5492493e9bb7c743b827e42546cbc260a055b7086e45f54b2b152b9 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -10923,7 +11124,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1": +"base64-js@npm:1.5.1, base64-js@npm:^1.0.2, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -11016,7 +11217,7 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.0.3, bl@npm:^4.1.0": +"bl@npm:4.1.0, bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" dependencies: @@ -11096,6 +11297,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:5.0.2": + version: 5.0.2 + resolution: "brace-expansion@npm:5.0.2" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10/18d382c0919c68f8bb56fbe4a9cb1181a0bf10e6786b5683e586493dfbb517bdcf972f4a3a8d560486627fd9c9c6ecef0a2b8fd454eb6082780307ffd5586251 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -11281,23 +11491,23 @@ __metadata: languageName: node linkType: hard -"buffer@npm:6.0.3, buffer@npm:^6.0.3": - version: 6.0.3 - resolution: "buffer@npm:6.0.3" +"buffer@npm:5.7.1, buffer@npm:^5.2.0, buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" dependencies: base64-js: "npm:^1.3.1" - ieee754: "npm:^1.2.1" - checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 + ieee754: "npm:^1.1.13" + checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 languageName: node linkType: hard -"buffer@npm:^5.2.0, buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" +"buffer@npm:6.0.3, buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" dependencies: base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 + ieee754: "npm:^1.2.1" + checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 languageName: node linkType: hard @@ -11357,7 +11567,7 @@ __metadata: languageName: node linkType: hard -"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": +"call-bind-apply-helpers@npm:1.0.2, call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 resolution: "call-bind-apply-helpers@npm:1.0.2" dependencies: @@ -11626,7 +11836,7 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^8.0.1": +"cliui@npm:8.0.1, cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" dependencies: @@ -11662,7 +11872,7 @@ __metadata: languageName: node linkType: hard -"clone@npm:^1.0.0, clone@npm:^1.0.2": +"clone@npm:1.0.4, clone@npm:^1.0.0, clone@npm:^1.0.2": version: 1.0.4 resolution: "clone@npm:1.0.4" checksum: 10/d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd @@ -11697,6 +11907,15 @@ __metadata: languageName: node linkType: hard +"color-convert@npm:2.0.1, color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -11706,15 +11925,6 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 - languageName: node - linkType: hard - "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -11722,7 +11932,7 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": +"color-name@npm:1.1.4, color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 @@ -11789,7 +11999,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.8": +"combined-stream@npm:1.0.8, combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -12798,7 +13008,7 @@ __metadata: languageName: node linkType: hard -"defaults@npm:^1.0.3": +"defaults@npm:1.0.4, defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" dependencies: @@ -12818,7 +13028,7 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^2.0.0": +"define-lazy-prop@npm:2.0.0, define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" checksum: 10/0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 @@ -12852,7 +13062,7 @@ __metadata: languageName: node linkType: hard -"delayed-stream@npm:~1.0.0": +"delayed-stream@npm:1.0.0, delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" checksum: 10/46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 @@ -13191,6 +13401,15 @@ __metadata: languageName: node linkType: hard +"dotenv-expand@npm:12.0.3": + version: 12.0.3 + resolution: "dotenv-expand@npm:12.0.3" + dependencies: + dotenv: "npm:^16.4.5" + checksum: 10/d9aaf051805c8fa64e7e3620936ecb38229a93f5603883e53856d6f57cd4ae93fb74baefdf0b03b1c0608bc3eefbbcb23912972d7303b6191a5c0e1688a8652f + languageName: node + linkType: hard + "dotenv-expand@npm:~11.0.6": version: 11.0.7 resolution: "dotenv-expand@npm:11.0.7" @@ -13238,7 +13457,7 @@ __metadata: languageName: node linkType: hard -"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": +"dunder-proto@npm:1.0.1, dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" dependencies: @@ -13340,7 +13559,7 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^8.0.0": +"emoji-regex@npm:8.0.0, emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 @@ -13394,6 +13613,15 @@ __metadata: languageName: node linkType: hard +"end-of-stream@npm:1.4.5": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10/1e0cfa6e7f49887544e03314f9dfc56a8cb6dde910cbb445983ecc2ff426fc05946df9d75d8a21a3a64f2cecfe1bf88f773952029f46756b2ed64a24e95b1fb8 + languageName: node + linkType: hard + "end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" @@ -13460,7 +13688,7 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:~2.3.6": +"enquirer@npm:2.3.6, enquirer@npm:~2.3.6": version: 2.3.6 resolution: "enquirer@npm:2.3.6" dependencies: @@ -13616,14 +13844,14 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": +"es-define-property@npm:1.0.1, es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 languageName: node linkType: hard -"es-errors@npm:^1.3.0": +"es-errors@npm:1.3.0, es-errors@npm:^1.3.0": version: 1.3.0 resolution: "es-errors@npm:1.3.0" checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 @@ -13661,7 +13889,7 @@ __metadata: languageName: node linkType: hard -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": +"es-object-atoms@npm:1.1.1, es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": version: 1.1.1 resolution: "es-object-atoms@npm:1.1.1" dependencies: @@ -13670,7 +13898,7 @@ __metadata: languageName: node linkType: hard -"es-set-tostringtag@npm:^2.1.0": +"es-set-tostringtag@npm:2.1.0, es-set-tostringtag@npm:^2.1.0": version: 2.1.0 resolution: "es-set-tostringtag@npm:2.1.0" dependencies: @@ -13702,7 +13930,7 @@ __metadata: languageName: node linkType: hard -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": +"escalade@npm:3.2.0, escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 @@ -13716,7 +13944,7 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^1.0.5": +"escape-string-regexp@npm:1.0.5, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 @@ -15051,7 +15279,7 @@ __metadata: languageName: node linkType: hard -"flat@npm:^5.0.2": +"flat@npm:5.0.2, flat@npm:^5.0.2": version: 5.0.2 resolution: "flat@npm:5.0.2" bin: @@ -15074,7 +15302,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.11": +"follow-redirects@npm:1.15.11, follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.11": version: 1.15.11 resolution: "follow-redirects@npm:1.15.11" peerDependenciesMeta: @@ -15133,7 +15361,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.5": +"form-data@npm:4.0.5, form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" dependencies: @@ -15190,7 +15418,7 @@ __metadata: languageName: node linkType: hard -"fs-constants@npm:^1.0.0": +"fs-constants@npm:1.0.0, fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" checksum: 10/18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d @@ -15340,7 +15568,7 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.2": +"function-bind@npm:1.1.2, function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 @@ -15428,7 +15656,7 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.5": +"get-caller-file@npm:2.0.5, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 @@ -15442,6 +15670,24 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.1 resolution: "get-intrinsic@npm:1.3.1" @@ -15484,7 +15730,7 @@ __metadata: languageName: node linkType: hard -"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": +"get-proto@npm:1.0.1, get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": version: 1.0.1 resolution: "get-proto@npm:1.0.1" dependencies: @@ -15805,7 +16051,7 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1, gopd@npm:^1.2.0": +"gopd@npm:1.2.0, gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 @@ -15901,6 +16147,13 @@ __metadata: languageName: node linkType: hard +"has-flag@npm:4.0.0, has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad + languageName: node + linkType: hard + "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -15908,13 +16161,6 @@ __metadata: languageName: node linkType: hard -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad - languageName: node - linkType: hard - "has-flag@npm:^5.0.1": version: 5.0.1 resolution: "has-flag@npm:5.0.1" @@ -15949,14 +16195,14 @@ __metadata: languageName: node linkType: hard -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": +"has-symbols@npm:1.1.0, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": version: 1.1.0 resolution: "has-symbols@npm:1.1.0" checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa languageName: node linkType: hard -"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": +"has-tostringtag@npm:1.0.2, has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": version: 1.0.2 resolution: "has-tostringtag@npm:1.0.2" dependencies: @@ -15991,7 +16237,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.2": +"hasown@npm:2.0.2, hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -16423,13 +16669,20 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": +"ieee754@npm:1.2.1, ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 languageName: node linkType: hard +"ignore@npm:7.0.5, ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + "ignore@npm:^5.0.4, ignore@npm:^5.2.0, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -16437,13 +16690,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.5": - version: 7.0.5 - resolution: "ignore@npm:7.0.5" - checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 - languageName: node - linkType: hard - "image-q@npm:^4.0.0": version: 4.0.0 resolution: "image-q@npm:4.0.0" @@ -16555,7 +16801,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -16804,7 +17050,7 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": +"is-docker@npm:2.2.1, is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" bin: @@ -16852,7 +17098,7 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^3.0.0": +"is-fullwidth-code-point@npm:3.0.0, is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 @@ -16911,7 +17157,7 @@ __metadata: languageName: node linkType: hard -"is-interactive@npm:^1.0.0": +"is-interactive@npm:1.0.0, is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" checksum: 10/824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 @@ -17141,7 +17387,7 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^0.1.0": +"is-unicode-supported@npm:0.1.0, is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" checksum: 10/a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 @@ -17223,14 +17469,7 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^1.1.0": - version: 1.1.0 - resolution: "is-wsl@npm:1.1.0" - checksum: 10/ea157d232351e68c92bd62fc541771096942fe72f69dff452dd26dcc31466258c570a3b04b8cda2e01cd2968255b02951b8670d08ea4ed76d6b1a646061ac4fe - languageName: node - linkType: hard - -"is-wsl@npm:^2.2.0": +"is-wsl@npm:2.2.0, is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" dependencies: @@ -17239,6 +17478,13 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^1.1.0": + version: 1.1.0 + resolution: "is-wsl@npm:1.1.0" + checksum: 10/ea157d232351e68c92bd62fc541771096942fe72f69dff452dd26dcc31466258c570a3b04b8cda2e01cd2968255b02951b8670d08ea4ed76d6b1a646061ac4fe + languageName: node + linkType: hard + "is-wsl@npm:^3.1.0": version: 3.1.0 resolution: "is-wsl@npm:3.1.0" @@ -18538,6 +18784,15 @@ __metadata: languageName: node linkType: hard +"json5@npm:2.2.3, json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.2, json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10/1db67b853ff0de3534085d630691d3247de53a2ed1390ba0ddff681ea43e9b3e30ecbdb65c5e9aab49435e44059c23dbd6fee8ee619419ba37465bb0dd7135da + languageName: node + linkType: hard + "json5@npm:^1.0.1, json5@npm:^1.0.2": version: 1.0.2 resolution: "json5@npm:1.0.2" @@ -18549,15 +18804,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.2, json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10/1db67b853ff0de3534085d630691d3247de53a2ed1390ba0ddff681ea43e9b3e30ecbdb65c5e9aab49435e44059c23dbd6fee8ee619419ba37465bb0dd7135da - languageName: node - linkType: hard - "jsonc-parser@npm:3.2.0": version: 3.2.0 resolution: "jsonc-parser@npm:3.2.0" @@ -19146,7 +19392,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": +"log-symbols@npm:4.1.0, log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -19419,7 +19665,7 @@ __metadata: languageName: node linkType: hard -"math-intrinsics@npm:^1.1.0": +"math-intrinsics@npm:1.1.0, math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd @@ -19579,7 +19825,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -19606,7 +19852,7 @@ __metadata: languageName: node linkType: hard -"mimic-fn@npm:^2.1.0": +"mimic-fn@npm:2.1.0, mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" checksum: 10/d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a @@ -19724,7 +19970,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:1.2.8, minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f @@ -20163,7 +20409,7 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^4.0.1": +"npm-run-path@npm:4.0.1, npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" dependencies: @@ -20286,6 +20532,166 @@ __metadata: languageName: node linkType: hard +"nx@npm:22.7.0": + version: 22.7.0 + resolution: "nx@npm:22.7.0" + dependencies: + "@emnapi/core": "npm:1.4.5" + "@emnapi/runtime": "npm:1.4.5" + "@emnapi/wasi-threads": "npm:1.0.4" + "@jest/diff-sequences": "npm:30.0.1" + "@napi-rs/wasm-runtime": "npm:0.2.4" + "@nx/nx-darwin-arm64": "npm:22.7.0" + "@nx/nx-darwin-x64": "npm:22.7.0" + "@nx/nx-freebsd-x64": "npm:22.7.0" + "@nx/nx-linux-arm-gnueabihf": "npm:22.7.0" + "@nx/nx-linux-arm64-gnu": "npm:22.7.0" + "@nx/nx-linux-arm64-musl": "npm:22.7.0" + "@nx/nx-linux-x64-gnu": "npm:22.7.0" + "@nx/nx-linux-x64-musl": "npm:22.7.0" + "@nx/nx-win32-arm64-msvc": "npm:22.7.0" + "@nx/nx-win32-x64-msvc": "npm:22.7.0" + "@tybys/wasm-util": "npm:0.9.0" + "@yarnpkg/lockfile": "npm:1.1.0" + "@zkochan/js-yaml": "npm:0.0.7" + ansi-colors: "npm:4.1.3" + ansi-regex: "npm:5.0.1" + ansi-styles: "npm:4.3.0" + argparse: "npm:2.0.1" + asynckit: "npm:0.4.0" + axios: "npm:1.15.0" + balanced-match: "npm:4.0.3" + base64-js: "npm:1.5.1" + bl: "npm:4.1.0" + brace-expansion: "npm:5.0.2" + buffer: "npm:5.7.1" + call-bind-apply-helpers: "npm:1.0.2" + chalk: "npm:4.1.2" + cli-cursor: "npm:3.1.0" + cli-spinners: "npm:2.6.1" + cliui: "npm:8.0.1" + clone: "npm:1.0.4" + color-convert: "npm:2.0.1" + color-name: "npm:1.1.4" + combined-stream: "npm:1.0.8" + defaults: "npm:1.0.4" + define-lazy-prop: "npm:2.0.0" + delayed-stream: "npm:1.0.0" + dotenv: "npm:16.4.7" + dotenv-expand: "npm:12.0.3" + dunder-proto: "npm:1.0.1" + ejs: "npm:5.0.1" + emoji-regex: "npm:8.0.0" + end-of-stream: "npm:1.4.5" + enquirer: "npm:2.3.6" + es-define-property: "npm:1.0.1" + es-errors: "npm:1.3.0" + es-object-atoms: "npm:1.1.1" + es-set-tostringtag: "npm:2.1.0" + escalade: "npm:3.2.0" + escape-string-regexp: "npm:1.0.5" + figures: "npm:3.2.0" + flat: "npm:5.0.2" + follow-redirects: "npm:1.15.11" + form-data: "npm:4.0.5" + fs-constants: "npm:1.0.0" + function-bind: "npm:1.1.2" + get-caller-file: "npm:2.0.5" + get-intrinsic: "npm:1.3.0" + get-proto: "npm:1.0.1" + gopd: "npm:1.2.0" + has-flag: "npm:4.0.0" + has-symbols: "npm:1.1.0" + has-tostringtag: "npm:1.0.2" + hasown: "npm:2.0.2" + ieee754: "npm:1.2.1" + ignore: "npm:7.0.5" + inherits: "npm:2.0.4" + is-docker: "npm:2.2.1" + is-fullwidth-code-point: "npm:3.0.0" + is-interactive: "npm:1.0.0" + is-unicode-supported: "npm:0.1.0" + is-wsl: "npm:2.2.0" + json5: "npm:2.2.3" + jsonc-parser: "npm:3.2.0" + lines-and-columns: "npm:2.0.3" + log-symbols: "npm:4.1.0" + math-intrinsics: "npm:1.1.0" + mime-db: "npm:1.52.0" + mime-types: "npm:2.1.35" + mimic-fn: "npm:2.1.0" + minimatch: "npm:10.2.4" + minimist: "npm:1.2.8" + npm-run-path: "npm:4.0.1" + once: "npm:1.4.0" + onetime: "npm:5.1.2" + open: "npm:8.4.2" + ora: "npm:5.3.0" + path-key: "npm:3.1.1" + picocolors: "npm:1.1.1" + proxy-from-env: "npm:2.1.0" + readable-stream: "npm:3.6.2" + require-directory: "npm:2.1.1" + resolve.exports: "npm:2.0.3" + restore-cursor: "npm:3.1.0" + safe-buffer: "npm:5.2.1" + semver: "npm:7.7.4" + signal-exit: "npm:3.0.7" + smol-toml: "npm:1.6.1" + string-width: "npm:4.2.3" + string_decoder: "npm:1.3.0" + strip-ansi: "npm:6.0.1" + strip-bom: "npm:3.0.0" + supports-color: "npm:7.2.0" + tar-stream: "npm:2.2.0" + tmp: "npm:0.2.4" + tree-kill: "npm:1.2.2" + tsconfig-paths: "npm:4.2.0" + tslib: "npm:2.8.1" + util-deprecate: "npm:1.0.2" + wcwidth: "npm:1.0.1" + wrap-ansi: "npm:7.0.0" + wrappy: "npm:1.0.2" + y18n: "npm:5.0.8" + yaml: "npm:2.8.0" + yargs: "npm:17.7.2" + yargs-parser: "npm:21.1.1" + peerDependencies: + "@swc-node/register": ^1.11.1 + "@swc/core": ^1.15.8 + dependenciesMeta: + "@nx/nx-darwin-arm64": + optional: true + "@nx/nx-darwin-x64": + optional: true + "@nx/nx-freebsd-x64": + optional: true + "@nx/nx-linux-arm-gnueabihf": + optional: true + "@nx/nx-linux-arm64-gnu": + optional: true + "@nx/nx-linux-arm64-musl": + optional: true + "@nx/nx-linux-x64-gnu": + optional: true + "@nx/nx-linux-x64-musl": + optional: true + "@nx/nx-win32-arm64-msvc": + optional: true + "@nx/nx-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc-node/register": + optional: true + "@swc/core": + optional: true + bin: + nx: dist/bin/nx.js + nx-cloud: dist/bin/nx-cloud.js + checksum: 10/d00559454655fec9e162c4038440e43e68d57511c00ced818469eae8df8e8335756142ce25d919aefb0bd66b9d33e6ea4323458ffab21a82f1fb32e851a224da + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -20423,7 +20829,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:1.4.0, once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -20432,7 +20838,7 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^5.1.0, onetime@npm:^5.1.2": +"onetime@npm:5.1.2, onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" dependencies: @@ -20441,6 +20847,17 @@ __metadata: languageName: node linkType: hard +"open@npm:8.4.2, open@npm:^8.4.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9 + languageName: node + linkType: hard + "open@npm:^10.0.3": version: 10.2.0 resolution: "open@npm:10.2.0" @@ -20453,17 +20870,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.4.0": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9 - languageName: node - linkType: hard - "opener@npm:^1.5.2": version: 1.5.2 resolution: "opener@npm:1.5.2" @@ -20911,7 +21317,7 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": +"path-key@npm:3.1.1, path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 10/55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 @@ -22589,7 +22995,7 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^2.1.0": +"proxy-from-env@npm:2.1.0, proxy-from-env@npm:^2.1.0": version: 2.1.0 resolution: "proxy-from-env@npm:2.1.0" checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee @@ -23065,7 +23471,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:3, readable-stream@npm:3.6.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -23335,7 +23741,7 @@ __metadata: languageName: node linkType: hard -"require-directory@npm:^2.1.1": +"require-directory@npm:2.1.1, require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf @@ -23530,7 +23936,7 @@ __metadata: languageName: node linkType: hard -"restore-cursor@npm:^3.1.0": +"restore-cursor@npm:3.1.0, restore-cursor@npm:^3.1.0": version: 3.1.0 resolution: "restore-cursor@npm:3.1.0" dependencies: @@ -24036,6 +24442,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.7.4": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75 + languageName: node + linkType: hard + "semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -24295,7 +24710,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:3.0.7, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -24825,7 +25240,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:4.2.3, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -24937,7 +25352,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": +"string_decoder@npm:1.3.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -24966,7 +25381,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -25010,7 +25425,7 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^3.0.0": +"strip-bom@npm:3.0.0, strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" checksum: 10/8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b @@ -25203,6 +25618,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:7.2.0, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a + languageName: node + linkType: hard + "supports-color@npm:8.1.1, supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" @@ -25228,15 +25652,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a - languageName: node - linkType: hard - "supports-hyperlinks@npm:^4.4.0": version: 4.4.0 resolution: "supports-hyperlinks@npm:4.4.0" @@ -25362,7 +25777,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": +"tar-stream@npm:2.2.0, tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -25636,6 +26051,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:0.2.4": + version: 0.2.4 + resolution: "tmp@npm:0.2.4" + checksum: 10/6fa6c4e6749824e51cb45f0b050090f0fec6b037cfd327d328ebaf3eae80b7e1a53c6651f9bc36f1fa80cf367ec9dc05067fc1a14034fcfbba96d3a2aaa5c732 + languageName: node + linkType: hard + "tmp@npm:^0.2.1, tmp@npm:~0.2.1": version: 0.2.5 resolution: "tmp@npm:0.2.5" @@ -25934,26 +26356,26 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths@npm:^3.15.0": - version: 3.15.0 - resolution: "tsconfig-paths@npm:3.15.0" +"tsconfig-paths@npm:4.2.0, tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" dependencies: - "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.2" + json5: "npm:^2.2.2" minimist: "npm:^1.2.6" strip-bom: "npm:^3.0.0" - checksum: 10/2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 + checksum: 10/5e55cc2fb6b800eb72011522e10edefccb45b1f9af055681a51354c9b597d1390c6fa9cc356b8c7529f195ac8a90a78190d563159f3a1eed10e01bbd4d01a8ab languageName: node linkType: hard -"tsconfig-paths@npm:^4.1.2": - version: 4.2.0 - resolution: "tsconfig-paths@npm:4.2.0" +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" dependencies: - json5: "npm:^2.2.2" + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" minimist: "npm:^1.2.6" strip-bom: "npm:^3.0.0" - checksum: 10/5e55cc2fb6b800eb72011522e10edefccb45b1f9af055681a51354c9b597d1390c6fa9cc356b8c7529f195ac8a90a78190d563159f3a1eed10e01bbd4d01a8ab + checksum: 10/2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 languageName: node linkType: hard @@ -26536,7 +26958,7 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": +"util-deprecate@npm:1.0.2, util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 @@ -26765,7 +27187,7 @@ __metadata: languageName: node linkType: hard -"wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": +"wcwidth@npm:1.0.1, wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" dependencies: @@ -27297,7 +27719,7 @@ __metadata: dependencies: "@faker-js/faker": "npm:9.9.0" "@ls-lint/ls-lint": "npm:2.3.1" - "@nx/jest": "npm:22.6.5" + "@nx/jest": "npm:22.7.0" "@nx/node": "npm:22.6.5" "@nx/workspace": "npm:22.6.5" "@stylistic/eslint-plugin": "npm:5.10.0" @@ -27587,7 +28009,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -27609,7 +28031,7 @@ __metadata: languageName: node linkType: hard -"wrappy@npm:1": +"wrappy@npm:1, wrappy@npm:1.0.2": version: 1.0.2 resolution: "wrappy@npm:1.0.2" checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 @@ -27774,7 +28196,7 @@ __metadata: languageName: node linkType: hard -"y18n@npm:^5.0.5": +"y18n@npm:5.0.8, y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d @@ -27795,6 +28217,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:2.8.0": + version: 2.8.0 + resolution: "yaml@npm:2.8.0" + bin: + yaml: bin.mjs + checksum: 10/7d4bd9c10d0e467601f496193f2ac254140f8e36f96f5ff7f852b9ce37974168eb7354f4c36dc8837dde527a2043d004b6aff48818ec24a69ab2dd3c6b6c381c + languageName: node + linkType: hard + "yaml@npm:^1.10.0": version: 1.10.2 resolution: "yaml@npm:1.10.2" From 1701f76bbc618c5684bf13eccac0d15ef6582a8b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:49:46 +0000 Subject: [PATCH 06/49] chore(deps): update nx monorepo tooling to v22.7.0 --- package.json | 6 ++--- yarn.lock | 72 +++++++++++++++++++--------------------------------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 85197897f29..eaea19a3ac6 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "@faker-js/faker": "9.9.0", "@ls-lint/ls-lint": "2.3.1", "@nx/jest": "22.7.0", - "@nx/node": "22.6.5", - "@nx/workspace": "22.6.5", + "@nx/node": "22.7.0", + "@nx/workspace": "22.7.0", "@stylistic/eslint-plugin": "5.10.0", "@tony.ganchev/eslint-plugin-header": "3.4.4", "@types/eslint": "9.6.1", @@ -59,7 +59,7 @@ "js-yaml": "4.1.1", "jsdom": "29.0.2", "nock": "14.0.13", - "nx": "22.6.5", + "nx": "22.7.0", "playwright": "1.59.1", "prettier": "3.8.3", "rimraf": "6.1.3", diff --git a/yarn.lock b/yarn.lock index 6fabf9c1f7d..086bc11ca8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5359,56 +5359,36 @@ __metadata: languageName: node linkType: hard -"@nx/docker@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/docker@npm:22.6.5" +"@nx/docker@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/docker@npm:22.7.0" dependencies: - "@nx/devkit": "npm:22.6.5" + "@nx/devkit": "npm:22.7.0" enquirer: "npm:~2.3.6" tslib: "npm:^2.3.0" - checksum: 10/bb4465d7e18943b5f6b8bad797f5b71d5d3bd7428160dcbdc141b3f192538ac3ae912532a9929f0d3afdcc96e6ccbdf26377159133e1c4c9af4dc6de090ae4d3 + checksum: 10/7a4ba33beabafb58ac824be2f439de67ef287035666ee757913759f8e3ec68bce6739b13362ad1a296695cf817966699adb40cb1ea6516a818d082071059998e languageName: node linkType: hard -"@nx/eslint@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/eslint@npm:22.6.5" +"@nx/eslint@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/eslint@npm:22.7.0" dependencies: - "@nx/devkit": "npm:22.6.5" - "@nx/js": "npm:22.6.5" + "@nx/devkit": "npm:22.7.0" + "@nx/js": "npm:22.7.0" semver: "npm:^7.6.3" tslib: "npm:^2.3.0" typescript: "npm:~5.9.2" peerDependencies: + "@nx/jest": 22.7.0 "@zkochan/js-yaml": 0.0.7 eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: + "@nx/jest": + optional: true "@zkochan/js-yaml": optional: true - checksum: 10/6a54e745204b1f985696b0404074c68178c677428d38ff204d56c392d292b44987509f80222aaad98135c556efc96217abc1651562348919aeb1e0e822110f67 - languageName: node - linkType: hard - -"@nx/jest@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/jest@npm:22.6.5" - dependencies: - "@jest/reporters": "npm:^30.0.2" - "@jest/test-result": "npm:^30.0.2" - "@nx/devkit": "npm:22.6.5" - "@nx/js": "npm:22.6.5" - "@phenomnomnominal/tsquery": "npm:~6.1.4" - identity-obj-proxy: "npm:3.0.0" - jest-config: "npm:^30.0.2" - jest-resolve: "npm:^30.0.2" - jest-util: "npm:^30.0.2" - minimatch: "npm:10.2.4" - picocolors: "npm:^1.1.0" - resolve.exports: "npm:2.0.3" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - checksum: 10/748a0883a05b4539593710c4c0e83800ac53c0e2aa8c7b2428ae4e1db6bcd78fa1772c9b42da946c5caabe5b5907a4c7eff7ccae05ac8f37fb21a4232fdf086c + checksum: 10/b54891278a042c3adb44b6dbdfe0c19637a495e0c7e84e174fd60164e109ba2f7b4ff2b4d2d00a39d2ed797fec69a18fa9ab7705a881bd6aa497423147143005 languageName: node linkType: hard @@ -5513,19 +5493,19 @@ __metadata: languageName: node linkType: hard -"@nx/node@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/node@npm:22.6.5" +"@nx/node@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/node@npm:22.7.0" dependencies: - "@nx/devkit": "npm:22.6.5" - "@nx/docker": "npm:22.6.5" - "@nx/eslint": "npm:22.6.5" - "@nx/jest": "npm:22.6.5" - "@nx/js": "npm:22.6.5" + "@nx/devkit": "npm:22.7.0" + "@nx/docker": "npm:22.7.0" + "@nx/eslint": "npm:22.7.0" + "@nx/jest": "npm:22.7.0" + "@nx/js": "npm:22.7.0" kill-port: "npm:^1.6.1" tcp-port-used: "npm:^1.0.2" tslib: "npm:^2.3.0" - checksum: 10/e47c219c2c86e48cafeee29d31742fc9a94ba6fa42a56de2278ece7104c369bc92aba1a15b9d0f8de274686a051efc6d3448c9c981b64bd870169790c56305d5 + checksum: 10/7531681966f4792c582c10cb184e25e323ad67d46d4403767de4dea32b4f61936dde8e48ff537d9ed63b66174b91113c6e00f90060dc29ff585f95b19c53c559 languageName: node linkType: hard @@ -27720,8 +27700,8 @@ __metadata: "@faker-js/faker": "npm:9.9.0" "@ls-lint/ls-lint": "npm:2.3.1" "@nx/jest": "npm:22.7.0" - "@nx/node": "npm:22.6.5" - "@nx/workspace": "npm:22.6.5" + "@nx/node": "npm:22.7.0" + "@nx/workspace": "npm:22.7.0" "@stylistic/eslint-plugin": "npm:5.10.0" "@tony.ganchev/eslint-plugin-header": "npm:3.4.4" "@types/eslint": "npm:9.6.1" @@ -27771,7 +27751,7 @@ __metadata: logdown: "npm:3.3.1" long: "npm:5.3.2" nock: "npm:14.0.13" - nx: "npm:22.6.5" + nx: "npm:22.7.0" playwright: "npm:1.59.1" prettier: "npm:3.8.3" rimraf: "npm:6.1.3" From 233cd9a56edcbc498305b36017d19305e518031a Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:44:59 +0200 Subject: [PATCH 07/49] test: skip TC-1097 since it's currently broken by the backend [WPB-22420] (#21168) This test is currently broken due to the dev backend taking more than 10s to delete the conversation. (See: https://github.com/wireapp/wire-server/pull/5205) Until this issue has been resolved it will be skipped to unblock CI. --- .../test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index d69c230c57d..c93e8ef102e 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -330,7 +330,9 @@ test.describe('History Backup', () => { }, ); - test( + // TODO: unskip this test once https://github.com/wireapp/wire-server/pull/5205 is merged + // This test is currently broken due to the backend taking more than 10s to delete the conversation + test.skip( 'I should not see the deleted group after restore from the backup', {tag: ['@TC-1097', '@regression']}, async ({createPage}, testInfo) => { From 5a46e4a3a3109fa687d491b79e5943872b6a858e Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Wed, 29 Apr 2026 05:39:56 +0200 Subject: [PATCH 08/49] ci: add dynamic sharding for E2E tests [WPB-24724] (#21155) * ci: add dynamic sharding for E2E tests [WPB-24724] * fix: transform shards array using fromJSON * refactor: log number of computed shards --- .github/workflows/e2e-tests.yml | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index bf29868f0ea..d02c4bbd5f3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -96,11 +96,39 @@ jobs: TEST_SERVICE_URL: http://localhost:8080 run: yarn playwright test -c apps/webapp/playwright.config.ts apps/webapp/test/e2e_tests/specs/CriticalFlow/startupHasNoConsoleErrors.spec.ts + # Since not always all e2e tests are run the number of shards to start up is computed dynamically from the amount of tests which will run + compute_shards: + name: Compute number of shards to use + runs-on: ubuntu-24.04 + + outputs: + matrixShards: ${{ steps.computeShards.outputs.matrixShards }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f + with: + node-version-file: '.nvmrc' + cache: 'yarn' + - run: yarn --immutable + + - name: Compute shards + id: computeShards + env: + GREP: ${{ inputs.grep }} + run: | + NUMBER_OF_TESTS=$(yarn nx run webapp:e2e --list --grep=\"$GREP\" | grep 'Total:' | awk '{print $2}') + NUMBER_OF_SHARDS=$(( $NUMBER_OF_TESTS > 64 ? 64 : $NUMBER_OF_TESTS )) # Limit the number of shards to 64 + MATRIX_SHARDS=$(jq -nc --argjson len "$NUMBER_OF_SHARDS" '[range(1; $len + 1)]') + echo "matrixShards=$MATRIX_SHARDS" >> $GITHUB_OUTPUT + echo "$NUMBER_OF_TESTS tests will be run across $NUMBER_OF_SHARDS shards" + test: name: Run E2E Tests runs-on: ubuntu-24.04 timeout-minutes: 45 - needs: [bootstrap_smoke_test] + needs: [bootstrap_smoke_test, compute_shards] container: image: mcr.microsoft.com/playwright:v1.59.1-noble options: --ipc=host @@ -108,8 +136,7 @@ jobs: strategy: fail-fast: false matrix: - # prettier-ignore - shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64] + shard: ${{ fromJSON(needs.compute_shards.outputs.matrixShards) }} services: # Run the kalium testservice as service container of the current job From 9b8163d0171ea14d81bdee47043eda8b1cc03261 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:14:31 +0000 Subject: [PATCH 09/49] chore(deps): update dependency baseline-browser-mapping to v2.10.22 --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 70c45b2cb53..06f9bcf25fe 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -136,7 +136,7 @@ "babel-jest": "30.3.0", "babel-loader": "10.1.1", "babel-plugin-transform-import-meta": "2.3.3", - "baseline-browser-mapping": "2.10.21", + "baseline-browser-mapping": "2.10.22", "browserslist": "4.28.2", "copy-webpack-plugin": "13.0.1", "css-loader": "7.1.4", diff --git a/yarn.lock b/yarn.lock index 086bc11ca8f..209aea7e7c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9904,7 +9904,7 @@ __metadata: babel-jest: "npm:30.3.0" babel-loader: "npm:10.1.1" babel-plugin-transform-import-meta: "npm:2.3.3" - baseline-browser-mapping: "npm:2.10.21" + baseline-browser-mapping: "npm:2.10.22" beautiful-react-hooks: "npm:5.0.3" browserslist: "npm:4.28.2" classnames: "npm:2.5.1" @@ -11118,12 +11118,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:2.10.21": - version: 2.10.21 - resolution: "baseline-browser-mapping@npm:2.10.21" +"baseline-browser-mapping@npm:2.10.22": + version: 2.10.22 + resolution: "baseline-browser-mapping@npm:2.10.22" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10/3bbf95e713eef02e2585843fea2792371cbcb6a2395ca2099c1818c10cfb5bce1b7a8aa84050306981748209791cb4442d1e5f3697fda88c36bb106f3d23043c + checksum: 10/2f798ebadf42a4c260400236e00e4b2bf2886781cf4557a11d3f06f0e9674c00b983f8806dc858e2ad28e073333240352771b85e918f9fdd0f90d8a12d9f531c languageName: node linkType: hard From 6a082aca06c155bc8ba3ae01821764acc2d12740 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:35:05 +0000 Subject: [PATCH 10/49] chore(deps): update dependency @nx/webpack to v22.7.0 --- apps/webapp/package.json | 2 +- yarn.lock | 296 ++------------------------------------- 2 files changed, 16 insertions(+), 282 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index d7f476df560..70863debe41 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -106,7 +106,7 @@ "@babel/preset-typescript": "7.28.5", "@emotion/eslint-plugin": "11.12.0", "@formatjs/cli": "6.14.3", - "@nx/webpack": "22.6.5", + "@nx/webpack": "22.7.0", "@playwright/test": "1.59.1", "@roamhq/wrtc": "0.10.0", "@testing-library/dom": "10.4.1", diff --git a/yarn.lock b/yarn.lock index 97f09b06131..489e23eb1a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5325,23 +5325,6 @@ __metadata: languageName: node linkType: hard -"@nx/devkit@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/devkit@npm:22.6.5" - dependencies: - "@zkochan/js-yaml": "npm:0.0.7" - ejs: "npm:5.0.1" - enquirer: "npm:~2.3.6" - minimatch: "npm:10.2.4" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - peerDependencies: - nx: ">= 21 <= 23 || ^22.0.0-0" - checksum: 10/07e6d9e7af0360ea6b07ce38caa041ad95a099405b6182f4c8abd116bee2f87e8e1a829482a4162b392fa33c315ec544d1719daa8fe23cf599ff885f4bef814d - languageName: node - linkType: hard - "@nx/devkit@npm:22.7.0": version: 22.7.0 resolution: "@nx/devkit@npm:22.7.0" @@ -5415,45 +5398,6 @@ __metadata: languageName: node linkType: hard -"@nx/js@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/js@npm:22.6.5" - dependencies: - "@babel/core": "npm:^7.23.2" - "@babel/plugin-proposal-decorators": "npm:^7.22.7" - "@babel/plugin-transform-class-properties": "npm:^7.22.5" - "@babel/plugin-transform-runtime": "npm:^7.23.2" - "@babel/preset-env": "npm:^7.23.2" - "@babel/preset-typescript": "npm:^7.22.5" - "@babel/runtime": "npm:^7.22.6" - "@nx/devkit": "npm:22.6.5" - "@nx/workspace": "npm:22.6.5" - "@zkochan/js-yaml": "npm:0.0.7" - babel-plugin-const-enum: "npm:^1.0.1" - babel-plugin-macros: "npm:^3.1.0" - babel-plugin-transform-typescript-metadata: "npm:^0.3.1" - chalk: "npm:^4.1.0" - columnify: "npm:^1.6.0" - detect-port: "npm:^1.5.1" - ignore: "npm:^5.0.4" - js-tokens: "npm:^4.0.0" - jsonc-parser: "npm:3.2.0" - npm-run-path: "npm:^4.0.1" - picocolors: "npm:^1.1.0" - picomatch: "npm:4.0.4" - semver: "npm:^7.6.3" - source-map-support: "npm:0.5.19" - tinyglobby: "npm:^0.2.12" - tslib: "npm:^2.3.0" - peerDependencies: - verdaccio: ^6.0.5 - peerDependenciesMeta: - verdaccio: - optional: true - checksum: 10/98974ccb905d40eddc443f4f6da97dbc9a88618e88606c854866ecb618759139a9fc37083dd229b74fa78a2de77d2eee33b6b652a7df3aa29781c02beeb2c274 - languageName: node - linkType: hard - "@nx/js@npm:22.7.0": version: 22.7.0 resolution: "@nx/js@npm:22.7.0" @@ -5509,13 +5453,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-darwin-arm64@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-darwin-arm64@npm:22.6.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@nx/nx-darwin-arm64@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-darwin-arm64@npm:22.7.0" @@ -5523,13 +5460,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-darwin-x64@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-darwin-x64@npm:22.6.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-darwin-x64@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-darwin-x64@npm:22.7.0" @@ -5537,13 +5467,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-freebsd-x64@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-freebsd-x64@npm:22.6.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-freebsd-x64@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-freebsd-x64@npm:22.7.0" @@ -5551,13 +5474,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm-gnueabihf@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.6.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@nx/nx-linux-arm-gnueabihf@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.0" @@ -5565,13 +5481,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm64-gnu@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-linux-arm64-gnu@npm:22.6.5" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@nx/nx-linux-arm64-gnu@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.0" @@ -5579,13 +5488,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm64-musl@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-linux-arm64-musl@npm:22.6.5" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@nx/nx-linux-arm64-musl@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm64-musl@npm:22.7.0" @@ -5593,13 +5495,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-x64-gnu@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-linux-x64-gnu@npm:22.6.5" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@nx/nx-linux-x64-gnu@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-x64-gnu@npm:22.7.0" @@ -5607,13 +5502,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-x64-musl@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-linux-x64-musl@npm:22.6.5" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@nx/nx-linux-x64-musl@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-x64-musl@npm:22.7.0" @@ -5621,13 +5509,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-win32-arm64-msvc@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-win32-arm64-msvc@npm:22.6.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@nx/nx-win32-arm64-msvc@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.0" @@ -5635,13 +5516,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-win32-x64-msvc@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/nx-win32-x64-msvc@npm:22.6.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-win32-x64-msvc@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-win32-x64-msvc@npm:22.7.0" @@ -5649,13 +5523,13 @@ __metadata: languageName: node linkType: hard -"@nx/webpack@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/webpack@npm:22.6.5" +"@nx/webpack@npm:22.7.0": + version: 22.7.0 + resolution: "@nx/webpack@npm:22.7.0" dependencies: "@babel/core": "npm:^7.23.2" - "@nx/devkit": "npm:22.6.5" - "@nx/js": "npm:22.6.5" + "@nx/devkit": "npm:22.7.0" + "@nx/js": "npm:22.7.0" "@phenomnomnominal/tsquery": "npm:~6.1.4" ajv: "npm:^8.12.0" autoprefixer: "npm:^10.4.9" @@ -5689,24 +5563,7 @@ __metadata: webpack-dev-server: "npm:^5.2.1" webpack-node-externals: "npm:^3.0.0" webpack-subresource-integrity: "npm:^5.1.0" - checksum: 10/ceb9f78c1d1d720da86754ed5c29f3f9f2d03c3029b444b466f23c40eaa18ebdb35bc67888f98a91dfaf42158c8d896e63c1590ff414c3507c835b48bb9e66bf - languageName: node - linkType: hard - -"@nx/workspace@npm:22.6.5": - version: 22.6.5 - resolution: "@nx/workspace@npm:22.6.5" - dependencies: - "@nx/devkit": "npm:22.6.5" - "@zkochan/js-yaml": "npm:0.0.7" - chalk: "npm:^4.1.0" - enquirer: "npm:~2.3.6" - nx: "npm:22.6.5" - picomatch: "npm:4.0.4" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - checksum: 10/4079a53bb2c1ec9e31e55c5692eea5548003d20a6c8b37d4c4b58310db630d17526daf1fd705c17575c89a6ce6c0c6003488e4b2c4045b70cef82739adbbb2eb + checksum: 10/fc6f7dfb70a4808080e8f79f7845b1af338df02d26750b072aa084344323206d8cea936f33a61386af1d8349926233566dd9534cf8d5ffa5bac91edff06f4dd3 languageName: node linkType: hard @@ -9860,7 +9717,7 @@ __metadata: "@lexical/react": "npm:0.27.2" "@lexical/rich-text": "npm:0.27.2" "@mediapipe/tasks-vision": "npm:0.10.34" - "@nx/webpack": "npm:22.6.5" + "@nx/webpack": "npm:22.7.0" "@playwright/test": "npm:1.59.1" "@roamhq/wrtc": "npm:0.10.0" "@sindresorhus/is": "npm:4.6.0" @@ -10008,23 +9865,13 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/lockfile@npm:1.1.0, @yarnpkg/lockfile@npm:^1.1.0": +"@yarnpkg/lockfile@npm:1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" checksum: 10/cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed languageName: node linkType: hard -"@yarnpkg/parsers@npm:3.0.2": - version: 3.0.2 - resolution: "@yarnpkg/parsers@npm:3.0.2" - dependencies: - js-yaml: "npm:^3.10.0" - tslib: "npm:^2.4.0" - checksum: 10/87506f140d6c401bdd89ff22073c3dd3ec7b6858e7f576e63ec1aea1b0b8a8ec241eb46ca5582dc2071098a86d6a55c3b0628da5eeff91d33afb4fa7cac0cf65 - languageName: node - linkType: hard - "@zkochan/js-yaml@npm:0.0.7": version: 0.0.7 resolution: "@zkochan/js-yaml@npm:0.0.7" @@ -13390,15 +13237,6 @@ __metadata: languageName: node linkType: hard -"dotenv-expand@npm:~11.0.6": - version: 11.0.7 - resolution: "dotenv-expand@npm:11.0.7" - dependencies: - dotenv: "npm:^16.4.5" - checksum: 10/1cd981e2b925e746919e9fca16fa5e953955d021b5d5fea0a4ae96dc61fcc76bc95874e7730f8ceca22f5e3df5a47eb1fc626c3f45e98019ceba54fd58521971 - languageName: node - linkType: hard - "dotenv-extended@npm:2.9.0": version: 2.9.0 resolution: "dotenv-extended@npm:2.9.0" @@ -15389,15 +15227,6 @@ __metadata: languageName: node linkType: hard -"front-matter@npm:^4.0.2": - version: 4.0.2 - resolution: "front-matter@npm:4.0.2" - dependencies: - js-yaml: "npm:^3.13.1" - checksum: 10/8897a831a82c5d35413b02b806ed421e793068ad8bf75e864163ec07b7f0cfd87e2fcce0893e8ceccc8f6c63a46e953a6c01208e573627626867a8b86cf6abb9 - languageName: node - linkType: hard - "fs-constants@npm:1.0.0, fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" @@ -17821,7 +17650,7 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:30.2.0, jest-diff@npm:^30.0.2": +"jest-diff@npm:30.2.0": version: 30.2.0 resolution: "jest-diff@npm:30.2.0" dependencies: @@ -18567,7 +18396,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.1": +"js-yaml@npm:^3.13.1": version: 3.14.2 resolution: "js-yaml@npm:3.14.2" dependencies: @@ -20426,92 +20255,6 @@ __metadata: languageName: node linkType: hard -"nx@npm:22.6.5": - version: 22.6.5 - resolution: "nx@npm:22.6.5" - dependencies: - "@napi-rs/wasm-runtime": "npm:0.2.4" - "@nx/nx-darwin-arm64": "npm:22.6.5" - "@nx/nx-darwin-x64": "npm:22.6.5" - "@nx/nx-freebsd-x64": "npm:22.6.5" - "@nx/nx-linux-arm-gnueabihf": "npm:22.6.5" - "@nx/nx-linux-arm64-gnu": "npm:22.6.5" - "@nx/nx-linux-arm64-musl": "npm:22.6.5" - "@nx/nx-linux-x64-gnu": "npm:22.6.5" - "@nx/nx-linux-x64-musl": "npm:22.6.5" - "@nx/nx-win32-arm64-msvc": "npm:22.6.5" - "@nx/nx-win32-x64-msvc": "npm:22.6.5" - "@yarnpkg/lockfile": "npm:^1.1.0" - "@yarnpkg/parsers": "npm:3.0.2" - "@zkochan/js-yaml": "npm:0.0.7" - axios: "npm:1.15.0" - cli-cursor: "npm:3.1.0" - cli-spinners: "npm:2.6.1" - cliui: "npm:^8.0.1" - dotenv: "npm:~16.4.5" - dotenv-expand: "npm:~11.0.6" - ejs: "npm:5.0.1" - enquirer: "npm:~2.3.6" - figures: "npm:3.2.0" - flat: "npm:^5.0.2" - front-matter: "npm:^4.0.2" - ignore: "npm:^7.0.5" - jest-diff: "npm:^30.0.2" - jsonc-parser: "npm:3.2.0" - lines-and-columns: "npm:2.0.3" - minimatch: "npm:10.2.4" - npm-run-path: "npm:^4.0.1" - open: "npm:^8.4.0" - ora: "npm:5.3.0" - picocolors: "npm:^1.1.0" - resolve.exports: "npm:2.0.3" - semver: "npm:^7.6.3" - smol-toml: "npm:1.6.1" - string-width: "npm:^4.2.3" - tar-stream: "npm:~2.2.0" - tmp: "npm:~0.2.1" - tree-kill: "npm:^1.2.2" - tsconfig-paths: "npm:^4.1.2" - tslib: "npm:^2.3.0" - yaml: "npm:^2.6.0" - yargs: "npm:^17.6.2" - yargs-parser: "npm:21.1.1" - peerDependencies: - "@swc-node/register": ^1.11.1 - "@swc/core": ^1.15.8 - dependenciesMeta: - "@nx/nx-darwin-arm64": - optional: true - "@nx/nx-darwin-x64": - optional: true - "@nx/nx-freebsd-x64": - optional: true - "@nx/nx-linux-arm-gnueabihf": - optional: true - "@nx/nx-linux-arm64-gnu": - optional: true - "@nx/nx-linux-arm64-musl": - optional: true - "@nx/nx-linux-x64-gnu": - optional: true - "@nx/nx-linux-x64-musl": - optional: true - "@nx/nx-win32-arm64-msvc": - optional: true - "@nx/nx-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc-node/register": - optional: true - "@swc/core": - optional: true - bin: - nx: bin/nx.js - nx-cloud: bin/nx-cloud.js - checksum: 10/9d9436e623aed840e6bb707d6f83b49868f31d3f26658e91997c98fa35751a1d0a5a5f282d36b65e4fce9c2bc11913ec19abb15455968efb34a5676ffa45ce17 - languageName: node - linkType: hard - "nx@npm:22.7.0": version: 22.7.0 resolution: "nx@npm:22.7.0" @@ -20827,7 +20570,7 @@ __metadata: languageName: node linkType: hard -"open@npm:8.4.2, open@npm:^8.4.0": +"open@npm:8.4.2": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -25757,7 +25500,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:2.2.0, tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": +"tar-stream@npm:2.2.0, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -26038,7 +25781,7 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.1, tmp@npm:~0.2.1": +"tmp@npm:^0.2.1": version: 0.2.5 resolution: "tmp@npm:0.2.5" checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec @@ -26211,7 +25954,7 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2": +"tree-kill@npm:1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" bin: @@ -28213,15 +27956,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.6.0": - version: 2.8.2 - resolution: "yaml@npm:2.8.2" - bin: - yaml: bin.mjs - checksum: 10/4eab0074da6bc5a5bffd25b9b359cf7061b771b95d1b3b571852098380db3b1b8f96e0f1f354b56cc7216aa97cea25163377ccbc33a2e9ce00316fe8d02f4539 - languageName: node - linkType: hard - "yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -28229,7 +27963,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": +"yargs@npm:17.7.2, yargs@npm:^17.3.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From f1a98f97e7f0d736df70f80c454f8fad0df7c1f2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:35:23 +0000 Subject: [PATCH 11/49] fix(deps): update aws-sdk-js-v3 monorepo to v3.1037.0 --- libraries/api-client/package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/api-client/package.json b/libraries/api-client/package.json index 17f681518db..767ab4fe2fc 100644 --- a/libraries/api-client/package.json +++ b/libraries/api-client/package.json @@ -18,8 +18,8 @@ "./lib/shims/node/websocket": "./lib/shims/browser/websocket.js" }, "dependencies": { - "@aws-sdk/client-s3": "3.1036.0", - "@aws-sdk/lib-storage": "3.1036.0", + "@aws-sdk/client-s3": "3.1037.0", + "@aws-sdk/lib-storage": "3.1037.0", "@wireapp/priority-queue": "2.1.19", "@wireapp/protocol-messaging": "1.56.0", "axios-retry": "4.5.0", diff --git a/yarn.lock b/yarn.lock index 97f09b06131..f5fe3958c00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,9 +167,9 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-s3@npm:3.1036.0": - version: 3.1036.0 - resolution: "@aws-sdk/client-s3@npm:3.1036.0" +"@aws-sdk/client-s3@npm:3.1037.0": + version: 3.1037.0 + resolution: "@aws-sdk/client-s3@npm:3.1037.0" dependencies: "@aws-crypto/sha1-browser": "npm:5.2.0" "@aws-crypto/sha256-browser": "npm:5.2.0" @@ -226,7 +226,7 @@ __metadata: "@smithy/util-utf8": "npm:^4.2.2" "@smithy/util-waiter": "npm:^4.2.16" tslib: "npm:^2.6.2" - checksum: 10/3711d8a86a5a2b0d60d6bca08bac4afe84fe6c1e740a9865d72f0ad433d0ccf20d8004d953cf9bcc5d74d0490b0bb577ffdc4310f5b5170beef0f8018ecb8f4d + checksum: 10/957bd108a9a938bb6e8a3451b47d16bb1a4c4a148094450a5af0a2a18391bb038ed53e840d77f6e88b2d274191ca1b0feb317ee54971aa6dff6d4321a1003f34 languageName: node linkType: hard @@ -396,9 +396,9 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/lib-storage@npm:3.1036.0": - version: 3.1036.0 - resolution: "@aws-sdk/lib-storage@npm:3.1036.0" +"@aws-sdk/lib-storage@npm:3.1037.0": + version: 3.1037.0 + resolution: "@aws-sdk/lib-storage@npm:3.1037.0" dependencies: "@smithy/middleware-endpoint": "npm:^4.4.32" "@smithy/protocol-http": "npm:^5.3.14" @@ -409,8 +409,8 @@ __metadata: stream-browserify: "npm:3.0.0" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-s3": ^3.1036.0 - checksum: 10/c54294c56de2bad787143c88c666db0a2b6b4b9fd1061662f529114e60d32b29f3f7e06a0ea40dab590a0bee4d00eabb314698ed80e769da1710276ea2dea952 + "@aws-sdk/client-s3": ^3.1037.0 + checksum: 10/c983adfa421a8a1dc1aecd1e815590f86409665884d12e33381c934d01141ebdfc0fd86b756ff27c3615a9098cd4e0f50de500f82b3b95a896bf48d2f9e630ae languageName: node linkType: hard @@ -9482,8 +9482,8 @@ __metadata: version: 0.0.0-use.local resolution: "@wireapp/api-client@workspace:libraries/api-client" dependencies: - "@aws-sdk/client-s3": "npm:3.1036.0" - "@aws-sdk/lib-storage": "npm:3.1036.0" + "@aws-sdk/client-s3": "npm:3.1037.0" + "@aws-sdk/lib-storage": "npm:3.1037.0" "@swc/core": "npm:1.15.30" "@swc/jest": "npm:0.2.39" "@types/jest": "npm:30.0.0" From 1e70a43c9c4d93ea4180b73e6199808b3c562c97 Mon Sep 17 00:00:00 2001 From: Zhanna Chaikovska <87633082+zhannach@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:47:42 +0200 Subject: [PATCH 12/49] test: verify link preview and media playback [WPB-24824] (#21172) * test: verify link preview and media playback TC-1264 * test: refactor TC-1264 to fix flaky media playback assertions --- .../specs/LinkPreview/linkPreview.spec.ts | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts diff --git a/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts new file mode 100644 index 00000000000..2529e3c9962 --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts @@ -0,0 +1,94 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {FrameLocator} from 'playwright/test'; +import {PageManager} from 'test/e2e_tests/pageManager'; +import {test, withLogin, expect, withConnectedUser} from 'test/e2e_tests/test.fixtures'; + +test.describe('Link Preview', () => { + test( + 'I want to see preview for youtube, spotify, soundcloud or vimeo which was sent into a group conversation, on the second end', + {tag: ['@TC-1264', '@regression']}, + async ({createPage, createUser, createTeam}) => { + const userB = await createUser(); + const team = await createTeam('Test Team', { + users: [userB], + }); + const userA = team.owner; + + const [userAPages, userBPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), + ]); + + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + + const linkConfigs = [ + { + url: 'https://soundcloud.com/slacker2d/dresden-ischen-sou-geil-v2', + iframeSelector: 'iframe.soundcloud', + validate: async (frame: FrameLocator) => { + await frame.getByRole('application', {name: 'Play', exact: true}).click(); + await expect(frame.getByRole('application', {name: 'Pause', exact: true})).toBeVisible(); + }, + }, + { + url: 'https://www.youtube.com/watch?v=BMFsJiAcELY', + iframeSelector: 'iframe.youtube', + validate: async (frame: FrameLocator) => { + await expect(frame.getByLabel('Time elapsed')).not.toBeVisible(); + await frame.getByRole('button', {name: 'Play video', exact: true}).click(); + // Video player might take some time to load, so here we aren't checking for the pause button + await expect(frame.getByLabel('Time elapsed')).toBeVisible(); + }, + }, + { + url: 'https://play.spotify.com/album/7buEcyw6fJF3WPgr06BomH', + iframeSelector: 'iframe.spotify', + validate: async (frame: FrameLocator) => { + await frame.getByRole('button', {name: 'Play', exact: true}).click(); + await expect(frame.getByRole('button', {name: 'Pause', exact: true})).toBeVisible(); + }, + }, + { + url: 'https://vimeo.com/288344114', + iframeSelector: 'iframe.vimeo', + validate: async (frame: FrameLocator) => { + await frame.getByRole('button', {name: 'Play', exact: true}).click(); + await expect(frame.getByRole('button', {name: 'Pause', exact: true})).toBeVisible(); + }, + }, + ]; + + for (const link of linkConfigs) { + await userAPages.conversation().sendMessage(link.url); + + // Validating the link preview for both users + for (const pages of [userAPages, userBPages]) { + const message = pages.conversation().getMessage({content: link.url}); + const frame = message.frameLocator(link.iframeSelector); + + await expect(message.locator(link.iframeSelector)).toBeVisible(); + await link.validate(frame); + } + } + }, + ); +}); From 5a7448c08b3ef5006678ec86aac5fb1ba7ab0644 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 16:05:56 +0000 Subject: [PATCH 13/49] chore(deps): update dependency baseline-browser-mapping to v2.10.23 --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 1dc71360452..aab8b0d98ad 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -136,7 +136,7 @@ "babel-jest": "30.3.0", "babel-loader": "10.1.1", "babel-plugin-transform-import-meta": "2.3.3", - "baseline-browser-mapping": "2.10.22", + "baseline-browser-mapping": "2.10.23", "browserslist": "4.28.2", "copy-webpack-plugin": "13.0.1", "css-loader": "7.1.4", diff --git a/yarn.lock b/yarn.lock index 60ed69f6698..5a8b753b0f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9761,7 +9761,7 @@ __metadata: babel-jest: "npm:30.3.0" babel-loader: "npm:10.1.1" babel-plugin-transform-import-meta: "npm:2.3.3" - baseline-browser-mapping: "npm:2.10.22" + baseline-browser-mapping: "npm:2.10.23" beautiful-react-hooks: "npm:5.0.3" browserslist: "npm:4.28.2" classnames: "npm:2.5.1" @@ -10965,12 +10965,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:2.10.22": - version: 2.10.22 - resolution: "baseline-browser-mapping@npm:2.10.22" +"baseline-browser-mapping@npm:2.10.23": + version: 2.10.23 + resolution: "baseline-browser-mapping@npm:2.10.23" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10/2f798ebadf42a4c260400236e00e4b2bf2886781cf4557a11d3f06f0e9674c00b983f8806dc858e2ad28e073333240352771b85e918f9fdd0f90d8a12d9f531c + checksum: 10/54c21dbc86b79f53b1c15fc7577695f1ccf6e1dba0aaa9161ad818848330c45c5d6c5debdcac16807037d04371b828213976a25ea77cc4b30466d9b52bbc702c languageName: node linkType: hard From 2c4cbdc78b56dfd74da665261d7e6146fafb5851 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 16:06:12 +0000 Subject: [PATCH 14/49] chore(deps): update dependency postcss to v8.5.12 --- apps/webapp/package.json | 2 +- yarn.lock | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 1dc71360452..acf678b8846 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -153,7 +153,7 @@ "less-loader": "12.3.2", "os-browserify": "0.3.0", "path-browserify": "1.0.1", - "postcss": "8.5.10", + "postcss": "8.5.12", "postcss-import": "16.1.1", "postcss-less": "6.0.0", "postcss-loader": "8.2.1", diff --git a/yarn.lock b/yarn.lock index 60ed69f6698..8456a79110c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9803,7 +9803,7 @@ __metadata: path-browserify: "npm:1.0.1" path-to-regexp: "npm:8.4.2" platform: "npm:1.3.6" - postcss: "npm:8.5.10" + postcss: "npm:8.5.12" postcss-import: "npm:16.1.1" postcss-less: "npm:6.0.0" postcss-loader: "npm:8.2.1" @@ -22435,14 +22435,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.5.10, postcss@npm:^8.4.32, postcss@npm:^8.4.33, postcss@npm:^8.4.38, postcss@npm:^8.4.40, postcss@npm:^8.5.9": - version: 8.5.10 - resolution: "postcss@npm:8.5.10" +"postcss@npm:8.5.12": + version: 8.5.12 + resolution: "postcss@npm:8.5.12" dependencies: nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/7eac6169e535b63c8412e94d4f6047fc23efa3e9dde804b541940043c831b25f1cd867d83cd2c4371ad2450c8abcb42c208aa25668c1f0f3650d7f72faf711a8 + checksum: 10/ec6b79b68c363eca3c8ffceb134a4ab637274aee6ac0857614bf7c18d40ce4ce5f9036edec57b7e0be99895724d2599d0ec7328dbd7f407204e7548697b322f1 languageName: node linkType: hard @@ -22456,6 +22456,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.32, postcss@npm:^8.4.33, postcss@npm:^8.4.38, postcss@npm:^8.4.40, postcss@npm:^8.5.9": + version: 8.5.10 + resolution: "postcss@npm:8.5.10" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/7eac6169e535b63c8412e94d4f6047fc23efa3e9dde804b541940043c831b25f1cd867d83cd2c4371ad2450c8abcb42c208aa25668c1f0f3650d7f72faf711a8 + languageName: node + linkType: hard + "prebuild-install@npm:^7.1.1": version: 7.1.3 resolution: "prebuild-install@npm:7.1.3" From 5182547005b1def19fb0d64fcc5577e0faca3c5b Mon Sep 17 00:00:00 2001 From: Zhanna Chaikovska <87633082+zhannach@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:19:14 +0200 Subject: [PATCH 15/49] test: add verification step for Terms of Use link in settings TC-8638 (#21176) --- .../test/e2e_tests/pageManager/index.ts | 2 ++ .../pageManager/webapp/pages/about.page.ts | 36 +++++++++++++++++++ .../pageManager/webapp/pages/settings.page.ts | 6 ++++ .../personalAccountLifecycle-TC-8638.spec.ts | 13 +++++++ 4 files changed, 57 insertions(+) create mode 100644 apps/webapp/test/e2e_tests/pageManager/webapp/pages/about.page.ts diff --git a/apps/webapp/test/e2e_tests/pageManager/index.ts b/apps/webapp/test/e2e_tests/pageManager/index.ts index 3fb4be5fcde..b0a60a3166f 100644 --- a/apps/webapp/test/e2e_tests/pageManager/index.ts +++ b/apps/webapp/test/e2e_tests/pageManager/index.ts @@ -75,6 +75,7 @@ import {ConversationJoinPage} from './webapp/pages/conversationJoin.page'; import {CreateConversationModal} from './webapp/modals/createConversation'; import {InviteModal} from './webapp/modals/invite.modal'; import {JoinGuestLinkPasswordModal} from './webapp/modals/joinGuestLinkPassword.modal'; +import {AboutPage} from './webapp/pages/about.page'; export const webAppPath = process.env.WEBAPP_URL ?? ''; @@ -173,6 +174,7 @@ export class PageManager { options: () => this.getOrCreate('webapp.pages.options', () => new OptionsPage(this.page)), audioVideoSettings: () => this.getOrCreate('webapp.pages.audioVideoSettings', () => new AudioVideoSettingsPage(this.page)), + about: () => this.getOrCreate('webapp.pages.about', () => new AboutPage(this.page)), outgoingConnection: () => this.getOrCreate('webapp.pages.outgoingConnection', () => new OutgoingConnectionPage(this.page)), guestOptions: () => this.getOrCreate('webapp.pages.guestOptions', () => GuestOptionsPage(this.page)), diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/about.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/about.page.ts new file mode 100644 index 00000000000..e869d87005b --- /dev/null +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/about.page.ts @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {Locator, Page} from '@playwright/test'; + +export class AboutPage { + private readonly page: Page; + readonly wireSection: Locator; + readonly termsOfUseLink: Locator; + + constructor(page: Page) { + this.page = page; + this.wireSection = this.page.getByRole('group', {name: 'Wire'}); + this.termsOfUseLink = this.wireSection.getByRole('link', {name: 'Terms of Use'}); + } + + async clickTermsOfUseLink() { + await this.termsOfUseLink.click(); + } +} diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/settings.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/settings.page.ts index f2f45878751..7307f33356c 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/settings.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/settings.page.ts @@ -24,6 +24,7 @@ export class SettingsPage { readonly devicesButton: Locator; readonly optionsButton: Locator; readonly audioVideoButton: Locator; + readonly aboutButton: Locator; readonly copyProfileLinkButton: Locator; readonly profileLink: Locator; @@ -32,6 +33,7 @@ export class SettingsPage { this.devicesButton = page.getByRole('button', {name: 'Devices'}); this.optionsButton = page.getByRole('button', {name: 'Options'}); this.audioVideoButton = page.getByRole('button', {name: 'Audio / Video'}); + this.aboutButton = page.getByRole('button', {name: 'About'}); this.copyProfileLinkButton = page.getByRole('button', {name: 'Copy Profile Link'}); this.profileLink = page.getByTestId('element-profile-link'); } @@ -47,4 +49,8 @@ export class SettingsPage { async clickOptionsButton() { await this.optionsButton.click(); } + + async clickAboutButton() { + await this.aboutButton.click(); + } } diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts index 9df449a2f40..7c0ab37ae18 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts @@ -161,6 +161,19 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async ).toBeVisible(); }); + await test.step('Personal user A opens About -> terms of use link in settings', async () => { + const {pages, components} = pageManagerA.webapp; + + await components.conversationSidebar().clickPreferencesButton(); + await pages.settings().clickAboutButton(); + + const [newTab] = await Promise.all([pageManagerA.page.waitForEvent('popup'), pages.about().clickTermsOfUseLink()]); + + await expect(newTab).toHaveURL(/legal#terms/); + + await newTab.close(); + }); + await test.step('Personal User A deletes their account', async () => { const {pages, modals, components} = pageManagerA.webapp; await components.conversationSidebar().clickPreferencesButton(); From 89140c005b2343231566e77dc597aefaa8ae7262 Mon Sep 17 00:00:00 2001 From: Zafar Saeed Khan Date: Thu, 30 Apr 2026 00:05:00 +0200 Subject: [PATCH 16/49] feat: add capability polling and move it behind feature flag [WPB-24520] (#21109) * feat: add capability polling and move it behind feature flag * feat: enhance polling logic with panel state checks, change useEffect return type * wip # Conflicts: # apps/webapp/jest.config.ts # apps/webapp/jest.report.config.cjs * test: coverage for capabilityInformationValidator --- apps/webapp/jest.config.ts | 2 +- apps/webapp/jest.report.config.cjs | 2 +- .../capabilityInformationValidator.test.ts | 115 ++++++++++++++++++ .../capabilityInformationValidator.ts | 40 ++++++ .../videoBackgroundPerformancePanel.tsx | 78 +++++++----- .../backgroundEffectsWorkerTypes.ts | 4 +- package.json | 1 + yarn.lock | 8 ++ 8 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.test.ts create mode 100644 apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.ts diff --git a/apps/webapp/jest.config.ts b/apps/webapp/jest.config.ts index 64cd0f9c1c1..1a9250a40aa 100644 --- a/apps/webapp/jest.config.ts +++ b/apps/webapp/jest.config.ts @@ -50,7 +50,7 @@ const config: Config = { }, testPathIgnorePatterns: ['/server', '/.yalc', '/test/e2e_tests'], testRunner: 'jest-jasmine2', - transformIgnorePatterns: ['/node_modules/(?!(true-myth|p-timeout|p-cancelable|noop-esm|uuid)/)'], + transformIgnorePatterns: ['/node_modules/(?!(true-myth|p-timeout|p-cancelable|noop-esm|uuid|@enormora/objectory)/)'], // Override transform to use babel-jest for webapp (uses React automatic runtime with Emotion) transform: { '^.+\\.[tj]sx?$': 'babel-jest', diff --git a/apps/webapp/jest.report.config.cjs b/apps/webapp/jest.report.config.cjs index a3cb02ca51e..1656c0b9d2a 100644 --- a/apps/webapp/jest.report.config.cjs +++ b/apps/webapp/jest.report.config.cjs @@ -41,7 +41,7 @@ module.exports = { }, testPathIgnorePatterns: ['/server', '/.yalc', '/test/e2e_tests'], testRunner: 'jest-jasmine2', - transformIgnorePatterns: ['/node_modules/(?!(true-myth|p-timeout|p-cancelable|noop-esm|uuid)/)'], + transformIgnorePatterns: ['/node_modules/(?!(true-myth|p-timeout|p-cancelable|noop-esm|uuid|@enormora/objectory)/)'], transform: { '^.+\\.[tj]sx?$': 'babel-jest', }, diff --git a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.test.ts b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.test.ts new file mode 100644 index 00000000000..a3e7523058a --- /dev/null +++ b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.test.ts @@ -0,0 +1,115 @@ +import {Maybe} from 'true-myth'; +import {areCapabilityInfosEqual} from 'Components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator'; +import {createFactory} from '@enormora/objectory'; +import {CapabilityInfo} from 'Repositories/media/backgroundEffects/backgroundEffectsWorkerTypes'; + +const capabilityInfoFactory = createFactory(() => { + return { + offscreenCanvas: false, + worker: false, + webgl2: false, + requestVideoFrameCallback: false, + }; +}); + +describe('areCapabilityInfosEqual', () => { + it('returns false when initialCapabilityInfo is a Nothing', () => { + const initialCapabilityInfo = Maybe.nothing(); + const futureCapabilityInfo = Maybe.just(capabilityInfoFactory.build()); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(false); + }); + + it('returns false when futureCapabilityInfo is a Nothing', () => { + const initialCapabilityInfo = Maybe.just(capabilityInfoFactory.build()); + const futureCapabilityInfo = Maybe.nothing(); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(false); + }); + + it('returns false when both capability infos are Nothing', () => { + const initialCapabilityInfo = Maybe.nothing(); + const futureCapabilityInfo = Maybe.nothing(); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(false); + }); + + it.each` + property | initialValue | futureValue + ${'webgl2'} | ${false} | ${true} + ${'webgl2'} | ${true} | ${false} + ${'worker'} | ${false} | ${true} + ${'worker'} | ${true} | ${false} + ${'offscreenCanvas'} | ${false} | ${true} + ${'offscreenCanvas'} | ${true} | ${false} + ${'requestVideoFrameCallback'} | ${false} | ${true} + ${'requestVideoFrameCallback'} | ${true} | ${false} + `( + 'returns false when $property changes from $initialValue to $futureValue', + ({property, initialValue, futureValue}) => { + const initialCapabilityInfo = Maybe.just( + capabilityInfoFactory.build({ + [property]: initialValue, + }), + ); + + const futureCapabilityInfo = Maybe.just( + capabilityInfoFactory.build({ + [property]: futureValue, + }), + ); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(false); + }, + ); + + it.each` + capabilityInfo + ${capabilityInfoFactory.build()} + ${capabilityInfoFactory.build({ + offscreenCanvas: true, + worker: true, + webgl2: true, + requestVideoFrameCallback: true, +})} + ${capabilityInfoFactory.build({ + offscreenCanvas: true, + worker: false, + webgl2: true, + requestVideoFrameCallback: false, +})} + ${capabilityInfoFactory.build({ + offscreenCanvas: false, + worker: true, + webgl2: false, + requestVideoFrameCallback: true, +})} + `('returns true when all attributes are equal: $capabilityInfo', ({capabilityInfo}) => { + const initialCapabilityInfo = Maybe.just(capabilityInfo); + const futureCapabilityInfo = Maybe.just({...capabilityInfo}); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(true); + }); + + it('returns false when multiple attributes are different', () => { + const initialCapabilityInfo = Maybe.just( + capabilityInfoFactory.build({ + offscreenCanvas: false, + worker: false, + webgl2: false, + requestVideoFrameCallback: false, + }), + ); + + const futureCapabilityInfo = Maybe.just( + capabilityInfoFactory.build({ + offscreenCanvas: true, + worker: true, + webgl2: true, + requestVideoFrameCallback: true, + }), + ); + + expect(areCapabilityInfosEqual(initialCapabilityInfo, futureCapabilityInfo)).toBe(false); + }); +}); diff --git a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.ts b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.ts new file mode 100644 index 00000000000..f882aa415e7 --- /dev/null +++ b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator.ts @@ -0,0 +1,40 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {Maybe} from 'true-myth'; + +import {CapabilityInfo} from 'Repositories/media/backgroundEffects/backgroundEffectsWorkerTypes'; + +// Business logic for checking if the capability info has changed +const capabilityComparator = (initialCapabilityInfo: CapabilityInfo) => (futureCapabilityInfo: CapabilityInfo) => { + return ( + initialCapabilityInfo.webgl2 === futureCapabilityInfo.webgl2 && + initialCapabilityInfo.worker === futureCapabilityInfo.worker && + initialCapabilityInfo.offscreenCanvas === futureCapabilityInfo.offscreenCanvas && + initialCapabilityInfo.requestVideoFrameCallback === futureCapabilityInfo.requestVideoFrameCallback + ); +}; + +// Guard for checking if the capability info has changed +export const areCapabilityInfosEqual = ( + initialCapabilityInfo: Maybe, + futureCapabilityInfo: Maybe, +): boolean => { + return Maybe.just(capabilityComparator).ap(initialCapabilityInfo).ap(futureCapabilityInfo).unwrapOr(false); +}; diff --git a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx index d939b878a98..01ea5f779bf 100644 --- a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx +++ b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx @@ -23,6 +23,7 @@ import {Maybe} from 'true-myth'; import {Button, ButtonVariant, CloseIcon, Option, Select} from '@wireapp/react-ui-kit'; +import {areCapabilityInfosEqual} from 'Components/calling/VideoControls/videoBackgroundPerformancePanel/capabilityInformationValidator'; import { buttonBaseStyles, buttonNeutralStyles, @@ -133,6 +134,11 @@ const MetricsDisplay = ({capabilityInfo}: MetricsDisplayProps) => { ); }; +const qualitySelectOptions = QUALITY_OPTIONS.map(option => ({ + label: option, + value: option, +})); + export const VideoBackgroundPerformancePanel = ({backgroundEffectsHandler}: PerformancePanelProps) => { const isFeatureEnabled = useBackgroundEffectsStore(state => state.isFeatureEnabled); @@ -140,33 +146,57 @@ export const VideoBackgroundPerformancePanel = ({backgroundEffectsHandler}: Perf const [isPanelOpen, setIsPanelOpen] = useState(false); const [capabilityInfo, setCapabilityInfo] = useState(null); - const qualitySelectOptions = useMemo( - () => - QUALITY_OPTIONS.map(option => ({ - label: option, - value: option, - })), - [], - ); - const selectedOption = useMemo( () => qualitySelectOptions.find(option => option.value === selectedQuality) ?? null, - [qualitySelectOptions, selectedQuality], + [selectedQuality], ); useEffect(() => { + if (!isFeatureEnabled || !isPanelOpen) { + setCapabilityInfo(null); + return; + } + setCapabilityInfo(backgroundEffectsHandler.getCapabilityInfo()); - }, [backgroundEffectsHandler]); + }, [backgroundEffectsHandler, isFeatureEnabled, isPanelOpen]); // Quality polling (fallback for non-reactive quality) - useEffect(() => { + useEffect((): void | (() => void) => { + if (!isFeatureEnabled || !isPanelOpen) { + return; + } + const interval = setInterval(() => { const current = backgroundEffectsHandler.getQuality(); + setSelectedQuality(prev => (prev !== current ? current : prev)); }, POLLING_INTERVAL); - return () => clearInterval(interval); - }, [backgroundEffectsHandler]); + return () => { + clearInterval(interval); + }; + }, [backgroundEffectsHandler, isFeatureEnabled, isPanelOpen]); + + // Capability polling (controller updates these after pipeline start) + useEffect((): void | (() => void) => { + if (!isFeatureEnabled || !isPanelOpen) { + setCapabilityInfo(null); + return; + } + + const syncCapabilities = () => { + const current = backgroundEffectsHandler.getCapabilityInfo(); + setCapabilityInfo(prev => (areCapabilityInfosEqual(Maybe.of(prev), Maybe.of(current)) ? prev : current)); + }; + + syncCapabilities(); + + const interval = setInterval(syncCapabilities, POLLING_INTERVAL); + + return () => { + clearInterval(interval); + }; + }, [backgroundEffectsHandler, isFeatureEnabled, isPanelOpen]); // Auto close if disabled useEffect(() => { @@ -175,21 +205,9 @@ export const VideoBackgroundPerformancePanel = ({backgroundEffectsHandler}: Perf } }, [isFeatureEnabled, isPanelOpen]); - const handleOpenPanel = useCallback(() => { - setIsPanelOpen(true); - }, []); - - const handleClosePanel = useCallback(() => { - setIsPanelOpen(false); - }, []); - - const togglePerformancePanel = useCallback(() => { - if (isPanelOpen) { - handleClosePanel(); - } else { - handleOpenPanel(); - } - }, [handleClosePanel, handleOpenPanel, isPanelOpen]); + const togglePerformancePanel = () => { + setIsPanelOpen(prev => !prev); + }; const handleQualityChange = useCallback( (quality: Option) => { @@ -234,7 +252,7 @@ export const VideoBackgroundPerformancePanel = ({backgroundEffectsHandler}: Perf type="button" className="icon-button" css={performancePanelCloseButtonStyles} - onClick={handleClosePanel} + onClick={togglePerformancePanel} aria-label={t('modalCloseButton')} > diff --git a/apps/webapp/src/script/repositories/media/backgroundEffects/backgroundEffectsWorkerTypes.ts b/apps/webapp/src/script/repositories/media/backgroundEffects/backgroundEffectsWorkerTypes.ts index 946e6dabc13..a45d322ccc8 100644 --- a/apps/webapp/src/script/repositories/media/backgroundEffects/backgroundEffectsWorkerTypes.ts +++ b/apps/webapp/src/script/repositories/media/backgroundEffects/backgroundEffectsWorkerTypes.ts @@ -66,12 +66,12 @@ export type SegmentationModelByTier = Partial>; /** * Browser capability information detected at runtime. */ -export interface CapabilityInfo { +export type CapabilityInfo = { offscreenCanvas: boolean; worker: boolean; webgl2: boolean; requestVideoFrameCallback: boolean; -} +}; export type QualityPolicyMode = 'auto' | 'conservative' | 'aggressive'; diff --git a/package.json b/package.json index eaea19a3ac6..d873312691b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "zod": "3.25.76" }, "devDependencies": { + "@enormora/objectory": "0.0.7", "@faker-js/faker": "9.9.0", "@ls-lint/ls-lint": "2.3.1", "@nx/jest": "22.7.0", diff --git a/yarn.lock b/yarn.lock index 6e048e6f285..4ed55e32994 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3420,6 +3420,13 @@ __metadata: languageName: node linkType: hard +"@enormora/objectory@npm:0.0.7": + version: 0.0.7 + resolution: "@enormora/objectory@npm:0.0.7" + checksum: 10/ecf200672c95f26e2aae19788cffee701bd171d1b0cf00e99abf03d6868c1d421046665127093e57c525045946dbae1f0484dde15c482af2bdf2b7841cddf26b + languageName: node + linkType: hard + "@epic-web/invariant@npm:^1.0.0": version: 1.0.0 resolution: "@epic-web/invariant@npm:1.0.0" @@ -27451,6 +27458,7 @@ __metadata: version: 0.0.0-use.local resolution: "wire-webapp@workspace:." dependencies: + "@enormora/objectory": "npm:0.0.7" "@faker-js/faker": "npm:9.9.0" "@ls-lint/ls-lint": "npm:2.3.1" "@nx/jest": "npm:22.7.0" From 19c9942f73be60cd3ebac497f5f3c74da2b276bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 07:03:48 +0000 Subject: [PATCH 17/49] chore(deps): update dependency jsdom to v29.1.0 --- package.json | 2 +- yarn.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index d873312691b..bdfcd5e079a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "jest": "30.3.0", "jest-environment-jsdom": "30.3.0", "js-yaml": "4.1.1", - "jsdom": "29.0.2", + "jsdom": "29.1.0", "nock": "14.0.13", "nx": "22.7.0", "playwright": "1.59.1", diff --git a/yarn.lock b/yarn.lock index 4ed55e32994..0051570afd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,7 +45,7 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/css-color@npm:^5.1.5": +"@asamuzakjp/css-color@npm:^5.1.11": version: 5.1.11 resolution: "@asamuzakjp/css-color@npm:5.1.11" dependencies: @@ -58,7 +58,7 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/dom-selector@npm:^7.0.6": +"@asamuzakjp/dom-selector@npm:^7.1.1": version: 7.1.1 resolution: "@asamuzakjp/dom-selector@npm:7.1.1" dependencies: @@ -2585,7 +2585,7 @@ __metadata: languageName: node linkType: hard -"@csstools/css-syntax-patches-for-csstree@npm:^1.1.1, @csstools/css-syntax-patches-for-csstree@npm:^1.1.3": +"@csstools/css-syntax-patches-for-csstree@npm:^1.1.3": version: 1.1.3 resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.3" peerDependencies: @@ -18475,26 +18475,26 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:29.0.2": - version: 29.0.2 - resolution: "jsdom@npm:29.0.2" +"jsdom@npm:29.1.0": + version: 29.1.0 + resolution: "jsdom@npm:29.1.0" dependencies: - "@asamuzakjp/css-color": "npm:^5.1.5" - "@asamuzakjp/dom-selector": "npm:^7.0.6" + "@asamuzakjp/css-color": "npm:^5.1.11" + "@asamuzakjp/dom-selector": "npm:^7.1.1" "@bramus/specificity": "npm:^2.4.2" - "@csstools/css-syntax-patches-for-csstree": "npm:^1.1.1" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.1.3" "@exodus/bytes": "npm:^1.15.0" css-tree: "npm:^3.2.1" data-urls: "npm:^7.0.0" decimal.js: "npm:^10.6.0" html-encoding-sniffer: "npm:^6.0.0" is-potential-custom-element-name: "npm:^1.0.1" - lru-cache: "npm:^11.2.7" - parse5: "npm:^8.0.0" + lru-cache: "npm:^11.3.5" + parse5: "npm:^8.0.1" saxes: "npm:^6.0.0" symbol-tree: "npm:^3.2.4" tough-cookie: "npm:^6.0.1" - undici: "npm:^7.24.5" + undici: "npm:^7.25.0" w3c-xmlserializer: "npm:^5.0.0" webidl-conversions: "npm:^8.0.1" whatwg-mimetype: "npm:^5.0.0" @@ -18505,7 +18505,7 @@ __metadata: peerDependenciesMeta: canvas: optional: true - checksum: 10/3ad1d9a5b6aba067427bc43be98e1c51fab489bf689a6530e596278c6326fe053c94fc47a9c133f126fbe914f421283ae723fb92214dfe4959ca6cf2ee1666f6 + checksum: 10/91194be30c10b518c8e21c3a15cf1ef0e6c74722fb8fe402f201efed3e5de55f13005f2a4108d02ccde75d1e16719ca898690be93cd22d498a4c062d035adae5 languageName: node linkType: hard @@ -19284,7 +19284,7 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.2.7": +"lru-cache@npm:^11.3.5": version: 11.3.5 resolution: "lru-cache@npm:11.3.5" checksum: 10/3701b77e87765a3aea453402a7850bdbf7e02445210f35bd5ba1561f601f605f488bf9932be4a3851a6664073924f671a1ec99c4a1a98c457e0d126872a3e04f @@ -20979,7 +20979,7 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^8.0.0": +"parse5@npm:^8.0.1": version: 8.0.1 resolution: "parse5@npm:8.0.1" dependencies: @@ -26408,7 +26408,7 @@ __metadata: languageName: node linkType: hard -"undici@npm:^7.24.5": +"undici@npm:^7.25.0": version: 7.25.0 resolution: "undici@npm:7.25.0" checksum: 10/038d3568c72bb976e3cc389284f7f1cc64cd70d578300e4676a449fbcb624a35fe99ac127b5f3729f18b8246d6c090444ab61b1b67736bb88f52a3e913d76bf8 @@ -27509,7 +27509,7 @@ __metadata: jest: "npm:30.3.0" jest-environment-jsdom: "npm:30.3.0" js-yaml: "npm:4.1.1" - jsdom: "npm:29.0.2" + jsdom: "npm:29.1.0" logdown: "npm:3.3.1" long: "npm:5.3.2" nock: "npm:14.0.13" From 67f7e712d2225a46605569b8cf4bdae2e1322691 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:07:45 +0200 Subject: [PATCH 18/49] test: remove page plugin "withConnectionRequest" to prevent race conditions during login [WPB-25038] (#21175) * refactor: overload sendConnectionRequest to also accept pages * refactor: overload connectWithUser to also accept pages * refactor: remove usages of withConnectionRequest from block tests * refactor: remove usages of withConnectionRequest from calling tests Co-authored-by: Copilot * refactor: remove usages of withConnectionRequest from connection tests Co-authored-by: Copilot * refactor: remove usages of withConnectionRequest from conversation tests Co-authored-by: Copilot * refactor: remove usages of withConnectionRequest from groupVideoCall test * refactor: remove usages of withConnectionRequest from messagesIn1On1 test * refactor: remove usages of withConnectionRequest from oneOnOneCall test * refactor: remove usages of withConnectionRequest from deepLink tests * refactor: remove usages of withConnectionRequest from guestroom tests * refactor: remove usages of withConnectionRequest from mention tests * refactor: remove usages of withConnectionRequest from participantProfile tests Co-authored-by: Copilot * refactor: remove page plugin "withConnectionRequest" --------- Co-authored-by: Copilot --- .../test/e2e_tests/specs/Block/block.spec.ts | 59 +++++++++---------- .../e2e_tests/specs/Calling/calling.spec.ts | 17 +++--- .../specs/Connections/Connections.spec.ts | 34 +++++++---- .../specs/Conversations/conversations.spec.ts | 50 ++++++++-------- .../groupVideoCall-TC-8637.spec.ts | 7 ++- .../messagesIn1On1-TC-8750.spec.ts | 7 ++- .../CriticalFlow/oneOnOneCall-TC-8754.spec.ts | 6 +- .../specs/DeepLinks/deepLinks.spec.ts | 10 ++-- .../specs/Guestroom/guestroom.spec.ts | 23 +++----- .../e2e_tests/specs/Mention/mention.spec.ts | 20 ++----- .../participantProfile.spec.ts | 34 ++++++----- apps/webapp/test/e2e_tests/test.fixtures.ts | 13 +--- .../test/e2e_tests/utils/userActions.ts | 10 ++-- 13 files changed, 140 insertions(+), 150 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts index d68a58274a1..c5f100bb350 100644 --- a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts @@ -19,9 +19,9 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, expect, withLogin, withConnectionRequest, withConnectedUser} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser} from 'test/e2e_tests/test.fixtures'; -import {createGroup} from '../../utils/userActions'; +import {createGroup, sendConnectionRequest} from '../../utils/userActions'; /* ========== Block Utils ========== */ /** @@ -83,9 +83,9 @@ test.describe('User Blocking', () => { 'I want to cancel blocking a 1:1 conversation from conversation list', {tag: ['@TC-137', '@regression']}, async ({createPage}) => { - const userAPageManager = (await PageManager.from(createPage(withLogin(userA), withConnectionRequest(userB)))) - .webapp; - const {pages: userAPages, modals: userAModals} = userAPageManager; + const userAPageManager = await PageManager.from(createPage(withLogin(userA))); + await sendConnectionRequest(userAPageManager, userB); + const {pages: userAPages, modals: userAModals} = userAPageManager.webapp; // Preconditions: User B accepts the connection request const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; @@ -113,9 +113,9 @@ test.describe('User Blocking', () => { 'Verify you can block a user who is not in your team', {tag: ['@TC-140', '@regression']}, async ({createPage}) => { - const userAPageManager = (await PageManager.from(createPage(withLogin(userA), withConnectionRequest(userB)))) - .webapp; - const {pages: userAPages, modals: userAModals} = userAPageManager; + const userAPageManager = await PageManager.from(createPage(withLogin(userA))); + await sendConnectionRequest(userAPageManager, userB); + const {pages: userAPages, modals: userAModals} = userAPageManager.webapp; // Preconditions: User B accepts the connection request const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; @@ -155,15 +155,19 @@ test.describe('User Blocking', () => { 'Verify you still receive messages from blocked person in a group chat', {tag: ['@TC-141', '@regression']}, async ({createPage}) => { - const userAPageManager = await PageManager.from(createPage(withLogin(userA), withConnectionRequest(userB))); - const userAPages = userAPageManager.webapp.pages; - - const conversationName = 'GroupConversation'; + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userB))), + ]); + const [userAPages, userBPages] = [userAPageManager, userBPageManager].map(pm => pm.webapp.pages); // Preconditions: User B accepts the connection request - const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; + await sendConnectionRequest(userAPageManager, userB); + await userBPages.conversationList().openPendingConnectionRequest(); await userBPages.connectRequest().clickConnectButton(); + const conversationName = 'GroupConversation'; + await expect(userAPages.conversationList().getConversationLocator(userB.fullName)).toBeAttached(); await createGroup(userAPages, conversationName, [userB]); @@ -195,11 +199,9 @@ test.describe('User Blocking', () => { {tag: ['@TC-142', '@regression']}, async ({createPage}) => { - const userAPageManagerInstance = await PageManager.from( - createPage(withLogin(userA), withConnectionRequest(userB)), - ); - const userAPageManager = userAPageManagerInstance.webapp; - const {pages: userAPages, modals: userAModals} = userAPageManager; + const userAPageManagerInstance = await PageManager.from(createPage(withLogin(userA))); + await sendConnectionRequest(userAPageManagerInstance, userB); + const {pages: userAPages, modals: userAModals} = userAPageManagerInstance.webapp; // Preconditions: User B accepts the connection request const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; @@ -236,11 +238,9 @@ test.describe('User Blocking', () => { 'Verify you cannot add a person who blocked you to a group chat', {tag: ['@TC-143', '@regression']}, async ({createPage}) => { - const userAPageManagerInstance = await PageManager.from( - createPage(withLogin(userA), withConnectedUser(userC), withConnectionRequest(userB)), - ); - const userAPageManager = userAPageManagerInstance.webapp; - const {pages: userAPages, modals: userAModals} = userAPageManager; + const userAPageManagerInstance = await PageManager.from(createPage(withLogin(userA), withConnectedUser(userC))); + await sendConnectionRequest(userAPageManagerInstance, userB); + const {pages: userAPages, modals: userAModals} = userAPageManagerInstance.webapp; const conversationName = 'Group Conversation'; @@ -276,9 +276,8 @@ test.describe('User Blocking', () => { 'Verify you can block a user you sent a connection request from conversation list', {tag: ['@TC-144', '@regression']}, async ({createPage}) => { - const userAPageManagerInstance = await PageManager.from( - createPage(withLogin(userA), withConnectionRequest(userB)), - ); + const userAPageManagerInstance = await PageManager.from(createPage(withLogin(userA))); + await sendConnectionRequest(userAPageManagerInstance, userB); // Preconditions: User B does not accept connection request const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; @@ -294,11 +293,9 @@ test.describe('User Blocking', () => { 'Verify you can unblock someone from conversation list options', {tag: ['@TC-148', '@regression']}, async ({createPage}) => { - const userAPageManagerInstance = await PageManager.from( - createPage(withLogin(userA), withConnectedUser(userC), withConnectionRequest(userB)), - ); - const userAPageManager = userAPageManagerInstance.webapp; - const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager; + const userAPageManagerInstance = await PageManager.from(createPage(withLogin(userA), withConnectedUser(userC))); + await sendConnectionRequest(userAPageManagerInstance, userB); + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManagerInstance.webapp; // Preconditions: User B accepts the connection request const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; diff --git a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts index cc586403e56..8713ff4835b 100644 --- a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts @@ -20,9 +20,9 @@ import {AudioType} from 'Repositories/audio/audioType'; import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, withConnectedUser, withLogin, expect, Team, withConnectionRequest} from 'test/e2e_tests/test.fixtures'; +import {test, withConnectedUser, withLogin, expect, Team} from 'test/e2e_tests/test.fixtures'; import {isPlayingAudio} from 'test/e2e_tests/utils/audio.util'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test.describe('Calling', () => { let userA: User; @@ -477,13 +477,14 @@ test.describe('Calling', () => { {tag: ['@TC-2844', '@regression']}, async ({createPage, createUser}) => { const guestUser = await createUser(); - const [userAPages, userBPages, guestPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectedUser(userB), withConnectionRequest(guestUser))).then( - pm => pm.webapp.pages, - ), - PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + const [userAPage, userBPage, guestPage] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB))), + PageManager.from(createPage(withLogin(guestUser))), ]); + await sendConnectionRequest(userAPage, guestUser); + + const [userAPages, userBPages, guestPages] = [userAPage, userBPage, guestPage].map(pm => pm.webapp.pages); // --- Setup and Call Initialization --- await test.step('Setup: Accept connection and start group call', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts index eaefdd58418..ad417929729 100644 --- a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts @@ -20,8 +20,8 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, expect, withLogin, withConnectionRequest} from '../../test.fixtures'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {test, expect, withLogin} from '../../test.fixtures'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test.describe('Connections', () => { let memberA: User; @@ -35,10 +35,11 @@ test.describe('Connections', () => { 'Verify 1on1 conversation is not created on the second end after you ignore connection request', {tag: ['@TC-365', '@regression']}, async ({createPage}) => { - const [memberBPages] = await Promise.all([ + const [memberBPages, memberAPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(memberB))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(memberA), withConnectionRequest(memberB))), + PageManager.from(createPage(withLogin(memberA))), ]); + await sendConnectionRequest(memberAPageManager, memberB); await memberBPages.conversationList().pendingConnectionRequest.click(); await memberBPages.conversation().clickIgnoreButton(); @@ -52,13 +53,17 @@ test.describe('Connections', () => { {tag: ['@TC-369', '@regression']}, async ({createPage, createUser}) => { const memberC = await createUser(); - const [memberAPages, memberBPages, memberCPages] = await Promise.all([ - PageManager.from( - createPage(withLogin(memberA), withConnectionRequest(memberB), withConnectionRequest(memberC)), - ).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(memberB))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(memberC))).then(pm => pm.webapp.pages), + const [memberAPage, memberBPage, memberCPage] = await Promise.all([ + PageManager.from(createPage(withLogin(memberA))), + PageManager.from(createPage(withLogin(memberB))), + PageManager.from(createPage(withLogin(memberC))), ]); + await sendConnectionRequest(memberAPage, memberB); + await sendConnectionRequest(memberAPage, memberC); + + const [memberAPages, memberBPages, memberCPages] = [memberAPage, memberBPage, memberCPage].map( + page => page.webapp.pages, + ); await test.step('B & C accept connection requests from A', async () => { for (const pages of [memberBPages, memberCPages]) { @@ -91,10 +96,11 @@ test.describe('Connections', () => { 'I want to cancel a pending request from conversation list', {tag: ['@TC-370', '@regression']}, async ({createPage}) => { - const [memberBPages] = await Promise.all([ + const [memberBPages, memberAPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(memberB))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(memberA), withConnectionRequest(memberB))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(memberA))), ]); + await sendConnectionRequest(memberAPageManager, memberB); await memberBPages.conversationList().pendingConnectionRequest.click(); await memberBPages.conversation().ignoreButton.click(); @@ -107,8 +113,10 @@ test.describe('Connections', () => { 'I want to archive a pending request from conversation list', {tag: ['@TC-371', '@regression']}, async ({createPage}) => { - const {pages} = PageManager.from(await createPage(withLogin(memberA), withConnectionRequest(memberB))).webapp; + const pageManager = PageManager.from(await createPage(withLogin(memberA))); + await sendConnectionRequest(pageManager, memberB); + const {pages} = pageManager.webapp; const contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); await contextMenu.archiveButton.click(); diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index 6887ca117c5..d7cff637b69 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -19,17 +19,9 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import { - test, - expect, - withConnectedUser, - withLogin, - Team, - withConnectionRequest, - LOGIN_TIMEOUT, -} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withConnectedUser, withLogin, Team, LOGIN_TIMEOUT} from 'test/e2e_tests/test.fixtures'; import {interceptNotifications} from 'test/e2e_tests/utils/mockNotifications.util'; -import {connectWithUser, createGroup} from 'test/e2e_tests/utils/userActions'; +import {connectWithUser, createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test.describe('Conversations', () => { let team: Team; @@ -135,20 +127,23 @@ test.describe('Conversations', () => { {tag: ['@TC-421', '@regression']}, async ({createPage, createUser}) => { const guestUser = await createUser(); - const [adminPage, guestPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + const [adminPage, guestPage] = await Promise.all([ + createPage(withLogin(userA)), + createPage(withLogin(guestUser)), ]); + await sendConnectionRequest(adminPage, guestUser); + + const [adminPages, guestPages] = [adminPage, guestPage].map(page => PageManager.from(page).webapp.pages); await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(adminPage.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(adminPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); - await createGroup(adminPage, groupName, [userB, guestUser]); + await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPage.conversationList().openConversation(groupName); - await adminPage.conversation().clickConversationInfoButton(); - await expect(adminPage.conversationDetails().getUserRoleIcon(guestUser.fullName)).toHaveAttribute( + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + await expect(adminPages.conversationDetails().getUserRoleIcon(guestUser.fullName)).toHaveAttribute( 'data-uie-name', 'status-guest', ); @@ -161,21 +156,24 @@ test.describe('Conversations', () => { async ({createPage, createUser}) => { const guestUser = await createUser(); - const [adminPage, guestPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + const [adminPage, guestPage] = await Promise.all([ + createPage(withLogin(userA)), + createPage(withLogin(guestUser)), ]); + await sendConnectionRequest(adminPage, guestUser); + + const [adminPages, guestPages] = [adminPage, guestPage].map(page => PageManager.from(page).webapp.pages); await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(adminPage.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(adminPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); - await createGroup(adminPage, groupName, [userB, guestUser]); + await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPage.conversationList().openConversation(groupName); - await adminPage.conversation().clickConversationInfoButton(); + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); - await expect(adminPage.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); + await expect(adminPages.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); }, ); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts index 82254dfecd8..ae64e33ab1c 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts @@ -18,8 +18,8 @@ */ import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, expect, withLogin, withConnectionRequest} from '../../test.fixtures'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {test, expect, withLogin} from '../../test.fixtures'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTeam, createUser, createPage, api}) => { test.setTimeout(150_000); @@ -32,9 +32,10 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe const teamOwner = team.owner; const [ownerPageManager, guestPageManager] = await Promise.all([ - PageManager.from(createPage(withLogin(teamOwner), withConnectionRequest(guestUser))), + PageManager.from(createPage(withLogin(teamOwner))), PageManager.from(createPage(withLogin(guestUser))), ]); + await sendConnectionRequest(ownerPageManager, guestUser); const ownerPages = ownerPageManager.webapp.pages; const guestPages = guestPageManager.webapp.pages; diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts index a8248864200..3e41e43a736 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts @@ -21,7 +21,8 @@ import {PageManager} from 'test/e2e_tests/pageManager'; import {getVideoFilePath, getAudioFilePath, getTextFilePath, isAssetDownloaded} from 'test/e2e_tests/utils/asset.util'; import {getImageFilePath, getLocalQRCodeValue, getQRCodeValueFromScreenshot} from 'test/e2e_tests/utils/sendImage.util'; -import {test, expect, withLogin, withConnectionRequest} from '../../test.fixtures'; +import {test, expect, withLogin} from '../../test.fixtures'; +import {sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; const imageFilePath = getImageFilePath(); const videoFilePath = getVideoFilePath(); @@ -36,9 +37,11 @@ test('Messages in 1:1', {tag: ['@TC-8750', '@crit-flow-web']}, async ({createTea // Create page managers - User A sends connection request to User B const [memberAPage, memberBPage] = await Promise.all([ - createPage(withLogin(memberA), withConnectionRequest(memberB)), + createPage(withLogin(memberA)), createPage(withLogin(memberB)), ]); + await sendConnectionRequest(memberAPage, memberB); + const [memberAPageManager, memberBPageManager] = [PageManager.from(memberAPage), PageManager.from(memberBPage)]; // Step 1-1: Preconditions diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts index 72c40929a52..cc089d7d86a 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts @@ -19,7 +19,8 @@ import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, expect, withLogin, withConnectionRequest} from '../../test.fixtures'; +import {test, expect, withLogin} from '../../test.fixtures'; +import {sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test( '1:1 Video call with device switch and screenshare', @@ -30,9 +31,10 @@ test( const [{owner: userA}, {owner: userB}] = await Promise.all([createTeam('User A Team'), createTeam('User B Team')]); const {id: callingServiceInstanceId} = await api.callingService.createInstance(userB.password, userB.email); const [userAPageManager, userBPageManager] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(userB))), + PageManager.from(createPage(withLogin(userA))), PageManager.from(createPage(withLogin(userB, {confirmNewHistory: true}))), ]); + await sendConnectionRequest(userAPageManager, userB); await test.step('User B accepts connection request from User A', async () => { const {pages} = userBPageManager.webapp; diff --git a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts index 7d0cf7a71a9..d286b5ea513 100644 --- a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts @@ -19,8 +19,8 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {expect, test, Team, withConnectedUser, withConnectionRequest, withLogin} from 'test/e2e_tests/test.fixtures'; -import {createGroup} from '../../utils/userActions'; +import {expect, test, Team, withConnectedUser, withLogin} from 'test/e2e_tests/test.fixtures'; +import {createGroup, sendConnectionRequest} from '../../utils/userActions'; import {UserProfileModal} from '../../pageManager/webapp/modals/userProfile.modal'; type ProfileModalOptions = { @@ -117,11 +117,13 @@ test.describe('Deep Links', () => { } const [userAPage, userBPage, userCPage, userDPage] = await Promise.all([ - createPage(withLogin(userA), withConnectionRequest(userC)), - createPage(withLogin(userB), withConnectedUser(userA), withConnectionRequest(userD)), + createPage(withLogin(userA)), + createPage(withLogin(userB), withConnectedUser(userA)), createPage(withLogin(userC)), createPage(withLogin(userD)), ]); + await sendConnectionRequest(userAPage, userC); + await sendConnectionRequest(userBPage, userD); const userAPageManager = PageManager.from(userAPage); const userBPageManager = PageManager.from(userBPage); diff --git a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts index 390b59e299a..409c6d7fab2 100644 --- a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts @@ -1,16 +1,8 @@ import {Page} from 'playwright/test'; import {getUser, User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import { - test, - expect, - withLogin, - Team, - LOGIN_TIMEOUT, - withGuestUser, - withConnectionRequest, -} from 'test/e2e_tests/test.fixtures'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {test, expect, withLogin, Team, LOGIN_TIMEOUT, withGuestUser} from 'test/e2e_tests/test.fixtures'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; /** * Navigates through the UI to generate a guest invitation link for a specific group. @@ -177,12 +169,13 @@ test.describe('Guestroom', () => { 'I should not see guests when adding people to existing conversation when guest toggle is OFF', {tag: ['@TC-3318', '@regression']}, async ({createPage}) => { - const [ownerPages, guestPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then( - ({webapp}) => webapp.pages, - ), - PageManager.from(createPage(withLogin(guestUser))).then(({webapp}) => webapp.pages), + const [ownerPage, guestPage] = await Promise.all([ + createPage(withLogin(userA)), + createPage(withLogin(guestUser)), ]); + await sendConnectionRequest(ownerPage, guestUser); + + const [ownerPages, guestPages] = [ownerPage, guestPage].map(page => PageManager.from(page).webapp.pages); await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index ebd4854a8b5..a1b7afb89c8 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -1,15 +1,7 @@ -import { - test, - expect, - withLogin, - withConnectedUser, - Team, - withGuestUser, - withConnectionRequest, -} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser, Team, withGuestUser} from 'test/e2e_tests/test.fixtures'; import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test.describe('Mention', () => { let team: Team; @@ -517,10 +509,10 @@ test.describe('Mention', () => { const otherUser = await createUser(); const otherUserPages = await PageManager.from(createPage(withLogin(otherUser))).then(pm => pm.webapp.pages); - const [userAPages, userBPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(otherUser))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), - ]); + const [userAPage, userBPage] = await Promise.all([createPage(withLogin(userA)), createPage(withLogin(userB))]); + await sendConnectionRequest(userAPage, otherUser); + + const [userAPages, userBPages] = [userAPage, userBPage].map(page => PageManager.from(page).webapp.pages); await otherUserPages.conversationList().pendingConnectionRequest.click(); await otherUserPages.connectRequest().connectButton.click(); diff --git a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts index 66663d0d0fd..67b90c2a93e 100644 --- a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts @@ -19,9 +19,9 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; -import {test, expect, withConnectedUser, withLogin, withConnectionRequest, Team} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withConnectedUser, withLogin, Team} from 'test/e2e_tests/test.fixtures'; async function openParticipantDetailsFromGroup( pages: PageManager['webapp']['pages'], @@ -56,11 +56,10 @@ test.describe('Participant Profile', () => { {tag: ['@TC-1474', '@regression']}, async ({createPage, createUser}) => { const userC = await createUser(); - const [userAPages, userCPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(userC))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(userC))).then(pm => pm.webapp.pages), - ]); + const [userAPage, userCPage] = await Promise.all([createPage(withLogin(userA)), createPage(withLogin(userC))]); + const [userAPages, userCPages] = [userAPage, userCPage].map(page => PageManager.from(page).webapp.pages); + await sendConnectionRequest(userAPage, userC); await acceptConnectionRequest(userCPages); await test.step('Go to any 1:1 conversation', async () => { @@ -92,12 +91,16 @@ test.describe('Participant Profile', () => { {tag: ['@TC-1477', '@regression']}, async ({createPage, createUser}) => { const userC = await createUser(); - const [userAPages, userBPages, userCPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(userC))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(userC))).then(pm => pm.webapp.pages), + const [userAPage, userBPage, userCPage] = await Promise.all([ + createPage(withLogin(userA)), + createPage(withLogin(userB), withConnectedUser(userA)), + createPage(withLogin(userC)), ]); + const [userAPages, userBPages, userCPages] = [userAPage, userBPage, userCPage].map( + page => PageManager.from(page).webapp.pages, + ); + await sendConnectionRequest(userAPage, userC); await acceptConnectionRequest(userCPages); await expect(userAPages.conversationList().getConversationLocator(userC.fullName)).toBeAttached(); @@ -133,13 +136,14 @@ test.describe('Participant Profile', () => { {tag: ['@TC-1479', '@regression']}, async ({createPage, createUser}) => { const userC = await createUser(); - const [userAPageManager, userCPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(userC))), - PageManager.from(createPage(withLogin(userC))).then(pm => pm.webapp.pages), + const [userAPageManager, userCPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userC))), ]); - const {pages, modals} = userAPageManager.webapp; - await acceptConnectionRequest(userCPages); + + await sendConnectionRequest(userAPageManager, userC); + await acceptConnectionRequest(userCPageManager.webapp.pages); await expect(pages.conversationList().getConversationLocator(userC.fullName)).toBeAttached(); await createGroup(pages, groupName, [userB, userC]); diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts index 804b3205ee2..d793b70930d 100644 --- a/apps/webapp/test/e2e_tests/test.fixtures.ts +++ b/apps/webapp/test/e2e_tests/test.fixtures.ts @@ -22,7 +22,7 @@ import {test as baseTest, type BrowserContext, type Page} from '@playwright/test import {ApiManagerE2E} from './backend/apiManager.e2e'; import {getUser, User} from './data/user'; import {PageManager} from './pageManager'; -import {connectWithUser, sendConnectionRequest} from './utils/userActions'; +import {connectWithUser} from './utils/userActions'; import {mockAudioAndVideoDevices} from './utils/mockVideoDevice.util'; import {Role} from '@wireapp/api-client/lib/team'; import {FEATURE_KEY} from '@wireapp/api-client/lib/team/feature'; @@ -239,17 +239,6 @@ export const withConnectedUser = await connectWithUser(pageManager, await user); }; -/** - * PagePlugin to connect with the given user - * Note: This plugin only works if the users are NOT in the same team - */ -export const withConnectionRequest = - (user: User | Promise): PagePlugin => - async page => { - const pageManager = PageManager.from(page); - await sendConnectionRequest(pageManager, await user); - }; - /** PagePlugin to open a guest user link and join the group chat as temporary member */ export const withGuestUser = (link: string, guestName: string): PagePlugin => diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 1f2ecec38bb..f860fd28d40 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -17,7 +17,7 @@ * */ -import {expect, TestInfo} from 'playwright/test'; +import {expect, Page, TestInfo} from 'playwright/test'; import {ApiManagerE2E} from '../backend/apiManager.e2e'; import {User} from '../data/user'; @@ -85,8 +85,8 @@ export const createGroup = async ( * Opens the connections tab, searches for the given user and starts a conversation with him * Note: This util only works if both users are part of the same team. */ -export async function connectWithUser(senderPageManager: PageManager, receiver: Pick) { - const {pages, modals, components} = senderPageManager.webapp; +export async function connectWithUser(sender: Page | PageManager, receiver: Pick) { + const {pages, modals, components} = ('webapp' in sender ? sender : PageManager.from(sender)).webapp; await components.conversationSidebar().clickConnectButton(); await pages.startUI().searchInput.fill(receiver.username); await pages.startUI().selectUsers(receiver.username); @@ -97,8 +97,8 @@ export async function connectWithUser(senderPageManager: PageManager, receiver: * Opens the connections tab, searches for the given user and sends a connection request * Note: This util only works if both users are NOT in the same team */ -export async function sendConnectionRequest(senderPageManager: PageManager, receiver: User) { - const {pages, modals, components} = senderPageManager.webapp; +export async function sendConnectionRequest(sender: Page | PageManager, receiver: User) { + const {pages, modals, components} = ('webapp' in sender ? sender : PageManager.from(sender)).webapp; await components.conversationSidebar().clickConnectButton(); await pages.startUI().searchInput.fill(receiver.username); await pages.startUI().selectUsers(receiver.username); From 906df38a76fe03cfe25402e3d0e72ebc2d6e5e29 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:16:24 +0200 Subject: [PATCH 19/49] ci: use manual testrun import instead of testiny "automation" [WPB-24713] (#21161) * ci: update command to import to testiny * chore: remove no longer needed beforeEach fixture * ci: only run testiny upload if previous steps completed * revert: remove blob cleanup from artifact * fix: don't prompt for confirmation before importing * fix: have tags added correctly to TC-764/5 * fix: remove duplicate test id * feat: add custom script to transform and upload test report via API * refactor: pass run description from within main * ci: allow nightly pipeline to be triggered manually * fix: add linebreak after markdown header in run description * refactor: extract magic number for project id * refactor: pass run description from yml * feat: associate test run with testplan automatically Co-authored-by: Copilot * refactor: declare resultMap outside of reduce * refactor: add time to name of test run * fix: remove broken alias in cli args * refactor: add logs to upload script --------- Co-authored-by: Copilot --- .github/workflows/e2e-tests-nightly.yml | 12 +- .github/workflows/e2e-tests.yml | 32 +-- .../e2e_tests/scripts/uploadTestReport.ts | 184 ++++++++++++++++++ .../e2e_tests/specs/AppLock/AppLock.spec.ts | 2 +- .../specs/SendingAssets/sendingAssets.spec.ts | 4 +- apps/webapp/test/e2e_tests/test.fixtures.ts | 13 -- 6 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 apps/webapp/test/e2e_tests/scripts/uploadTestReport.ts diff --git a/.github/workflows/e2e-tests-nightly.yml b/.github/workflows/e2e-tests-nightly.yml index 7e4a0904b3f..ae4796b607b 100644 --- a/.github/workflows/e2e-tests-nightly.yml +++ b/.github/workflows/e2e-tests-nightly.yml @@ -1,6 +1,7 @@ name: E2E Tests Nightly on: + workflow_dispatch: schedule: # we want to run this nightly on dev - cron: '0 02 * * 1-5' @@ -43,8 +44,15 @@ jobs: artifact-ids: ${{ needs.e2e_tests.outputs.reportArtifactId }} path: apps/webapp/playwright-report - - name: Run Testiny CLI + - name: Upload report to Testiny env: TESTINY_API_KEY: ${{ secrets.TESTINY_API_KEY }} + TESTINY_TEST_PLAN_ID: 105 # 105 is the id of the "Regression" test plan within Testiny + DESCRIPTION: | + Build URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | - yarn dlx @testiny/cli automation --project WEB --source "github" --playwright ./apps/webapp/playwright-report/report.json + node ./apps/webapp/test/e2e_tests/scripts/uploadTestReport.ts \ + --runName="Regression $(date +'%m/%d/%Y - %H:%M')" \ + --testPlanId="$TESTINY_TEST_PLAN_ID" \ + --description="$DESCRIPTION" \ + --reportPath="./apps/webapp/playwright-report/report.json" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d02c4bbd5f3..6796ec6c1c6 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -221,43 +221,14 @@ jobs: pattern: blob-report-* merge-multiple: true - - name: Check for blob reports - id: check_blob_reports - run: | - if [ -d "${{ env.PLAYWRIGHT_REPORT_PATH }}/blob-reports" ]; then - echo "has_blob_reports=true" >> "$GITHUB_OUTPUT" - else - echo "has_blob_reports=false" >> "$GITHUB_OUTPUT" - fi - - # Merge reports and delete blob reports and zip archives for traces, only keep resources to include within the artifact - name: Merge playwright reports - if: ${{ steps.check_blob_reports.outputs.has_blob_reports == 'true' }} working-directory: apps/webapp run: | yarn playwright merge-reports --config ./playwright.config.ts playwright-report/blob-reports rm -rf playwright-report/blob-reports - - name: Delete trace artifacts - if: ${{ !inputs.keep_traces && steps.check_blob_reports.outputs.has_blob_reports == 'true' }} - working-directory: apps/webapp - run: | - if [ -d "playwright-report/html/data" ]; then - DELETED_COUNT=$(find playwright-report/html/data -name "*.zip" -type f -delete -print) - echo "Deleted $DELETED_COUNT trace archive files" - else - echo "Trace data directory not found, skipping trace cleanup" - fi - - - name: Delete blob report artifacts - if: ${{ always() && steps.check_blob_reports.outputs.has_blob_reports == 'true' }} - uses: geekyeggo/delete-artifact@d79b2442431e4adbc383d29a28e630374eceb303 - with: - name: blob-report-* - - name: Upload test report id: upload_playwright_report - if: ${{ steps.check_blob_reports.outputs.has_blob_reports == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: playwright-report @@ -271,7 +242,6 @@ jobs: - name: Extract test results id: test-results - if: ${{ steps.check_blob_reports.outputs.has_blob_reports == 'true' }} run: | REPORT="${{ env.PLAYWRIGHT_REPORT_PATH }}/report.json" if [ ! -f "$REPORT" ]; then @@ -316,7 +286,7 @@ jobs: } - name: Update Testiny Run with results - if: ${{ inputs.testinyRunName }} + if: ${{ success() && inputs.testinyRunName }} env: TESTINY_API_KEY: ${{ secrets.TESTINY_API_KEY }} TESTINY_RUN_NAME: ${{ inputs.testinyRunName }} diff --git a/apps/webapp/test/e2e_tests/scripts/uploadTestReport.ts b/apps/webapp/test/e2e_tests/scripts/uploadTestReport.ts new file mode 100644 index 00000000000..27d361aac5f --- /dev/null +++ b/apps/webapp/test/e2e_tests/scripts/uploadTestReport.ts @@ -0,0 +1,184 @@ +/** + * Script to upload report the result of a playwright test run to testiny. + * It will create a new test run, add the results of the given report.json and close the report automatically. + * + * This is used instead of the cli published by Testiny since the CLI is limited by features. + */ + +import path from 'node:path'; +import {parseArgs} from 'node:util'; +import * as fs from 'node:fs'; +import type {JSONReport, JSONReportSpec, JSONReportSuite, JSONReportTest} from '@playwright/test/reporter'; + +const TESTINY_PROJECT_ID = 3; // 3 is the id of the WEB project in Testiny + +const {values: args} = parseArgs({ + args: process.argv.slice(2), + options: { + testinyApiKey: { + type: 'string', + description: 'API key to connect to testiny', + default: process.env.TESTINY_API_KEY, + }, + reportPath: { + type: 'string', + description: 'Path to the playright report in json format', + }, + runName: { + type: 'string', + description: 'Name of the test run', + }, + testPlanId: { + type: 'string', + description: 'ID of the test plan this run should be associated with', + }, + description: { + type: 'string', + description: 'Description to add to the run, supports markdown', + }, + }, +}); + +if (!args.testinyApiKey) throw new Error('Missing required arg testinyApiKey'); +if (!args.reportPath) throw new Error('Missing required arg reportPath'); +if (!args.runName) throw new Error('Missing required arg runName'); + +const getTests = (suite: JSONReportSuite): (JSONReportTest & Pick)[] => { + return [ + ...(suite.specs.flatMap(spec => spec.tests.map(test => ({...test, tags: spec.tags}))) ?? []), + ...(suite.suites?.flatMap(suite => getTests(suite)) ?? []), + ]; +}; + +type TestRun = {id: number}; +async function createTestRun(options?: {testPlanId?: number; description?: string}): Promise { + const body = { + title: args.runName, + project_id: TESTINY_PROJECT_ID, + testplan_id: options?.testPlanId, + description: options?.description, + }; + console.log(`Creating test run with title: ${body.title}`, body); + + const res = await fetch('https://app.testiny.io/api/v1/testrun', { + method: 'POST', + headers: { + Authorization: `Bearer ${args.testinyApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + throw new Error(`Failed to create test run ${JSON.stringify(await res.json(), undefined, 2)}`); + } + + return await res.json(); +} + +async function closeTestRun(testRun: TestRun) { + const res = await fetch(`https://app.testiny.io/api/v1/testrun/${testRun.id}`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${args.testinyApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({...testRun, is_closed: true}), + }); + + if (!res.ok) { + throw new Error(`Failed to close test run ${JSON.stringify(await res.json(), undefined, 2)}`); + } +} + +async function deleteTestRun(runId: number) { + const res = await fetch(`https://app.testiny.io/api/v1/testrun/${runId}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${args.testinyApiKey}`, + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`Couldn't delete test run ${runId}`); + } +} + +type TestinyTestCaseMapping = { + ids: {testcase_id: number; testrun_id: number}; + mapped: {assigned_to: 'ANY'; result_status: 'NOTRUN' | 'PASSED' | 'FAILED' | 'BLOCKED' | 'SKIPPED'}; +}; + +function transformReportToTestinyMappings(report: JSONReport, runId: number) { + // Mapping from playwrights status to the result expected by Testiny + const resultMap: Record = { + expected: 'PASSED', + unexpected: 'FAILED', + flaky: 'PASSED', + skipped: 'SKIPPED', + }; + + return report.suites + .flatMap(suite => getTests(suite)) + .reduce((acc, test) => { + const testIds = test.tags + .filter(tag => tag.startsWith('TC-')) + .map(tag => Number(tag.replace('TC-', ''))) + .filter(Number.isInteger); + + for (const testId of testIds) { + acc.push({ + ids: {testrun_id: runId, testcase_id: testId}, + mapped: {assigned_to: 'ANY', result_status: resultMap[test.status]}, + }); + } + + return acc; + }, []); +} + +async function addTestResultsToRun(testCaseMappings: TestinyTestCaseMapping[]) { + const res = await fetch('https://app.testiny.io/api/v1/testrun/mapping/bulk/testcase:testrun?op=add', { + method: 'POST', + headers: { + Authorization: `Bearer ${args.testinyApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(testCaseMappings), + }); + + if (!res.ok) { + throw new Error(`Failed to add test results to test run ${JSON.stringify(await res.json(), undefined, 2)}`); + } +} + +async function main() { + const reportAbsPath = path.resolve(args.reportPath); + if (!fs.existsSync(reportAbsPath)) { + throw new Error(`Report file not found: ${reportAbsPath}`); + } + + const report: JSONReport = JSON.parse(fs.readFileSync(reportAbsPath, 'utf-8')); + const testRun = await createTestRun({ + testPlanId: Number.isInteger(+args.testPlanId) ? +args.testPlanId : undefined, + description: `\n${args.description}\n`, + }); + console.log(`Created test run with id: ${testRun.id}`); + + try { + const testResults = transformReportToTestinyMappings(report, testRun.id); + + await addTestResultsToRun(testResults); + console.log(`Added ${testResults.length} test results to test run`); + + await closeTestRun(testRun); + console.log('Successfully imported test results'); + } catch (e) { + console.error('Failed to add test results to run, deleting test run'); + // In case adding the results failed delete the whole test run + await deleteTestRun(testRun.id); + throw e; + } +} +main(); diff --git a/apps/webapp/test/e2e_tests/specs/AppLock/AppLock.spec.ts b/apps/webapp/test/e2e_tests/specs/AppLock/AppLock.spec.ts index c8bbea0ede3..f474417bf41 100644 --- a/apps/webapp/test/e2e_tests/specs/AppLock/AppLock.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AppLock/AppLock.spec.ts @@ -211,7 +211,7 @@ test.describe('AppLock', () => { }, ); - test('I want to switch off app lock', {tag: ['@TC-2771', '@TC-2772', '@regression']}, async ({api, createPage}) => { + test('I want to switch off app lock', {tag: ['@TC-2771', '@regression']}, async ({api, createPage}) => { await api.brig.toggleAppLock(owner.teamId, 'enabled', false); const page = await createPage(withLogin(memberA)); diff --git a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts index df60db705ce..ce6afdb8ecc 100644 --- a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts @@ -177,11 +177,11 @@ test.describe('Sending Assets', () => { [ { - tag: ['@TC-764', '@regression'], + tag: '@TC-764', conversationType: '1on1', } as const, { - tag: ['@TC-765', '@regression'], + tag: '@TC-765', conversationType: 'group', } as const, ].forEach(({tag, conversationType}) => { diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts index d793b70930d..31aef4d75bb 100644 --- a/apps/webapp/test/e2e_tests/test.fixtures.ts +++ b/apps/webapp/test/e2e_tests/test.fixtures.ts @@ -71,19 +71,6 @@ export type Team = { export {expect} from '@playwright/test'; export const test = baseTest.extend({ - // Temporary workaround to add the test id as annotation instead of tag so Testiny can pick it up - // The following test suites need to be updated to be individual tests: AppLock, Connections, RegisterSpecs - _beforeEach: [ - async ({}, use, testInfo) => { - const testid = testInfo.tags.find(tag => tag.startsWith('@TC')); - if (testid && !testInfo.annotations.some(annotation => annotation.type === 'testid')) { - testInfo.annotations.push({type: 'testid', description: testid.slice(1)}); - } - - await use(); - }, - {auto: true}, - ], api: async ({}, use) => { // Create a new instance of ApiManager for each test await use(new ApiManagerE2E()); From 5d329d46d117b583f52081c6cf0452d3094f1f7d Mon Sep 17 00:00:00 2001 From: Zhanna Chaikovska <87633082+zhannach@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:36:53 +0200 Subject: [PATCH 20/49] test: write missing History Backup test [WPB-24920] (#21166) * test: verify message blocking in left groups after backup TC-10549 * test: add login timeout to prevent flakiness TC-10549 --- .../specs/HistoryBackup/historyBackup.spec.ts | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index c93e8ef102e..5e905eb54ea 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -19,7 +19,7 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; -import {test, expect, withLogin, withConnectedUser} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser, LOGIN_TIMEOUT} from 'test/e2e_tests/test.fixtures'; import {createAndSaveBackup, createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; import {generateSecurePassword, generateWireEmail} from '../../utils/userDataGenerator'; import {RequestResetPasswordPage} from '../../pageManager/webapp/pages/requestResetPassword.page'; @@ -27,6 +27,8 @@ import {RequestResetPasswordPage} from '../../pageManager/webapp/pages/requestRe test.describe('History Backup', () => { let userA: User; let userB: User; + const conversationName = 'Test group'; + test.beforeEach(async ({createTeam, createUser}) => { userB = await createUser(); const team = await createTeam('Test Team', {users: [userB]}); @@ -45,7 +47,6 @@ test.describe('History Backup', () => { const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; - const conversationName = 'Test group'; await createGroup(userAPages, conversationName, [userB]); const messageUserA = 'Message from User A'; @@ -168,7 +169,6 @@ test.describe('History Backup', () => { const {pages: userAPages, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; - const conversationName = 'Test group'; await createGroup(userBPages, conversationName, [userA]); const messageUserA = 'Message from User A'; @@ -228,7 +228,6 @@ test.describe('History Backup', () => { const {pages: userAPages, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; - const conversationName = 'Test group'; await createGroup(userAPages, conversationName, [userB]); const messageUserA = 'Message from User A'; @@ -343,7 +342,6 @@ test.describe('History Backup', () => { const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; - const conversationName = 'Test group'; await createGroup(userAPages, conversationName, [userB]); const messageUserA = 'Message from User A'; @@ -380,4 +378,55 @@ test.describe('History Backup', () => { }); }, ); + + test( + 'I shouldn`t be able to send messages in the group conversation that I previously left after backup', + {tag: ['@TC-10549', '@regression']}, + async ({createPage}, testInfo) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB))), + ]); + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + await createGroup(userBPages, conversationName, [userA]); + + await test.step('User A and User B write messages to each other', async () => { + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage('Message from User A'); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage('Message from User B'); + await expect(userAPages.conversation().messageItems).toHaveCount(2); + }); + + await test.step('User A leaves the group', async () => { + await userAPages.conversation().toggleGroupInformation(); + await userAPages.conversation().leaveConversation(); + await userAModals.leaveConversation().clickConfirm(); + await expect(userAPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); + }); + + const backupName = await test.step('User A creates History Backup', async () => { + await userAComponents.conversationSidebar().clickPreferencesButton(); + return await createAndSaveBackup(testInfo, userAPageManager); + }); + + await test.step('User A restores backup', async () => { + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().preferencesButton.click({timeout: LOGIN_TIMEOUT}); + await userAPages.account().backupFileInput.setInputFiles(backupName); + }); + + await test.step('Validate user A cannot send messages in the left group', async () => { + await userAComponents.conversationSidebar().allConversationsButton.click(); + await userAPages.conversationList().openConversation(conversationName); + await expect(userAPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); + await expect(userAPages.conversation().messageInput).toBeHidden(); + }); + }, + ); }); From 88c93e41f206372dd13fc2ac45ea24a0bf49d514 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 14 Apr 2026 19:35:12 +0200 Subject: [PATCH 21/49] refactor: add open function to enhanced conversation locator --- .../pageManager/webapp/pages/conversationList.page.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts index f55baf7d0a7..fc95ac2da59 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts @@ -68,7 +68,7 @@ export class ConversationListPage { conversation = conversation.and(this.page.locator(`[data-protocol="${options.protocol}"]`)); } - return Object.assign(conversation, { + const enhancedLocator = Object.assign(conversation, { userAvatar: conversation.getByTestId('element-avatar-user'), statusAvailabilityIcon: conversation.getByTestId('status-availability-icon'), unreadIndicator: conversation.getByTitle('Unread message'), @@ -76,8 +76,17 @@ export class ConversationListPage { mentionIndicator: conversation.getByTitle('Unread mention'), blockedIndicator: conversation.locator(`span[data-uie-name="status-label"] + span`), joinCallButton: conversation.getByRole('button', {name: 'Join'}), + + // This is just syntactic sugar to allow capturing the enhanced locator from the open function + open: async () => { + await conversation.click(); + return enhancedLocator; + }, + openContextMenu: () => this.openContextMenu(conversation), }); + + return enhancedLocator; } /** From 67de4faa4426a342a9b44a5d0adb2a65a118e480 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Thu, 30 Apr 2026 11:36:38 +0200 Subject: [PATCH 22/49] refactor: remove all usages of openConversation --- .../specs/Accessibility/Accessibility.spec.ts | 10 +-- .../AccountSettings/accountSettings.spec.ts | 6 +- .../e2e_tests/specs/Archive/archive.spec.ts | 43 +++++----- .../Authentication/authentication.spec.ts | 8 +- .../test/e2e_tests/specs/Block/block.spec.ts | 73 ++++++++-------- .../e2e_tests/specs/Calling/calling.spec.ts | 38 ++++---- .../clearConversationContent.spec.ts | 83 +++++++++--------- .../specs/Connections/Connections.spec.ts | 7 +- .../specs/Conversations/conversations.spec.ts | 48 +++++------ .../addMembersToChat-TC-8631.spec.ts | 20 ++--- .../backupRestoration-TC-8634.spec.ts | 15 ++-- .../CriticalFlow/channelsCall-TC-8755.spec.ts | 2 +- .../channelsManagement-TC-8752.spec.ts | 14 +-- .../conversationManagement-TC-8636.spec.ts | 14 +-- .../CriticalFlow/groupCalls-TC-8632.spec.ts | 4 +- .../groupVideoCall-TC-8637.spec.ts | 2 +- .../CriticalFlow/joinTeam-TC-8635.spec.ts | 8 +- .../messagesIn1On1-TC-8750.spec.ts | 7 +- .../messagesInChannels-TC-8753.spec.ts | 9 +- .../messagesInGroups-TC-8751.spec.ts | 9 +- .../CriticalFlow/oneOnOneCall-TC-8754.spec.ts | 2 +- .../personalAccountLifecycle-TC-8638.spec.ts | 12 +-- .../specs/DeepLinks/deepLinks.spec.ts | 8 +- .../e2e_tests/specs/Delete/delete.spec.ts | 40 ++++----- .../test/e2e_tests/specs/Drive/drive.spec.ts | 16 ++-- .../test/e2e_tests/specs/Edit/edit.spec.ts | 26 +++--- .../e2e_tests/specs/Folders/folders.spec.ts | 28 +++--- .../groupConversation.spec.ts | 23 +++-- .../specs/Guestroom/guestroom.spec.ts | 14 +-- .../specs/HistoryBackup/historyBackup.spec.ts | 44 +++++----- .../inConversationSearch.spec.ts | 64 +++++++------- .../specs/LinkPreview/linkPreview.spec.ts | 4 +- .../specs/Localization/localization.spec.ts | 8 +- .../test/e2e_tests/specs/Logs/logs.spec.ts | 4 +- .../e2e_tests/specs/Markdown/markdown.spec.ts | 22 ++--- .../e2e_tests/specs/Mention/mention.spec.ts | 77 +++++++++-------- .../specs/Notifications/notifications.spec.ts | 86 ++++++++++--------- .../participantProfile.spec.ts | 8 +- .../test/e2e_tests/specs/Ping/ping.spec.ts | 14 +-- .../specs/Reactions/reactions.spec.ts | 26 +++--- .../test/e2e_tests/specs/Reply/reply.spec.ts | 8 +- .../e2e_tests/specs/Search/search.spec.ts | 2 +- .../selfDeletingMessages.spec.ts | 22 ++--- .../specs/SendingAssets/sendingAssets.spec.ts | 26 +++--- .../e2e_tests/specs/Status/status.spec.ts | 38 ++++---- .../test/e2e_tests/utils/userActions.ts | 2 +- 46 files changed, 526 insertions(+), 518 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts b/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts index 1538390d871..3cf43c8d38e 100644 --- a/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts @@ -44,8 +44,8 @@ test.describe('Accessibility', () => { ]); await createGroup(userAPages, 'Accessible Group', [userB]); - await userAPages.conversationList().openConversation('Accessible Group'); - await userBPages.conversationList().openConversation('Accessible Group'); + await userAPages.conversationList().getConversationLocator('Accessible Group').open(); + await userBPages.conversationList().getConversationLocator('Accessible Group').open(); await test.step('User A starts typing in group and B sees typing indicator', async () => { await userAPages.conversation().messageInput.pressSequentially('Test', {delay: 100}); @@ -60,7 +60,7 @@ test.describe('Accessibility', () => { await test.step('User A types more into group', async () => { await userAPages.sidebar().allConversationsButton.click(); - await userAPages.conversationList().openConversation('Accessible Group'); + await userAPages.conversationList().getConversationLocator('Accessible Group').open(); await userAPages.conversation().messageInput.pressSequentially('Test', {delay: 100}); // Since A disabled the typing indicator B should not see it await expect(userBPages.conversation().typingIndicator).not.toBeVisible(); @@ -92,11 +92,11 @@ test.describe('Accessibility', () => { await pages.conversation().messageInput.fill('Draft Message'); await pages.conversation().backButton.click(); - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await expect(pages.conversation().messageInput).toBeEmpty(); await pages.conversation().backButton.click(); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await expect(pages.conversation().messageInput).toHaveText('Draft Message'); }, ); diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index cf2d19cd3ea..662943c26a7 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -187,7 +187,7 @@ test.describe('account settings', () => { const memberAPages = PageManager.from(memberAPage).webapp.pages; const memberBPages = PageManager.from(memberBPage).webapp.pages; - await memberAPages.conversationList().openConversation(memberB.fullName, {protocol: 'mls'}); + await memberAPages.conversationList().getConversationLocator(memberB.fullName, {protocol: 'mls'}).open(); await memberAPages.conversation().startCall(); await memberBPages.calling().clickAcceptCallButton(); @@ -213,7 +213,7 @@ test.describe('account settings', () => { }); await test.step('Conversation itself on top of users message', async () => { - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await pages.conversation().sendMessage('test'); await expect(pages.conversation().getMessage({content: 'test'})).toContainText(memberA.fullName); }); @@ -243,7 +243,7 @@ test.describe('account settings', () => { await pages.conversationList().clickCreateGroup(); await modals.createConversation().createChannel('Test Channel', {members: [memberB]}); - await pages.conversationList().openConversation('Test Channel'); + await pages.conversationList().getConversationLocator('Test Channel').open(); await pages.conversation().conversationInfoButton.click(); await expect(pages.conversationDetails().groupAdmins.filter({hasText: memberA.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts b/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts index 72c8dfb959f..1d204779200 100644 --- a/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts @@ -40,19 +40,20 @@ test.describe('Archive', () => { const page = await createPage(withLogin(memberA), withConnectedUser(memberB)); const {pages, components} = PageManager.from(page).webapp; - let contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); + const conversation = pages.conversationList().getConversationLocator(memberB.fullName); + let contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(conversation).not.toBeVisible(); await components.conversationSidebar().clickArchive(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).toBeVisible(); + await expect(conversation).toBeVisible(); - contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); + contextMenu = await conversation.openContextMenu(); await contextMenu.unarchiveButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(conversation).not.toBeVisible(); await components.conversationSidebar().clickAllConversationsButton(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).toBeVisible(); + await expect(conversation).toBeVisible(); }, ); @@ -65,25 +66,25 @@ test.describe('Archive', () => { PageManager.from(createPage(withLogin(memberB))).then(pm => pm.webapp.pages), ]); - await memberAPages.conversationList().openConversation(memberB.fullName, {protocol: 'mls'}); - await memberBPages.conversationList().openConversation(memberA.fullName, {protocol: 'mls'}); + const memberAConversation = await memberAPages + .conversationList() + .getConversationLocator(memberB.fullName, {protocol: 'mls'}) + .open(); + await memberBPages.conversationList().getConversationLocator(memberA.fullName, {protocol: 'mls'}).open(); await test.step('MemberA archives conversation with memberB', async () => { - const contextMenu = await memberAPages - .conversationList() - .getConversationLocator(memberB.fullName, {protocol: 'mls'}) - .openContextMenu(); + const contextMenu = await memberAConversation.openContextMenu(); await contextMenu.archiveButton.click(); }); await test.step('MemberB sends message in archived conversation', async () => { await memberBPages.conversation().sendMessage('Test message'); - await expect(memberAPages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(memberAConversation).not.toBeVisible(); }); await test.step('MemberB pings in archived conversation', async () => { await memberBPages.conversation().sendPing(); - await expect(memberAPages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(memberAConversation).not.toBeVisible(); }); }, ); @@ -98,35 +99,35 @@ test.describe('Archive', () => { const page = await createPage(withLogin(memberA), withConnectedUser(memberB)); const {pages, components} = PageManager.from(page).webapp; - await pages.conversationList().openConversation(memberB.fullName); + const conversation = await pages.conversationList().getConversationLocator(memberB.fullName).open(); if (tag === '@TC-103') { await test.step('User mutes the conversation', async () => { - const contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.notificationsButton.click(); await pages.conversationDetails().selectNotificationsLevel('Nothing'); }); } await test.step('User archives the conversation', async () => { - const contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(conversation).not.toBeVisible(); }); await test.step('User switches to archived conversations and sees it there', async () => { await components.conversationSidebar().archiveButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).toBeVisible(); + await expect(conversation).toBeVisible(); }); await test.step('User starts a call in the archived conversation', async () => { - await pages.conversationList().openConversation(memberB.fullName); + await conversation.open(); await pages.conversation().startCall(); }); await test.step('The conversation should still not be shown within all conversations', async () => { await components.conversationSidebar().allConversationsButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(conversation).not.toBeVisible(); }); }); }); diff --git a/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts b/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts index e21ecec8af1..0f1058b5106 100644 --- a/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts @@ -162,7 +162,7 @@ test.describe('Authentication', () => { }); await test.step('Send a message', async () => { - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().sendMessage('Before refresh'); }); @@ -172,7 +172,7 @@ test.describe('Authentication', () => { await pageManager.refreshPage({waitUntil: 'load'}); - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await expect(message).toBeVisible(); }); }, @@ -213,7 +213,7 @@ test.describe('Authentication', () => { await test.step('Connect with and send message to userB', async () => { await connectWithUser(pageManager, userB); - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().sendMessage('Test message'); await expect(pages.conversation().getMessage({content: 'Test message'})).toBeVisible(); }); @@ -233,7 +233,7 @@ test.describe('Authentication', () => { }); await test.step('Verify previously sent message is gone', async () => { - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await expect(pages.conversation().getMessage({content: 'Test message'})).not.toBeAttached(); }); }, diff --git a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts index c5f100bb350..251b3b59f52 100644 --- a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts @@ -32,8 +32,8 @@ import {createGroup, sendConnectionRequest} from '../../utils/userActions'; export async function blockUserFromConversationList(pageManager: PageManager, userToBlock: User) { const {pages, modals} = pageManager.webapp; - await pages.conversationList().openConversation(userToBlock.fullName); - const contextMenu = await pages.conversationList().getConversationLocator(userToBlock.fullName).openContextMenu(); + const conversation = await pages.conversationList().getConversationLocator(userToBlock.fullName).open(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.blockButton.click(); await modals.blockWarning().clickBlock(); } @@ -92,18 +92,18 @@ test.describe('User Blocking', () => { await userBPages.connectRequest().clickConnectButton(); // Step 1: User A opens conversation with User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - // Step 2: User A opens the options menu for user B - const contextMenu = await userAPages + const conversation = await userAPages .conversationList() .getConversationLocator(userB.fullName, {protocol: 'mls'}) - .openContextMenu(); + .open(); + // Step 2: User A opens the options menu for user B + const contextMenu = await conversation.openContextMenu(); // Step 3: User A opens modal and clicks 'Block' button await contextMenu.blockButton.click(); // Step 4: User A clicks 'Cancel' button await userAModals.blockWarning().clickCancel(); // Step 5: Conversation is still present, and User A can open it - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await conversation.open(); // Step 6: User A still can send message to User B await expect(userAPages.conversation().messageInput).toBeVisible(); }, @@ -122,7 +122,12 @@ test.describe('User Blocking', () => { await userBPages.connectRequest().clickConnectButton(); // Step 1: User A and B have a 1:1 conversation - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + const conversation = await userAPages + .conversationList() + .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + // Step 2: User A opens conversation info await userAPages.conversation().clickConversationInfoButton(); // Step 3: User A clicks 'Block conversation' button @@ -130,23 +135,18 @@ test.describe('User Blocking', () => { // Step 4: User A clicks 'Confirm' button await userAModals.blockWarning().clickBlock(); // Step 5: User A cannot send message to User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await conversation.open(); await expect(userAPages.conversation().messageInput).not.toBeVisible(); // Step 6: User B cannot send message to User A - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); await userBPages.conversation().sendMessage('Message after block'); const message = userAPages.conversation().getMessage({sender: userB}); await expect(message).not.toBeVisible(); // Step 7: 'Blocked' chip is visible next to the name of User B in the conversation list - const statusTextElement = userAPages - .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}).blockedIndicator; - await expect(statusTextElement).toBeVisible(); - await expect(statusTextElement).toHaveText('Blocked'); + await expect(conversation.blockedIndicator).toBeVisible(); + await expect(conversation.blockedIndicator).toHaveText('Blocked'); // Step 8: Profile Picture of User B is replaced by 'blocked' picture - const {userAvatar} = userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}); - const blockedIcon = userAvatar.locator('[data-uie-value="blocked"]'); + const blockedIcon = conversation.userAvatar.locator('[data-uie-value="blocked"]'); await expect(blockedIcon).toBeVisible(); }, ); @@ -172,23 +172,23 @@ test.describe('User Blocking', () => { await createGroup(userAPages, conversationName, [userB]); await test.step('Step 1: User B sends message to group chat with User A', async () => { - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage('message before block'); }); await test.step('Step 2: User A blocks User B from group conversation', async () => { // Ensures User A is in the group before blocking - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await blockUserFromOpenGroupProfileView(userAPageManager, userB); }); await test.step('Step 3: User B writes second message to the group chat after being blocked by User A', async () => { - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage('message after block'); }); await test.step('Step 4: User A receives message from User B in Group Chat even though User B is blocked', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await expect(userAPages.conversation().messages).toHaveCount(2); }); }, @@ -208,27 +208,26 @@ test.describe('User Blocking', () => { await userBPages.connectRequest().clickConnectButton(); // Step 1: User A and B have a 1:1 conversation - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + const conversation = await userAPages + .conversationList() + .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .open(); // Step 2: User A blocks User B await blockUserFromProfileView(userAPageManagerInstance); await expect(userAPages.conversation().messageInput).toBeHidden(); // Step 3: User A unblocks User B from Conversation Details Options - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) - .openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.unblockButton.click(); await userAModals.confirm().clickAction(); // Step 4: User A send message to User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await conversation.open(); await userAPages.conversation().sendMessage('Message after unblock'); // Step 5: User B receives message from User A - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await expect(userBPages.conversation().messages).toHaveCount(1); // Step 6: User B writes message to User A await userBPages.conversation().sendMessage('Message after being unblocked'); // Step 7: User A receives message from User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); const message = userAPages.conversation().getMessage({sender: userB}); await expect(message).toContainText('Message after being unblocked'); }, @@ -252,7 +251,7 @@ test.describe('User Blocking', () => { // Step 1: User B blocks User A await test.step('User B blocks User A', async () => { // Open the conversation with User C to prevent the "Wire cannot open this conversation" modal - await userAPages.conversationList().openConversation(userC.fullName); + await userAPages.conversationList().getConversationLocator(userC.fullName).open(); await blockUserFromConversationList(userBPageManagerInstance, userA); }); @@ -302,7 +301,7 @@ test.describe('User Blocking', () => { await userBPages.connectRequest().clickConnectButton(); await test.step('User A blocks User B', async () => { - await userAPages.conversationList().openConversation(userC.fullName); + await userAPages.conversationList().getConversationLocator(userC.fullName).open(); await blockUserFromConversationList(userAPageManagerInstance, userB); }); @@ -315,16 +314,16 @@ test.describe('User Blocking', () => { }); await test.step('User B receives message sent by User A', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message after unblocking'); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await expect(userBPages.conversation().messages).toHaveCount(1); }); await test.step('User A receives message sent by User B', async () => { - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message after being unblocked'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); const message = userAPages.conversation().getMessage({sender: userB}); await expect(message).toContainText('Message after being unblocked'); }); @@ -347,10 +346,10 @@ test.describe('User Blocking', () => { const userAPages = userAPageManager.pages; // Step 1: User A opens conversation with User B - await userAPages.conversationList().openConversation(userB.fullName); + const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); // Step 2: User A opens the options menu for user B - const contextMenu = await userAPages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); // Step 3: Block conversation button is not visible for team members await expect(contextMenu.blockButton).not.toBeAttached(); diff --git a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts index 8713ff4835b..3c64c4e75da 100644 --- a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts @@ -53,13 +53,13 @@ test.describe('Calling', () => { ]); // User A has a call with user B - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().clickCallButton(); await userBPages.calling().clickAcceptCallButton(); await expect(userBPages.calling().callCell).toBeVisible(); // User A starts a call with user C while in a call with user B - await userAPages.conversationList().openConversation(userC.fullName); + await userAPages.conversationList().getConversationLocator(userC.fullName).open(); await userAPages.conversation().clickCallButton(); // A modal is shown prompting him to confirm before cancelling the ongoing call @@ -83,7 +83,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // Establish group call; required precondition for hand-raise testing - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userBPages.calling().callCell).toBeVisible(); @@ -114,7 +114,7 @@ test.describe('Calling', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().clickCallButton(); await userBPages.calling().clickAcceptCallButton(); @@ -137,7 +137,7 @@ test.describe('Calling', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); const {joinCallButton} = userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); @@ -174,10 +174,10 @@ test.describe('Calling', () => { const userBDevice2Pages = PageManager.from(userBPage2).webapp.pages; if (conversationType === '1on1') { - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); } else { await createGroup(userAPages, 'Calling group', [userB]); - await userAPages.conversationList().openConversation('Calling group'); + await userAPages.conversationList().getConversationLocator('Calling group').open(); } // Ensure no audio is playing on both devices initially @@ -211,7 +211,7 @@ test.describe('Calling', () => { ]); // User A calls user B - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().clickCallButton(); // User B declines the call @@ -219,7 +219,7 @@ test.describe('Calling', () => { await expect(userBPages.calling().callCell).not.toBeVisible(); // User B calls user C instead - await userBPages.conversationList().openConversation(userC.fullName); + await userBPages.conversationList().getConversationLocator(userC.fullName).open(); await expect(userBPages.conversation().callButton).toBeEnabled(); await userBPages.conversation().startCall(); await expect(userBPages.calling().callCell).toBeVisible(); @@ -236,7 +236,7 @@ test.describe('Calling', () => { const userCPages = PageManager.from(userCPage).webapp.pages; await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -263,7 +263,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -286,7 +286,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -315,7 +315,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -343,7 +343,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); await test.step('User A starts a call and User B joins', async () => { - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -354,7 +354,7 @@ test.describe('Calling', () => { }); await test.step('User A joins then leaves the call from their second device', async () => { - await userADevice2Pages.conversationList().openConversation(groupName); + await userADevice2Pages.conversationList().getConversationLocator(groupName).open(); await userADevice2Pages.conversation().clickCallButton(); await expect(userADevice2Pages.calling().callCell).toBeVisible(); @@ -391,7 +391,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, userC]); // User A initiates the call - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -427,7 +427,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -463,7 +463,7 @@ test.describe('Calling', () => { const userAPages = PageManager.from(userAPage).webapp.pages; await createGroup(userAPages, groupName, allMembers); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPage.getByTestId('modal-without-title')).toContainText( @@ -493,7 +493,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, guestUser]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickCallButton(); }); diff --git a/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts b/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts index bfb6f27ab45..bf073ab8ebc 100644 --- a/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts @@ -24,6 +24,7 @@ import {test, withLogin, withConnectedUser, expect} from 'test/e2e_tests/test.fi import {getTextFilePath, shareAssetHelper} from '../../utils/asset.util'; import {getImageFilePath} from '../../utils/sendImage.util'; import {createGroup} from '../../utils/userActions'; +import {ConversationListPage} from 'test/e2e_tests/pageManager/webapp/pages/conversationList.page'; test.describe('Clear Conversation Content', () => { let userA: User; @@ -64,22 +65,19 @@ test.describe('Clear Conversation Content', () => { await createGroup(userAPages, conversationName, [userB, userC]); // Step 2: Write messages in the group conversation - await userAPages.conversationList().openConversation(conversationName); + const conversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); - await userCPages.conversationList().openConversation(conversationName); + await userCPages.conversationList().getConversationLocator(conversationName).open(); await userCPages.conversation().sendMessage('Message from User C'); await expect(userAPages.conversation().messages).toHaveCount(3); // Step 3: User A selects 'Clear Conversation' option from the Conversation List Context Menu - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(conversationName) - .openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.clearContentButton.click(); // Step 4: Warning Popup should open await expect(userAModals.optionModal().modal).toBeVisible(); @@ -88,13 +86,13 @@ test.describe('Clear Conversation Content', () => { // Step 5: User A clicks 'Clear' await userAModals.optionModal().clickAction(); // Step 6: Verify that the conversation does not contain any past messages - await userAPages.conversationList().openConversation(conversationName); + await conversation.open(); await expect(userAPages.conversation().messages).toHaveCount(0); } else { // Step 5: User A clicks 'Cancel' await userAModals.optionModal().clickCancel(); // Step 6: Verify that the conversation still contains any past messages - await userAPages.conversationList().openConversation(conversationName); + await conversation.open(); await expect(userAPages.conversation().messages).toHaveCount(3); } }, @@ -116,21 +114,21 @@ test.describe('Clear Conversation Content', () => { const userBPages = userBPageManager.webapp.pages; // Step 1: Create a 1:1 conversation with User A and B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + const conversation = await userAPages + .conversationList() + .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .open(); // Step 2: Write messages in the conversation await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); await expect(userAPages.conversation().messages).toHaveCount(2); // Step 3: User A selects 'Clear Conversation' option from the Conversation List Context Menu - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) - .openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.clearContentButton.click(); // Step 4: Warning Popup should open await expect(userAModals.confirm().modal).toBeVisible(); @@ -139,7 +137,7 @@ test.describe('Clear Conversation Content', () => { // Step 5: User A clicks 'Clear' await userAModals.confirm().clickAction(); // Step 6: Verify that the conversation does not contain any past messages - await userAPages.conversationList().openConversation(userB.fullName); + await conversation.open(); await expect(userAPages.conversation().messages).toHaveCount(0); } else { // Step 5: User A clicks 'Cancel' @@ -174,23 +172,28 @@ test.describe('Clear Conversation Content', () => { // Step 1: Create a group conversation with User A, B and C const conversationName = conversationType === 'group' ? 'Group conversation' : userB.fullName; + let userAConversation: ReturnType; if (conversationType === 'group') { await createGroup(userAPages, conversationName, [userB, userC]); + userAConversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); } else { - await userAPages.conversationList().openConversation(conversationName, {protocol: 'mls'}); + userAConversation = await userAPages + .conversationList() + .getConversationLocator(conversationName, {protocol: 'mls'}) + .open(); } // Step 2: Write messages in the conversation - await userAPages.conversationList().openConversation(conversationName); await userAPages.conversation().sendMessage('Message from User A'); - const conversationPartnerForUserB = conversationType === 'group' ? conversationName : userA.fullName; - - await userBPages.conversationList().openConversation(conversationPartnerForUserB); + const userBConversation = await userBPages + .conversationList() + .getConversationLocator(conversationType === 'group' ? conversationName : userA.fullName) + .open(); await userBPages.conversation().sendMessage('Message from User B'); if (conversationType === 'group') { - await userCPages.conversationList().openConversation(conversationName); + await userCPages.conversationList().getConversationLocator(conversationName).open(); await userCPages.conversation().sendMessage('Message from User C'); await expect(userAPages.conversation().messages).toHaveCount(3); } else { @@ -198,10 +201,7 @@ test.describe('Clear Conversation Content', () => { } // Step 3: User A selects 'Clear Conversation' option from the Conversation List Context Menu - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(conversationName, {protocol: conversationType === '1:1' ? 'mls' : undefined}) - .openContextMenu(); + const contextMenu = await userAConversation.openContextMenu(); await contextMenu.clearContentButton.click(); // Step 4: Warning Popup should open and User A clicks 'Clear' @@ -217,15 +217,15 @@ test.describe('Clear Conversation Content', () => { // Step 5: Verify you can receive incoming new messages, pings, calls, pictures, and files // 5.1 Messages - await userBPages.conversationList().openConversation(conversationPartnerForUserB); + await userBConversation.open(); await userBPages.conversation().sendMessage('Message from User B after Clear'); - await userAPages.conversationList().openConversation(conversationName); + await userAConversation.open(); await expect(userAPages.conversation().messages).toHaveCount(1); // 5.2 Pings - await userBPages.conversationList().openConversation(conversationPartnerForUserB); + await userBConversation.open(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().openConversation(conversationName); + await userAConversation.open(); await expect(userAPages.conversation().getPing()).toBeVisible(); // 5.3 Pictures @@ -238,7 +238,7 @@ test.describe('Clear Conversation Content', () => { await expect(messageWithImage).toBeVisible(); // 5.4 Files - await userAPages.conversationList().openConversation(conversationName); + await userAConversation.open(); await shareAssetHelper(getTextFilePath(), userBPage, userBPage.getByRole('button', {name: 'Add file'})); const messageWithFile = userAPages .conversation() @@ -247,8 +247,8 @@ test.describe('Clear Conversation Content', () => { await expect(messageWithFile).toBeVisible(); // 5.5 Calls - await userAPages.conversationList().openConversation(conversationName); - await userBPages.conversationList().openConversation(conversationPartnerForUserB); + await userAConversation.open(); + await userBConversation.open(); await userBPages.conversation().startCall(); await expect(userAPages.calling().acceptCallButton).toBeVisible(); }, @@ -270,20 +270,25 @@ test.describe('Clear Conversation Content', () => { const userBPages = userBPageManager.webapp.pages; const conversationName = 'Group conversation'; + let userAConversation: ReturnType; + if (conversationType === 'group') { await createGroup(userAPages, conversationName, [userB]); // Step 1: User A and B write in group conversation - await userAPages.conversationList().openConversation(conversationName); + userAConversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); } else { // Step 1: User A and B write in conversation - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + userAConversation = await userAPages + .conversationList() + .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); } await expect(userAPages.conversation().messages).toHaveCount(2); @@ -299,14 +304,14 @@ test.describe('Clear Conversation Content', () => { // Step 5: User A clicks 'Clear' await userAModals.optionModal().clickAction(); // Step 6: Verify that the conversation does not contain any past messages - await userAPages.conversationList().openConversation(conversationName); + await userAConversation.open(); } else { // Step 4: Clear Conversation Content Modal appears await expect(userAModals.confirm().modal).toBeVisible(); // Step 5: User A clicks 'Clear' await userAModals.confirm().clickAction(); // Step 6: Verify that the conversation does not contain any past messages - await userAPages.conversationList().openConversation(userB.fullName); + await userAConversation.open(); } await expect(userAPages.conversation().messages).toHaveCount(0); }, diff --git a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts index ad417929729..0e99d287847 100644 --- a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts @@ -80,7 +80,7 @@ test.describe('Connections', () => { }); await test.step('B sends a connection request to C via the group conversation', async () => { - await memberBPages.conversationList().openConversation('Group'); + await memberBPages.conversationList().getConversationLocator('Group').open(); await memberBPages.conversation().conversationInfoButton.click(); await memberBPages.conversationDetails().openParticipantDetails(memberC.fullName); await memberBPages.participantDetails().sendConnectRequest(); @@ -117,10 +117,11 @@ test.describe('Connections', () => { await sendConnectionRequest(pageManager, memberB); const {pages} = pageManager.webapp; - const contextMenu = await pages.conversationList().getConversationLocator(memberB.fullName).openContextMenu(); + const conversation = pages.conversationList().getConversationLocator(memberB.fullName); + const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); - await expect(pages.conversationList().getConversationLocator(memberB.fullName)).toBeVisible(); + await expect(conversation).not.toBeVisible(); }, ); }); diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index d7cff637b69..da6e6473f3e 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -44,7 +44,7 @@ test.describe('Conversations', () => { const userAPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); const firstMessage = userAPages.conversation().systemMessages.first(); await expect(firstMessage).toContainText(new RegExp(`You started the conversation\\s*${groupName}\\s*with`, 'i')); @@ -63,7 +63,7 @@ test.describe('Conversations', () => { ]); await createGroup(userAPages, groupName, [userB, userC]); - userBPages.conversationList().openConversation(groupName); + userBPages.conversationList().getConversationLocator(groupName).open(); const pattern = new RegExp( `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, @@ -113,7 +113,7 @@ test.describe('Conversations', () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName('New Group Name'); @@ -141,7 +141,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversationDetails().getUserRoleIcon(guestUser.fullName)).toHaveAttribute( 'data-uie-name', @@ -170,7 +170,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); @@ -191,20 +191,20 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // Confirm that the userA is an admin in the group - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); expect(userBPages.conversation().adminsList).toBeVisible(); expect(userBPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); // User A leaves the group - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversation().leaveConversation(); await modals.leaveConversation().clickConfirm(); await expect(adminPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); // User B sees the empty admins section - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); expect(adminPages.conversation().adminsList).not.toBeVisible(); }, @@ -222,7 +222,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB]); // User B can see members section - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); expect(adminPages.conversation().membersList).toBeVisible(); expect(adminPages.conversation().membersList.getByRole('listitem')).toHaveCount(1); @@ -231,7 +231,7 @@ test.describe('Conversations', () => { await adminPages.conversation().makeUserAdmin(userB.fullName); // User A and B cannot see members section - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); for (const userPages of [adminPages, userBPages]) { await userPages.conversation().clickConversationInfoButton(); @@ -248,7 +248,7 @@ test.describe('Conversations', () => { const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(adminPages, groupName, [userB]); - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); @@ -269,7 +269,7 @@ test.describe('Conversations', () => { await createGroup(adminPage, groupName, [userB, externalUser]); - await adminPage.conversationList().openConversation(groupName); + await adminPage.conversationList().getConversationLocator(groupName).open(); await adminPage.conversation().clickConversationInfoButton(); await expect(adminPage.conversation().membersList).toBeVisible(); await expect(adminPage.conversationDetails().getUserRoleIcon(externalUser.fullName)).toHaveAttribute( @@ -290,12 +290,12 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // User A makes userB an admin - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversation().makeUserAdmin(userB.fullName); // User B can see delete group and options in conversation details - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().deleteGroupButton).toBeVisible(); await expect(userBPages.conversationDetails().guestOptionsButton).toBeVisible(); @@ -316,10 +316,10 @@ test.describe('Conversations', () => { const totalMessages = 20; await test.step('User B opens conversation with User A to mark previous history as read', async () => { - await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User A sends "Read" messages while User B is looking at the chat - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); for (let i = 1; i <= totalMessages; i++) { await userAPages.conversation().sendMessage(`READ message: ${i}`); } @@ -333,7 +333,7 @@ test.describe('Conversations', () => { }); await test.step('User B switches context to another conversation', async () => { - await pages.conversationList().openConversation(userC.fullName); + await pages.conversationList().getConversationLocator(userC.fullName).open(); }); await test.step('User A sends new unread messages to User B', async () => { @@ -349,7 +349,7 @@ test.describe('Conversations', () => { await test.step('User B opens the conversation and verifies scroll position', async () => { await components.conversationSidebar().clickAllConversationsButton(); - await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // 1. Verify conversation scroll is NOT at the very bottom (last unread shouldn't be visible yet) const lastUnreadMessage = pages @@ -391,10 +391,10 @@ test.describe('Conversations', () => { await createGroup(userBPages, groupName, [userA, userC]); // User A and B both send messages to start conversation - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().sendMessage('Message 1'); - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().sendMessage('Message 2'); // User A reacts to Message 2 while still a member @@ -461,7 +461,7 @@ test.describe('Conversations', () => { pm => pm.webapp.pages, ); - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().messageInput.pressSequentially(':) ', {delay: 100}); expect(userAPages.conversation().messageInput).toContainText('🙂'); @@ -478,7 +478,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // User A renames the conversation - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversationDetails().changeConversationName('New Group Name'); @@ -502,11 +502,11 @@ test.describe('Conversations', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(adminPages, groupName, [userB, userC]); - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A makes User C an admin - await adminPages.conversationList().openConversation(groupName); + await adminPages.conversationList().getConversationLocator(groupName).open(); await adminPages.conversation().toggleGroupInformation(); await adminPages.conversation().makeUserAdmin(userC.fullName); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts index 04a6d41067c..5628cb3ee66 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts @@ -76,11 +76,11 @@ test( await test.step('All group participants send messages in a group', async () => { const {pages} = ownerPageManager.webapp; // Member1 and Member2 open the conversation (establish encrypted session) - await member1PageManager.webapp.pages.conversationList().openConversation(conversationName); - await member2PageManager.webapp.pages.conversationList().openConversation(conversationName); + await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); // Wait for encryption to be established by checking that conversation is fully loaded - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); await pages.conversation().conversationTitle.waitFor({state: 'visible'}); // Now all members can send and receive encrypted messages @@ -101,7 +101,7 @@ test( ).toBeVisible(); // Owner verifies all messages are visible - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); await expect(pages.conversation().getMessage({content: `Hello from ${member1.firstName}!`})).toBeVisible(); await expect(pages.conversation().getMessage({content: `Hello from ${member2.firstName}!`})).toBeVisible(); }); @@ -109,7 +109,7 @@ test( await test.step('Team owner and group members react on received messages with reactions', async () => { const {pages} = ownerPageManager.webapp; // Owner reacts to member1's message with +1 (thumbs up) - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); const member1MessageForOwner = pages.conversation().getMessage({content: `Hello from ${member1.firstName}!`}); await member1MessageForOwner.waitFor({state: 'visible'}); // Wait for message to be ready await pages.conversation().reactOnMessage(member1MessageForOwner, 'plus-one'); @@ -119,7 +119,7 @@ test( await pages.conversation().reactOnMessage(member2MessageForOwner, 'plus-one'); // Member1 reacts to owner's message with heart (❤️) - await member1PageManager.webapp.pages.conversationList().openConversation(conversationName); + await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); const ownerMessageForMember1 = member1PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${owner.firstName}!`}); @@ -132,7 +132,7 @@ test( await member1PageManager.webapp.pages.conversation().reactOnMessage(member2MessageForMember1, 'heart'); // Member2 reacts to owner's message with joy (😂) - await member2PageManager.webapp.pages.conversationList().openConversation(conversationName); + await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); const ownerMessageForMember2 = member2PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${owner.firstName}!`}); @@ -148,13 +148,13 @@ test( await test.step('All group participants make sure they see reactions from other group participants', async () => { const {pages} = ownerPageManager.webapp; // Owner verifies they can see heart (❤️) and joy (😂) reactions on their message from member1 and member2 - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); const ownerMessage = pages.conversation().getMessage({content: `Hello from ${owner.firstName}!`}); await expect(pages.conversation().getReactionOnMessage(ownerMessage, 'heart')).toBeVisible(); await expect(pages.conversation().getReactionOnMessage(ownerMessage, 'joy')).toBeVisible(); // Member1 verifies they can see thumbs up (+1) and joy (😂) reactions on their message from owner and member2 - await member1PageManager.webapp.pages.conversationList().openConversation(conversationName); + await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); const member1Message = member1PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${member1.firstName}!`}); @@ -166,7 +166,7 @@ test( ).toBeVisible(); // Member2 verifies they can see thumbs up (+1) and heart (❤️) reactions on their message from owner and member1 - await member2PageManager.webapp.pages.conversationList().openConversation(conversationName); + await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); const member2Message = member2PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${member2.firstName}!`}); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts index e4f915170c1..d45f6081944 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts @@ -41,13 +41,16 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] const pageManager = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))); const {pages, modals, components} = pageManager.webapp; + const userBConversation = pages.conversationList().getConversationLocator(userB.fullName); + const groupConversation = pages.conversationList().getConversationLocator(groupName); + await test.step('User generates data', async () => { - await pages.conversationList().openConversation(userB.fullName); + await userBConversation.open(); await pages.conversation().sendMessage(personalMessage); await expect(pages.conversation().getMessage({content: personalMessage})).toBeVisible(); await createGroup(pages, groupName, [userB]); - await pages.conversationList().openConversation(groupName); + await groupConversation.open(); await pages.conversation().sendMessage(groupMessage); await expect(pages.conversation().getMessage({content: groupMessage})).toBeVisible(); @@ -73,10 +76,10 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] }); await test.step("User doesn't see previous data (messages)", async () => { - await pages.conversationList().openConversation(userB.fullName); + await userBConversation.open(); await expect(pages.conversation().getMessage({content: personalMessage})).not.toBeVisible(); - await pages.conversationList().openConversation(groupName); + await groupConversation.open(); await expect(pages.conversation().getMessage({content: groupMessage})).not.toBeVisible(); }); @@ -99,10 +102,10 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] await test.step('All data (chat history, contacts) are restored', async () => { await components.conversationSidebar().clickAllConversationsButton(); - await pages.conversationList().openConversation(groupName); + await groupConversation.open(); await expect(pages.conversation().getMessage({content: groupMessage})).toBeVisible(); - await pages.conversationList().openConversation(userB.fullName); + await userBConversation.open(); await expect(pages.conversation().getMessage({content: personalMessage})).toBeVisible(); }); }); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts index 6c686caf8c3..2fab4709c48 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts @@ -56,7 +56,7 @@ test( const response = await api.callingService.createInstance(member.password, member.email); callingServiceInstanceId = response.id; await api.callingService.setAcceptNextCall(callingServiceInstanceId); - await pages.conversationList().openConversation(channelName); + await pages.conversationList().getConversationLocator(channelName).open(); await pages.conversation().clickCallButton(); } catch (error: unknown) { console.error('Error during call initiation:', error); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts index 2459a9699e2..119135287f3 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts @@ -60,7 +60,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team owner confirm member left', async () => { - await ownerPages.conversationList().openConversation(conversation1); + await ownerPages.conversationList().getConversationLocator(conversation1).open(); await expect(ownerPages.conversation().systemMessages.filter({hasText: `${member.fullName} left`})).toBeVisible(); }); @@ -76,7 +76,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member confirms admin status', async () => { - await memberPages.conversationList().openConversation(conversation2); + await memberPages.conversationList().getConversationLocator(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect(memberPages.conversation().adminsList.filter({hasText: member.fullName})).toBeVisible(); }); @@ -88,12 +88,12 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member verifies they have been removed by the owner', async () => { - await memberPages.conversationList().openConversation(conversation2); + await memberPages.conversationList().getConversationLocator(conversation2).open(); await expect(memberPages.conversation().messageInput).not.toBeAttached(); }); await test.step('Team owner add member back to the same conversation', async () => { - await ownerPages.conversationList().openConversation(conversation2); + await ownerPages.conversationList().getConversationLocator(conversation2).open(); await expect( ownerPages.conversation().systemMessages.filter({hasText: `You removed ${member.fullName}`}), ).toBeVisible(); @@ -102,7 +102,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member confirms they have been added back to the conversation', async () => { - await memberPages.conversationList().openConversation(conversation2); + await memberPages.conversationList().getConversationLocator(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect( memberPages.conversation().systemMessages.filter({hasText: `${owner.fullName} added you to the conversation`}), @@ -110,12 +110,12 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team owner promote member to admin', async () => { - await ownerPages.conversationList().openConversation(conversation2); + await ownerPages.conversationList().getConversationLocator(conversation2).open(); await ownerPages.conversation().makeUserAdmin(member.fullName); }); await test.step('Team member confirms admin status', async () => { - await memberPages.conversationList().openConversation(conversation2); + await memberPages.conversationList().getConversationLocator(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect(memberPages.conversation().adminsList.filter({hasText: member.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts index 216da92e12c..f875dbc2e4f 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts @@ -40,10 +40,12 @@ test('Conversation Management', {tag: ['@TC-8636', '@crit-flow-web']}, async ({c ...memberPages.map(page => PageManager.from(page)), ]; + const conversation = ownerPageManager.webapp.pages.conversationList().getConversationLocator(conversationName); + await test.step('Team owner creates a group with all the five members', async () => { const {pages} = ownerPageManager.webapp; await createGroup(pages, conversationName, members); - await expect(pages.conversationList().getConversationLocator(conversationName)).toBeVisible(); + await expect(conversation).toBeVisible(); }); await test.step('Team owner sends a message in the conversation', async () => { @@ -64,7 +66,7 @@ test('Conversation Management', {tag: ['@TC-8636', '@crit-flow-web']}, async ({c await test.step('Team owner signed in to the application and verify messages', async () => { const {pages} = ownerPageManager.webapp; - await pages.conversationList().openConversation(conversationName); + await conversation.open(); await Promise.all( members.map(async member => { const message = pages.conversation().getMessage({content: `Hello team! ${member.firstName} here.`}); @@ -90,14 +92,14 @@ test('Conversation Management', {tag: ['@TC-8636', '@crit-flow-web']}, async ({c await test.step('Team owner open searched conversation', async () => { const {pages} = ownerPageManager.webapp; await pages.conversationList().searchConversationsInput.fill(conversationName); - await pages.conversationList().openConversation(conversationName); - await expect(pages.conversationList().getConversationLocator(conversationName)).toBeVisible(); - await pages.conversationList().openConversation(conversationName); + await conversation.open(); + await expect(conversation).toBeVisible(); + await conversation.open(); }); await test.step('Team owner leave conversation with clear history', async () => { const {pages, modals} = ownerPageManager.webapp; - const contextMenu = await pages.conversationList().getConversationLocator(conversationName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.leaveConversationButton.click(); await modals.leaveConversation().toggleCheckbox(); await modals.leaveConversation().clickConfirm(); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts index 09be028879f..1145a48cfd3 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts @@ -49,7 +49,7 @@ test( await test.step('Owner starts call', async () => { const {pages, components} = ownerPageManager.webapp; - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); await pages.conversation().startCall(); await expect(components.calling().callCell).toBeVisible(); @@ -57,7 +57,7 @@ test( await test.step('Member joins call and goes full screen', async () => { const {pages, components} = memberPageManager.webapp; - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); const memberCalling = components.calling(); await expect(memberCalling.callCell).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts index ae64e33ab1c..6bbc287bc51 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts @@ -51,7 +51,7 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe }); await test.step('Owner invites guest user to the group', async () => { - await ownerPages.conversationList().openConversation(conversationName); + await ownerPages.conversationList().getConversationLocator(conversationName).open(); await ownerPages.conversation().clickConversationTitle(); await ownerPages.conversationDetails().clickAddPeopleButton(); await ownerPages.conversationDetails().addUsersToConversation([guestUser.fullName]); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts index 372ea647350..d59d01297f4 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts @@ -44,10 +44,10 @@ test( const userAPages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(owner))).webapp.pages; await test.step('A sends text to owner', async () => { - await userAPages.conversationList().openConversation(owner.fullName); + await userAPages.conversationList().getConversationLocator(owner.fullName).open(); await userAPages.conversation().sendMessage('Hello Team Owner!'); - await ownerPages.conversationList().openConversation(userA.fullName); + await ownerPages.conversationList().getConversationLocator(userA.fullName).open(); await expect(ownerPages.conversation().getMessage({content: 'Hello Team Owner!'})).toBeVisible(); }); @@ -57,7 +57,7 @@ test( }); await test.step('Owner adds A to group chat and mentions him', async () => { - await ownerPages.conversationList().openConversation('Test Group'); + await ownerPages.conversationList().getConversationLocator('Test Group').open(); // Add user A to group chat await ownerPages.conversation().toggleGroupInformation(); @@ -69,7 +69,7 @@ test( }); await test.step('User A receives mention in group chat', async () => { - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); await expect(userAPages.conversation().getMessage({content: `@${userA.fullName}`})).toBeVisible(); }); }, diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts index 3e41e43a736..835dc35d0c6 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts @@ -57,12 +57,15 @@ test('Messages in 1:1', {tag: ['@TC-8750', '@crit-flow-web']}, async ({createTea // When a conversation between two non team members is created proteus will be used by default and later upgrade to mls. // To avoid loosing messages during the fast execution with playwright we wait for the upgrade is finished to open the MLS conversation. - await pages.conversationList().openConversation(memberB.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(memberB.fullName, {protocol: 'mls'}).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect(pages.conversation().getImageLocator(memberA)).toBeVisible(); }); await test.step('User B can see the image in the conversation', async () => { - await memberBPageManager.webapp.pages.conversationList().openConversation(memberA.fullName, {protocol: 'mls'}); + await memberBPageManager.webapp.pages + .conversationList() + .getConversationLocator(memberA.fullName, {protocol: 'mls'}) + .open(); // Verify that the image is visible in the conversation await expect(memberBPageManager.webapp.pages.conversation().getImageLocator(memberA)).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts index acab132fc04..ee24cf69358 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts @@ -54,21 +54,22 @@ test( await test.step('User A mentions User B in the channel', async () => { const {pages} = userAPageManager.webapp; - await pages.conversationList().openConversation(channelName); + await pages.conversationList().getConversationLocator(channelName).open(); await pages.conversation().sendMessageWithUserMention(userB.fullName, messageText); }); await test.step('User B should receive mention', async () => { const {pages} = userBPageManager.webapp; - await expect(pages.conversationList().getConversationLocator(channelName).mentionIndicator).toBeVisible(); + const conversation = pages.conversationList().getConversationLocator(channelName); + await expect(conversation.mentionIndicator).toBeVisible(); - await pages.conversationList().openConversation(channelName); + await conversation.open(); await expect(pages.conversation().getMessage({content: `@${userB.fullName} ${messageText}`})).toBeVisible(); }); await test.step('User A sends image', async () => { const {pages, components} = userAPageManager.webapp; - await pages.conversationList().openConversation(channelName); + await pages.conversationList().getConversationLocator(channelName).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect( diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts index c0d463f0ae2..257c254c185 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts @@ -52,21 +52,22 @@ test( await test.step('User A mentions User B in the group', async () => { const {pages} = userAPageManager.webapp; - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); await pages.conversation().sendMessageWithUserMention(userB.fullName, messageText); }); await test.step('User B should receive mention', async () => { const {pages} = userBPageManager.webapp; - await expect(pages.conversationList().getConversationLocator(conversationName).mentionIndicator).toBeVisible(); + const conversation = pages.conversationList().getConversationLocator(conversationName); + await expect(conversation.mentionIndicator).toBeVisible(); - await pages.conversationList().openConversation(conversationName); + await conversation.open(); await expect(pages.conversation().getMessage({content: `@${userB.fullName} ${messageText}`})).toBeVisible(); }); await test.step('User A sends image', async () => { const {pages, components} = userAPageManager.webapp; - await pages.conversationList().openConversation(conversationName); + await pages.conversationList().getConversationLocator(conversationName).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect( diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts index cc089d7d86a..8ff53195cc3 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts @@ -46,7 +46,7 @@ test( const {pages} = userAPageManager.webapp; await api.callingService.setAcceptNextCall(callingServiceInstanceId); - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversation().clickCallButton(); }); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts index 7c0ab37ae18..94d2295db01 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts @@ -90,7 +90,7 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A sends a connection request to personal user B', async () => { const {pages, modals} = pageManagerA.webapp; await modals.userProfile().clickConnectButton(); - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await expect(pages.outgoingConnection().uniqueUsernameOutgoing).toContainText(userB.username); await expect(pages.outgoingConnection().getPendingConnectionIconLocator(userB.fullName)).toBeVisible(); }); @@ -103,12 +103,12 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A send message to personal user B', async () => { const {pages} = pageManagerA.webapp; - await pages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage(`Hello! ${userA.firstName} here.`); }); await test.step('Personal user B can see the message from user A', async () => { - await pageManagerB.webapp.pages.conversationList().openConversation(userA.fullName); + await pageManagerB.webapp.pages.conversationList().getConversationLocator(userA.fullName).open(); await expect( pageManagerB.webapp.pages.conversation().getMessage({content: `Hello! ${userA.firstName} here.`}), ).toBeVisible(); @@ -135,7 +135,7 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await pageManagerC.webapp.components.conversationSidebar().clickConnectButton(); await pageManagerC.webapp.pages.startUI().selectUsers(userA.username); await pageManagerC.webapp.modals.userProfile().clickConnectButton(); - await pageManagerC.webapp.pages.conversationList().openConversation(userA.fullName); + await pageManagerC.webapp.pages.conversationList().getConversationLocator(userA.fullName).open(); await expect(pageManagerC.webapp.pages.outgoingConnection().uniqueUsernameOutgoing).toContainText(userA.username); await expect( pageManagerC.webapp.pages.outgoingConnection().getPendingConnectionIconLocator(userA.fullName), @@ -150,12 +150,12 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A send message to personal user C', async () => { const {pages} = pageManagerA.webapp; - await pages.conversationList().openConversation(userC.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userC.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage(`Hello! ${userA.firstName} here.`); }); await test.step('Personal user C can see the message from user A', async () => { - await pageManagerC.webapp.pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await pageManagerC.webapp.pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await expect( pageManagerC.webapp.pages.conversation().getMessage({content: `Hello! ${userA.firstName} here.`}), ).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts index d286b5ea513..a897b404729 100644 --- a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts @@ -153,7 +153,7 @@ test.describe('Deep Links', () => { ); await test.step('User B sends all profile links to User A', async () => { - await userBPages.conversationList().openConversation(userA.fullName); + await userBPages.conversationList().getConversationLocator(userA.fullName).open(); const userLabels = ['A', 'B', 'C', 'D']; @@ -164,17 +164,17 @@ test.describe('Deep Links', () => { await test.step('User B creates group and sends conversation join link to User A', async () => { await createGroup(userBPages, groupName, []); - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await userBPages.conversationDetails().openGuestOptions(); const conversationJoinLink = await userBPages.guestOptions().createLink(); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(`Group conversation: ${conversationJoinLink}`); }); await test.step('User A verifies information for every user profile modal', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); // Verify information for User A await userAPages.conversation().getMessage({content: 'User A:'}).getByRole('link').click(); diff --git a/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts b/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts index cd8638cafa1..7fe985d9438 100644 --- a/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts @@ -43,7 +43,7 @@ test.describe('Delete', () => { const deviceA = (await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB)))).webapp.pages; const deviceB = (await PageManager.from(createPage(withLogin(userA, {confirmNewHistory: true})))).webapp.pages; - await deviceB.conversationList().openConversation(userB.fullName); + await deviceB.conversationList().getConversationLocator(userB.fullName).open(); await deviceA.conversation().sendMessage('Test Message'); @@ -67,14 +67,14 @@ test.describe('Delete', () => { await test.step('Create test group', async () => { await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversationLocator('Test Group').open(); }); let messageA: Locator; let messageB: Locator; await test.step('Send message from user A to B', async () => { - await userAPages.conversationList().openConversation('Test Group'); await userAPages.conversation().sendMessage('Test Message'); messageA = userAPages.conversation().getMessage({sender: userA}); @@ -102,7 +102,7 @@ test.describe('Delete', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); const systemMessage = userAPages.conversation().systemMessages.last(); @@ -119,9 +119,9 @@ test.describe('Delete', () => { async ({createPage}) => { const {components, pages} = (await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB)))) .webapp; + const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); let message: Locator; - await test.step('Send and delete message', async () => { await pages.conversation().sendMessage('Test Message'); @@ -133,19 +133,19 @@ test.describe('Delete', () => { }); await test.step('Archive conversation', async () => { - const contextMenu = await pages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); }); await test.step('Unarchive conversation', async () => { await components.conversationSidebar().clickArchive(); - const contextMenu = await pages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.unarchiveButton.click(); await components.conversationSidebar().clickAllConversationsButton(); }); await test.step('Verify message remains deleted', async () => { - await pages.conversationList().openConversation(userB.fullName); + await conversation.open(); await expect(message).not.toBeAttached(); }); }, @@ -162,13 +162,13 @@ test.describe('Delete', () => { const userAPages = PageManager.from(userAPage).webapp.pages; let userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const messageA = userAPages.conversation().getMessage({sender: userA}); await expect(messageA).toContainText('Test Message'); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); let messageB = userBPages.conversation().getMessage({sender: userA}); await expect(messageB).toContainText('Test Message'); @@ -176,7 +176,7 @@ test.describe('Delete', () => { await userAPages.conversation().deleteMessage(messageA, 'Everyone'); userBPages = (await PageManager.from(createPage(context, withLogin(userB)))).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName); + await userBPages.conversationList().getConversationLocator(userA.fullName).open(); messageB = userBPages.conversation().getMessage({sender: userA}); @@ -194,8 +194,8 @@ test.describe('Delete', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const messageA = userAPages.conversation().getMessage({sender: userA}); @@ -224,14 +224,14 @@ test.describe('Delete', () => { await test.step('Create test group', async () => { await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); }); let messageA: Locator; let messageB: Locator; await test.step('Send and delete message from user A to B', async () => { - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().sendMessage('Test Message'); messageA = userAPages.conversation().getMessage({sender: userA}); @@ -328,8 +328,8 @@ test.describe('Delete', () => { const userBPages = PageManager.from(pageB).webapp.pages; await test.step('Await mls conversation creation', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); }); await test.step('Execute send action', async () => { @@ -367,11 +367,11 @@ test.describe('Delete', () => { await test.step('Create group as second conversation', async () => { // We need to create a second conversation in order to switch to it to ensure the unread marker can be shown on the not open conversation await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); }); await test.step('Send message from user A to B', async () => { - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().sendMessage('Test Message'); }); @@ -390,7 +390,7 @@ test.describe('Delete', () => { const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); await expect(conversation.getByTestId('status-unread')).not.toBeVisible(); - await userBPages.conversationList().openConversation(userA.fullName); + await conversation.open(); const message = userBPages.conversation().getMessage({sender: userA}); await expect(message).not.toBeAttached(); }); diff --git a/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts b/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts index 7ee67820a70..a5b0502d2ca 100644 --- a/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts @@ -80,8 +80,8 @@ test.describe('Conversations', () => { }); await test.step('User A sends an image to User B in a group conversation', async () => { - await userAPages.conversationList().openConversation(conversationName); - await userBPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().clickSendMessage(); @@ -127,11 +127,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a multipart message in a group conversation', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().setMessageInput(initialMessageText); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); const multipartMessage = userBPages.conversation().getMessage({sender: userA}); await expect(multipartMessage).toContainText(initialMessageText); @@ -168,11 +168,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a multipart message to User B in a group conversation', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().setMessageInput(initialMessageText); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); const multipartMessage = userBPages.conversation().getMessage({sender: userA}); await expect(multipartMessage).toContainText(initialMessageText); @@ -208,11 +208,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a message with assets in a group conversation', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().clickShareFile(videoFilePath); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await expect(getImageInMultipartMessageLocator(userBPages.conversation(), userA)).toBeVisible(); await expect(getVideoInMultipartMessageLocator(userBPages.conversation(), userA)).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts index 73cd23d3037..0a863fde488 100644 --- a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts @@ -51,7 +51,7 @@ test.describe('Edit', () => { const pages = (await PageManager.from(createPage(withLogin(userA)))).webapp.pages; await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await pages.conversation().sendMessage('Test Message'); const message = pages.conversation().getMessage({sender: userA}); @@ -73,7 +73,7 @@ test.describe('Edit', () => { // Device 2 is intentionally created after device 1 to ensure the history info warning is confirmed const deviceB = (await PageManager.from(createPage(withLogin(userA, {confirmNewHistory: true})))).webapp.pages; - await deviceB.conversationList().openConversation(userB.fullName); + await deviceB.conversationList().getConversationLocator(userB.fullName).open(); await deviceA.conversation().sendMessage('Message from device 1'); @@ -97,8 +97,8 @@ test.describe('Edit', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const message = userBPages.conversation().getMessage({sender: userA}); @@ -134,25 +134,25 @@ test.describe('Edit', () => { await test.step('Create group as second conversation', async () => { // We need to create a second conversation in order to switch to it to ensure the unread marker can be shown on the not open conversation await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); }); await test.step('Send message from user A to B', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); }); await test.step('Check user B has a unread conversation with A containing the sent message', async () => { - const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); + const conversation = userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); await expect(conversation.unreadIndicator).toBeVisible(); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await conversation.open(); await expect(conversation).toContainText('Test Message'); await expect(conversation.unreadIndicator).not.toBeVisible(); }); await test.step("Open group conversation to ensure new messages won't be read immediately", async () => { - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await expect(userBPages.conversation().conversationTitle).toHaveText('Test Group'); }); @@ -167,7 +167,7 @@ test.describe('Edit', () => { const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); await expect(conversation.unreadIndicator).not.toBeVisible(); - await userBPages.conversationList().openConversation(userA.fullName); + await conversation.open(); await expect(userBPages.conversation().getMessage({sender: userA})).toContainText('Edited Message'); }); }, @@ -182,8 +182,8 @@ test.describe('Edit', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test'); const sentMessage = userAPages.conversation().getMessage({sender: userA}); @@ -207,7 +207,7 @@ test.describe('Edit', () => { const pages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(pages, 'Test Group', [userB]); // The message detail view is only available for group conversations - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await pages.conversation().sendMessage('Test message'); const message = pages.conversation().getMessage({sender: userA}); diff --git a/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts b/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts index ae7de74b724..f88b3ab7851 100644 --- a/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts @@ -72,7 +72,7 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Step 1: User A opens 1:1 conversation with User B - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); // Step 2: User A moves 1:1 conversation with User B into new custom folder await createCustomFolder(userAPageManager, userB.fullName, customFolderName); // Step 3: 1:1 conversation with User B is in the custom folder @@ -90,7 +90,7 @@ test.describe('Folders', () => { await createGroup(userAPages, conversationName, [userB]); // Step 1: User A opens group conversation with User B - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); // Step 2: User A moves group conversation with User B in new custom folder await createCustomFolder(userAPageManager, conversationName, customFolderName); // Step 3: Group conversation with User B is in the custom folder @@ -157,14 +157,11 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Preconditions: Conversation is in custom folder - await userAPages.conversationList().openConversation(userB.fullName); + const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await createCustomFolder(userAPageManager, userB.fullName, customFolderName); await test.step("User A clicks 'remove from custom folder' button", async () => { - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(userB.fullName) - .openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.getByRole('button', {name: `Remove from "${customFolderName}"`}).click(); }); @@ -174,10 +171,7 @@ test.describe('Folders', () => { }); await test.step('Custom Folder Name is no longer visible in the Move-To-Menu', async () => { - const contextMenu = await userAPages - .conversationList() - .getConversationLocator(userB.fullName) - .openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.moveToButton.click(); await expect(contextMenu.getByRole('button', {name: customFolderName})).not.toBeVisible(); }); @@ -188,9 +182,9 @@ test.describe('Folders', () => { const {pages: userAPages, modals: userAModals} = userAPageManager.webapp; // Step 1: User A opens 1:1 conversation with User B - await userAPages.conversationList().openConversation(userB.fullName); + const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); // Step 2: User A wants to move 1:1 conversation with User B into custom folder - const contextMenu = await userAPages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.moveToButton.click(); await contextMenu.getByRole('button', {name: 'Create new folder'}).click(); @@ -204,17 +198,17 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Preconditions: Conversation is in custom folder - await userAPages.conversationList().openConversation(userB.fullName); + const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await createCustomFolder(userAPageManager, userB.fullName, customFolderName); await test.step('User A removes conversation with User B from custom folder', async () => { - const contextMenu = await userAPages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.getByRole('button', {name: `Remove from "${customFolderName}"`}).click(); }); await test.step('User A tries to move conversation with User B back into the folder, custom folder name should no longer be visible in the folder menu', async () => { - await userAPages.conversationList().openConversation(userB.fullName); - const contextMenu = await userAPages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + await conversation.open(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.moveToButton.click(); await expect(contextMenu.getByRole('button', {name: customFolderName})).not.toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts b/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts index 59fa5f4cf5c..a7e7448acb6 100644 --- a/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts @@ -41,7 +41,7 @@ test.describe('Group Conversation', () => { const userAPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(userAPages, groupName, []); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().sendMessage('Message'); const message = userAPages.conversation().getMessage({content: 'Message'}); @@ -95,14 +95,13 @@ test.describe('Group Conversation', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, groupName, [userB, userC]); - const adminGroupLocator = userAPages.conversationList().getConversationLocator(groupName); + const adminGroupLocator = await userAPages.conversationList().getConversationLocator(groupName).open(); const memberGroupLocator = userBPages.conversationList().getConversationLocator(groupName); // Pre-condition: Ensure the member can see the group before the creator deletes it await expect(memberGroupLocator).toBeVisible(); // User A initiate group deletion - await userAPages.conversationList().openConversation(groupName); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().deleteGroupButton.click(); @@ -145,11 +144,11 @@ test.describe('Group Conversation', () => { await blankTab.goto('about:blank'); await blankTab.bringToFront(); - await userAPages.conversationList().openConversation(groupName); + const userAConversation = await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().deleteGroupButton.click(); await userAModals.confirm().actionButton.click(); - await expect(userAPages.conversationList().getConversationLocator(groupName)).not.toBeAttached({timeout: 20_000}); + await expect(userAConversation).not.toBeAttached({timeout: 20_000}); await expect .poll(() => getUserBNotifications()) @@ -189,7 +188,7 @@ test.describe('Group Conversation', () => { for (const group of groups) { const {pages, modals} = PageManager.from(group.owner).webapp; - await pages.conversationList().openConversation(group.name); + await pages.conversationList().getConversationLocator(group.name).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversationDetails().deleteGroupButton.click(); await modals.confirm().actionButton.click(); @@ -213,7 +212,7 @@ test.describe('Group Conversation', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, groupName, [userA]); - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); // User A leaves conversation through options menu from conversation list const contextMenu = await userAPages.conversationList().getConversationLocator(groupName).openContextMenu(); @@ -231,7 +230,7 @@ test.describe('Group Conversation', () => { const pages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(pages, groupName, [userB, userC]); - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().toggleGroupInformation(); await expect(pages.conversationDetails().groupMembers.filter({hasText: userB.fullName})).toBeVisible(); @@ -253,7 +252,7 @@ test.describe('Group Conversation', () => { const pages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(pages, groupName, [userB]); - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().toggleGroupInformation(); await expect(pages.conversationDetails().groupMembers.filter({hasText: userB.fullName})).toBeVisible(); @@ -272,7 +271,7 @@ test.describe('Group Conversation', () => { ]); await createGroup(userAPages, groupName, [userB]); - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await expect(userBPages.conversationDetails().groupAdmins.filter({hasText: userA.fullName})).toBeVisible(); @@ -291,12 +290,12 @@ test.describe('Group Conversation', () => { await test.step('Setup: Create group and promote User B to Admin', async () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversation().makeUserAdmin(userB.fullName); // Verify User B is an admin - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await expect(userBPages.conversationDetails().groupAdmins.filter({hasText: userB.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts index 409c6d7fab2..2f3b44cdcc3 100644 --- a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts @@ -14,7 +14,7 @@ import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActio */ const generateGroupGuestsLink = async (pages: PageManager['webapp']['pages'], groupName: string, password?: string) => { - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().toggleGroupInformation(); await pages.conversationDetails().openGuestOptions(); @@ -75,7 +75,7 @@ test.describe('Guestroom', () => { const {pages, modals} = PageManager.from(userAPage).webapp; await createGroup(pages, groupName, []); - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await test.step('User A sees an error message when trying to create a password secured link with a weak password', async () => { await pages.conversation().toggleGroupInformation(); @@ -183,7 +183,7 @@ test.describe('Guestroom', () => { await test.step('Owner creates a group with guest', async () => { await createGroup(ownerPages, groupName, [guestUser]); - await ownerPages.conversationList().openConversation(groupName); + await ownerPages.conversationList().getConversationLocator(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); await expect(ownerPages.conversationDetails().groupMembers.filter({hasText: guestUser.fullName})).toBeVisible(); }); @@ -214,7 +214,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().openConversation(groupName); + await ownerPages.conversationList().getConversationLocator(groupName).open(); // Owner creates a guest link await ownerPages.conversation().toggleGroupInformation(); @@ -243,7 +243,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(ownerPage).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().openConversation(groupName); + await ownerPages.conversationList().getConversationLocator(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); await ownerPages.conversationDetails().openGuestOptions(); @@ -276,7 +276,7 @@ test.describe('Guestroom', () => { await ownerPages.groupCreation().setGroupName(groupName); await ownerPages.groupCreation().clickCreateGroupButton(); - await ownerPages.conversationList().openConversation(groupName); + await ownerPages.conversationList().getConversationLocator(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); // UserA sees guest options label shows ON in conversation details @@ -344,7 +344,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().openConversation(groupName); + await ownerPages.conversationList().getConversationLocator(groupName).open(); await verify(ownerPages); await ownerPages.conversation().invitePeopleButton.click(); diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 5e905eb54ea..f3ef9df75ed 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -53,9 +53,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -104,7 +104,7 @@ test.describe('History Backup', () => { await test.step('Validate conversation is still visible with all messages after restoring backup', async () => { await userAComponents2.conversationSidebar().allConversationsButton.click(); - await userAPages2.conversationList().openConversation(conversationName); + await userAPages2.conversationList().getConversationLocator(conversationName).open(); await expect(userAPages2.conversation().getMessage({sender: userB})).toContainText(messageUserB); await expect(userAPages2.conversation().getMessage({sender: userA})).toContainText(messageUserA); }); @@ -127,9 +127,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -177,9 +177,9 @@ test.describe('History Backup', () => { const renamedConversationName = 'renamedConversationName'; await test.step('User A and B write in their group conversation', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -189,7 +189,7 @@ test.describe('History Backup', () => { await userAComponents.conversationSidebar().clickPreferencesButton(); backupName = await createAndSaveBackup(testInfo, userAPageManager); await userAComponents.conversationSidebar().allConversationsButton.click(); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); }); await test.step('User B renames group conversation', async () => { @@ -208,7 +208,7 @@ test.describe('History Backup', () => { await expect(userAPages.conversationList().getConversationLocator(renamedConversationName)).toBeVisible(); // User A sees system message that User B had renamed the conversation - await userAPages.conversationList().openConversation(renamedConversationName); + await userAPages.conversationList().getConversationLocator(renamedConversationName).open(); const renamedSystemMessage = userAPages .conversation() .systemMessages.filter({hasText: `${userB.fullName} renamed the conversation`}); @@ -234,9 +234,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -246,7 +246,7 @@ test.describe('History Backup', () => { }); await test.step('User A archives 1:1 conversation with User B', async () => { - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().conversationInfoButton.click(); await userAPages.conversationDetails().archiveButton.click(); }); @@ -271,10 +271,8 @@ test.describe('History Backup', () => { await test.step('Validate muted and archived state are the same', async () => { await userAComponents.conversationSidebar().allConversationsButton.click(); - await userAPages.conversationList().openConversation(conversationName); - await expect( - userAPages.conversationList().getConversationLocator(conversationName).mutedIndicator, - ).toBeVisible(); + const conversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); + await expect(conversation.mutedIndicator).toBeVisible(); await userAComponents.conversationSidebar().archiveButton.click(); const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); @@ -299,9 +297,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -348,9 +346,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and User B write messages to each other', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -393,9 +391,9 @@ test.describe('History Backup', () => { await createGroup(userBPages, conversationName, [userA]); await test.step('User A and User B write messages to each other', async () => { - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); await expect(userAPages.conversation().messageItems).toHaveCount(2); }); @@ -423,7 +421,7 @@ test.describe('History Backup', () => { await test.step('Validate user A cannot send messages in the left group', async () => { await userAComponents.conversationSidebar().allConversationsButton.click(); - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await expect(userAPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); await expect(userAPages.conversation().messageInput).toBeHidden(); }); diff --git a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts index 4151fe0719a..7ab882fb1d6 100644 --- a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts @@ -46,7 +46,7 @@ test.describe('In Conversation Search', () => { const userBPages = PageManager.from(userBPage).webapp.pages; // Preconditions: User B sends media from all categories (images, links, audio and files) - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // Image await shareAssetHelper(getImageFilePath(), userBPage, userBPage.getByRole('button', {name: 'Add picture'})); // Audio @@ -54,7 +54,7 @@ test.describe('In Conversation Search', () => { // File await shareAssetHelper(getTextFilePath(), userBPage, userBPage.getByRole('button', {name: 'Add file'})); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toHaveCount(3); await userAPages.conversation().searchButton.click(); @@ -77,11 +77,11 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().enableSelfDeletingMessages(); await userBPages.conversation().sendMessage('Gone in 10s'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); await userAPage.waitForTimeout(10_000); @@ -106,11 +106,11 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().enableSelfDeletingMessages(); await userBPages.conversation().sendMessage('Gone in 10s'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); await userAPages.conversation().searchButton.click(); @@ -138,8 +138,8 @@ test.describe('In Conversation Search', () => { const conversationName = 'Test Group'; await createGroup(userAPages, conversationName, [userB]); - await userBPages.conversationList().openConversation(conversationName); - await userAPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversationLocator(conversationName).open(); for (let imageCount = 0; imageCount < 10; imageCount++) { await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); @@ -167,10 +167,10 @@ test.describe('In Conversation Search', () => { const {pages: userAPages, modals: userAModals} = PageManager.from(pageA).webapp; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageA, pageA.getByRole('button', {name: 'Add picture'})); await userAPages.conversation().searchButton.click(); @@ -186,10 +186,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // Step: User B sends several links - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); // Step: User B sends several links await userAPages.conversation().searchButton.click(); @@ -205,13 +205,13 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(pageA).webapp.pages; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); for (let fileCount = 0; fileCount < 10; fileCount++) { await shareAssetHelper(getTextFilePath(), pageB, pageB.getByRole('button', {name: 'Add file'})); } - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); for (let fileCount = 0; fileCount < 10; fileCount++) { await shareAssetHelper(getTextFilePath(), pageA, pageA.getByRole('button', {name: 'Add file'})); } @@ -232,10 +232,10 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(pageA).webapp.pages; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); const messageWithImage = userAPages.conversation().getMessage({sender: userB}); await expect(messageWithImage).toBeVisible(); @@ -255,10 +255,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('User A Message'); await userAPages.conversation().searchButton.click(); @@ -283,10 +283,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Here is a link: [LinkPreview]https://www.kaufland.de'); await userAPages.conversation().searchButton.click(); @@ -304,10 +304,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('https://www.kaufland.de'); await expect(userBPages.conversation().getMessage({content: 'https://www.kaufland.de'})).toBeVisible(); @@ -326,10 +326,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message: Papaya'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('User A message: Guava'); const messageUserB = userBPages.conversation().getMessage({sender: userB}); @@ -351,13 +351,13 @@ test.describe('In Conversation Search', () => { ]); // User B sends the first (older) message - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Older Message from User B'); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); // User A sends the second (newer) message - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Newer Message from User A'); await userAPages.conversation().searchButton.click(); @@ -378,8 +378,8 @@ test.describe('In Conversation Search', () => { const specialWord = 'Crème brûlée'; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(`Message with diacritical letter: ${specialWord}`); await expect(userAPages.conversation().getMessage({content: specialWord})).toBeAttached(); @@ -397,8 +397,8 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); @@ -419,10 +419,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Papaya'); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({content: 'Papaya'})).toBeVisible(); await userAPages.conversation().sendMessage(`Message from User A: 1`); await userAPages.conversation().sendMessage('Empty\n'.repeat(50)); diff --git a/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts index 2529e3c9962..9c34098390a 100644 --- a/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts @@ -37,8 +37,8 @@ test.describe('Link Preview', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const linkConfigs = [ { diff --git a/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts b/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts index 14dc0472e18..da2528a63ad 100644 --- a/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts @@ -140,16 +140,12 @@ test.describe('Localization', () => { await pages.startUI().component.getByRole('button', {name: userB.fullName}).click(); await modals.userProfile().startConversationButton.click(); - await pages.conversationList().openConversation(userB.fullName); + const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); const messagePlaceholder = page.locator('[data-uie-name="input-placeholder"]'); await expect(messagePlaceholder).toHaveText(deTranslations['tooltipConversationInputPlaceholder']); await components.conversationSidebar().allConversationsButton.click(); - await pages - .conversationList() - .getConversationLocator(userB.fullName) - .getByRole('button', {name: deTranslations['accessibility.conversationOptionsMenu']}) - .click(); + await conversation.getByRole('button', {name: deTranslations['accessibility.conversationOptionsMenu']}).click(); const menuList = page.getByRole('menu').getByRole('menuitem'); diff --git a/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts b/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts index 5cd6503934a..3d250ca3b80 100644 --- a/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts @@ -27,8 +27,8 @@ test.describe('Logs', () => { const messageA = 'Hello from UserA! This is a secret message.'; const messageB = 'Hi from UserB! No logging allowed.'; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageA); await userBPages.conversation().sendMessage(messageB); diff --git a/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts b/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts index fbebb4efa82..eaee7860521 100644 --- a/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts @@ -68,8 +68,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage(message); for (const pages of [userAPages, userBPages]) { @@ -88,7 +88,7 @@ test.describe('Markdown', () => { const userAPageManager = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))); const {pages, modals} = userAPageManager.webapp; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().sendTypedMessage(targetUrl); const message = pages.conversation().getMessage({sender: userA}); @@ -110,8 +110,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages), PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const longCodeMessage = '```\nconst a = 5;\nconst b = 10;\nconsole.log(a + b);\n```'; await userAPages.conversation().sendMessage(longCodeMessage); @@ -129,8 +129,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage('**Bold**, *Italic* and `Code`'); for (const pages of [userAPages, userBPages]) { @@ -148,8 +148,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage('Start **Bold** Message'); const sentMessageA = userAPages.conversation().getMessage({sender: userA}); @@ -179,8 +179,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const linkText = 'Wire Website'; const url = 'https://wire.com'; diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index a1b7afb89c8..286243724a0 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -23,11 +23,11 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Mention Group', [userB]); // User A sends a message with a mention - await userAPages.conversationList().openConversation('Mention Group'); + await userAPages.conversationList().getConversationLocator('Mention Group').open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey'); // User B receives the message - await userBPages.conversationList().openConversation('Mention Group'); + await userBPages.conversationList().getConversationLocator('Mention Group').open(); const messageOnUserB = userBPages.conversation().getMessage({content: 'Hey', sender: userA}); await expect(messageOnUserB).toBeVisible(); const mentionOnUserB = messageOnUserB.getByRole('button', {name: `@${userB.fullName}`}); @@ -45,11 +45,11 @@ test.describe('Mention', () => { ]); await createGroup(userAPages, 'Multi-Mention Group', [userB, userC]); - await userBPages.conversationList().openConversation('Multi-Mention Group'); - await userCPages.conversationList().openConversation('Multi-Mention Group'); + await userBPages.conversationList().getConversationLocator('Multi-Mention Group').open(); + await userCPages.conversationList().getConversationLocator('Multi-Mention Group').open(); // User A sends a message with multiple mentions - await userAPages.conversationList().openConversation('Multi-Mention Group'); + await userAPages.conversationList().getConversationLocator('Multi-Mention Group').open(); const conversationPageA = userAPages.conversation(); await conversationPageA.messageInput.fill('Hello '); @@ -79,9 +79,9 @@ test.describe('Mention', () => { await test.step('Create group', async () => { await createGroup(userAPages, 'Edit-Mention Group', [userB, userC]); - await userAPages.conversationList().openConversation('Edit-Mention Group'); - await userBPages.conversationList().openConversation('Edit-Mention Group'); - await userCPages.conversationList().openConversation('Edit-Mention Group'); + await userAPages.conversationList().getConversationLocator('Edit-Mention Group').open(); + await userBPages.conversationList().getConversationLocator('Edit-Mention Group').open(); + await userCPages.conversationList().getConversationLocator('Edit-Mention Group').open(); }); await test.step('User A sends an initial message mentioning userB', async () => { @@ -122,8 +122,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); @@ -145,8 +145,8 @@ test.describe('Mention', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User A sends a self deleting message with a mention await userAPages.conversation().enableSelfDeletingMessages(); @@ -174,8 +174,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User A sends a message with the same person mentioned twice const conversationPageA = userAPages.conversation(); @@ -199,7 +199,7 @@ test.describe('Mention', () => { {tag: ['@TC-3493', '@regression']}, async ({createPage}) => { const userAPages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); const conversationPageA = userAPages.conversation(); await conversationPageA.messageInput.fill(''); // Clear input @@ -223,8 +223,8 @@ test.describe('Mention', () => { createPage(withLogin(userA), withConnectedUser(userB)).then(page => PageManager.from(page).webapp.pages), createPage(withLogin(userB)).then(page => PageManager.from(page).webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const conversationPageA = userAPages.conversation(); @@ -256,8 +256,9 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Draft Group', [userB]); const conversationPageA = userAPages.conversation(); + const draftGroupConversation = userAPages.conversationList().getConversationLocator('Draft Group'); await test.step('Draft a message with a mention in the group chat', async () => { - await userAPages.conversationList().openConversation('Draft Group'); + await draftGroupConversation.open(); await conversationPageA.messageInput.fill('Draft message with '); await conversationPageA.mentionUser(userB.fullName); @@ -266,12 +267,12 @@ test.describe('Mention', () => { }); await test.step('Switch to the 1:1 chat', async () => { - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await expect(conversationPageA.messageInput).toBeEmpty(); }); await test.step('Switch back to the group chat and verify the draft with the mention is preserved', async () => { - await userAPages.conversationList().openConversation('Draft Group'); + await draftGroupConversation.open(); await expect(conversationPageA.messageInput).toHaveText(`Draft message with @${userB.fullName}`); const preservedMention = conversationPageA.messageInput.getByTestId('item-input-mention'); @@ -287,8 +288,8 @@ test.describe('Mention', () => { pm => pm.webapp.pages, ); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User A sends a message with a mention to User B await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); @@ -312,10 +313,10 @@ test.describe('Mention', () => { // Create and open a group conversation for userB to ensure the message from A won't be read immediately await createGroup(userBPages, 'Distraction Group', [userC]); - await userBPages.conversationList().openConversation('Distraction Group'); + await userBPages.conversationList().getConversationLocator('Distraction Group').open(); // User A opens conversation with user B and sends a message with mention - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey, you have an unread mention!'); // User B is in the 'Distraction Group', so the conversation with user A is unread. @@ -337,11 +338,11 @@ test.describe('Mention', () => { await test.step('Create and open a distraction group conversation for User B', async () => { // userA creates the group, userB opens it. This is the distraction. await createGroup(userAPages, 'Distraction Group', [userB]); - await userBPages.conversationList().openConversation('Distraction Group'); + await userBPages.conversationList().getConversationLocator('Distraction Group').open(); }); await test.step('User A tries to call User B in 1:1 conversation but B declines', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().startCall(); await userBPages.calling().leaveCallButton.click(); await userAPages.calling().leaveCallButton.click(); @@ -376,8 +377,8 @@ test.describe('Mention', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName); const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); @@ -397,8 +398,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(`@${userB.fullName} Hello`); for (const pages of [userAPages, userBPages]) { @@ -414,7 +415,7 @@ test.describe('Mention', () => { {tag: ['@TC-3532', '@regression']}, async ({createPage}) => { const {pages} = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().messageInput.pressSequentially(`test@${userB.firstName}`); await expect(pages.conversation().mentionSuggestions).toHaveCount(0); @@ -428,7 +429,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); // It should be possible to mention userB as he's part of the group await pages.conversation().messageInput.pressSequentially(`@${userB.firstName}`); @@ -448,7 +449,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [userB, userC]); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); // It should be possible to mention userB as he's part of the group await pages.conversation().messageInput.pressSequentially(`@`); @@ -467,7 +468,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [memberWithStrangeName]); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await pages.conversation().messageInput.pressSequentially('@Gunter'); await expect(pages.conversation().mentionSuggestions).toHaveCount(1); @@ -483,7 +484,7 @@ test.describe('Mention', () => { await createGroup(pages, 'Test Group', []); await test.step('Create guest link for group & join as guest user', async () => { - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); await pages.conversation().clickConversationTitle(); const link = await pages.conversationDetails().createGuestLink(); await createPage(withGuestUser(link, 'Guest User')); @@ -523,12 +524,12 @@ test.describe('Mention', () => { }); await test.step("UserB mentions otherUser in the group although they're not connected", async () => { - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await userBPages.conversation().sendMessageWithUserMention(otherUser.fullName); }); await test.step('OtherUser receives the message from userB including the mention', async () => { - await otherUserPages.conversationList().openConversation('Test Group'); + await otherUserPages.conversationList().getConversationLocator('Test Group').open(); const mentionInMessage = otherUserPages .conversation() .getMessage({sender: userB}) @@ -546,7 +547,7 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Test Group', [userB, userC]); await test.step('UserA removes userB from the group', async () => { - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().conversationTitle.click(); await userAPages.conversationDetails().openParticipantDetails(userB.fullName); await userAPages.participantDetails().removeFromGroup(); diff --git a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts index 3c8a9deb5d7..ea4556450cd 100644 --- a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts @@ -27,7 +27,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // Send message from A to B in 1on1 conversation - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); // Check the notifications B received to contain the message from A @@ -59,20 +59,20 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // Archive conversation for user B - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - const contextMenu = await userBPages + const conversation = await userBPages .conversationList() .getConversationLocator(userA.fullName, {protocol: 'mls'}) - .openContextMenu(); + .open(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); // Send message from A to B in 1on1 conversation - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Hello'); // Open the archived conversation and ensure the message was received await userBComponents.conversationSidebar().clickArchive(); - await userBPages.conversationList().openConversation(userA.fullName); + await conversation.open(); await expect(userBPages.conversation().getMessage({sender: userA})).toBeVisible(); // Check that B did not receive any more notifications @@ -86,11 +86,11 @@ test.describe('Notifications', () => { async ({createPage}) => { const pages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages; - const contextMenu = await pages.conversationList().getConversationLocator(userB.fullName).openContextMenu(); + const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.notificationsButton.click(); await pages.conversationDetails().selectNotificationsLevel('Nothing'); - const conversation = pages.conversationList().getConversationLocator(userB.fullName); await expect(conversation.mutedIndicator).toBeVisible(); }, ); @@ -139,7 +139,8 @@ test.describe('Notifications', () => { // Depending on the current test case mute either the group or the 1on1 await userBPages .conversationList() - .openConversation(conversationType === 'group' ? 'Test Group' : userA.fullName); + .getConversationLocator(conversationType === 'group' ? 'Test Group' : userA.fullName) + .open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().notificationsButton).toContainText('Everything'); @@ -153,7 +154,8 @@ test.describe('Notifications', () => { // User A sends a message to the muted conversation await userAPages .conversationList() - .openConversation(conversationType === 'group' ? 'Test Group' : userB.fullName); + .getConversationLocator(conversationType === 'group' ? 'Test Group' : userB.fullName) + .open(); await userAPages.conversation().sendMessage('Test Message'); await expect.poll(() => getUserBNotifications()).toHaveLength(0); }, @@ -170,21 +172,20 @@ test.describe('Notifications', () => { ]); // User B mutes the conversation with User A - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - const contextMenu = await userBPages + const conversation = await userBPages .conversationList() .getConversationLocator(userA.fullName, {protocol: 'mls'}) - .openContextMenu(); + .open(); + const contextMenu = await conversation.openContextMenu(); await contextMenu.notificationsButton.click(); await userBPages.conversationDetails().selectNotificationsLevel('Nothing'); // User A initiates a call to User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); // Verify that User B sees a "Join" button on the muted conversation - const {joinCallButton} = userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); - await expect(joinCallButton).toBeVisible(); + await expect(conversation.joinCallButton).toBeVisible(); }, ); @@ -200,28 +201,31 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; // Open 1on1 conversation between users - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - - // User B mutes the conversation with User A via recent view - const contextMenu = await userBPages + const userAConversation = await userAPages + .conversationList() + .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .open(); + const userBConversation = await userBPages .conversationList() .getConversationLocator(userA.fullName, {protocol: 'mls'}) - .openContextMenu(); + .open(); + + // User B mutes the conversation with User A via recent view + const contextMenu = await userBConversation.openContextMenu(); await contextMenu.notificationsButton.click(); await userBPages.conversationDetails().selectNotificationsLevel('Nothing'); // Create group and open it for user B so the message won't be read immediately await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); // Start intercepting notifications for User B const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A sends message to User B and B received it - await userAPages.conversationList().openConversation(userB.fullName); + await userAConversation.open(); await userAPages.conversation().sendMessage('Test Message'); - await expect(userBPages.conversationList().getConversationLocator(userA.fullName)).toContainText('1 message'); + await expect(userBConversation).toContainText('1 message'); // Verify User B did not receive any notifications for the second message await expect.poll(() => getUserBNotifications()).toHaveLength(0); @@ -242,7 +246,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A sends an ephemeral message - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Ephemeral Message'); @@ -347,10 +351,10 @@ test.describe('Notifications', () => { for (const conversation of ['1on1', 'group'] as const) { await test.step(`User A opens the ${conversation} conversation`, async () => { if (conversation === '1on1') { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); } else { await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); } }); @@ -388,13 +392,13 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, 'Test Group', [userA]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); // Start intercepting notifications const {clickNotification} = await interceptNotifications(userBPage); // User A pings User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendPing(); await clickNotification({title: userA.fullName, body: 'Pinged'}); @@ -416,12 +420,12 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, 'Test Group', [userA]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); const {clickNotification} = await interceptNotifications(userBPage); // User A initiates a call to User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); // Verify user B receives the call @@ -447,13 +451,13 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // Start intercepting notifications const {clickNotification} = await interceptNotifications(userBPage); // User A sends message in group - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().sendMessage('Test Message'); await clickNotification({title: `${userA.fullName} in Test Group`, body: 'Test Message'}); @@ -473,7 +477,7 @@ test.describe('Notifications', () => { ]); const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const {getNotifications} = await interceptNotifications(userBPage); @@ -504,7 +508,7 @@ test.describe('Notifications', () => { ]); const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User A creates a group with User B const originalGroupName = 'Original Group Name'; @@ -515,7 +519,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A opens ConversationDetailsPage and changes the group name - await userAPages.conversationList().openConversation(originalGroupName); + await userAPages.conversationList().getConversationLocator(originalGroupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName(newGroupName); @@ -577,13 +581,13 @@ test.describe('Notifications', () => { const groupName = 'Image Group'; await createGroup(userAPages, groupName, [userB]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // Start intercepting notifications for User B const {getNotifications} = await interceptNotifications(userBPage); // User A sends an image to the group conversation - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); await expect @@ -611,12 +615,12 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const {getNotifications} = await interceptNotifications(userBPage); // User A changes the conversation timer to 10 seconds - await userAPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversationDetails().setSelfDeletingMessages('10 seconds'); diff --git a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts index 67b90c2a93e..7b59cbe03c6 100644 --- a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts @@ -28,7 +28,7 @@ async function openParticipantDetailsFromGroup( groupName: string, participantName: string, ) { - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversationDetails().openParticipantDetails(participantName); } @@ -63,7 +63,7 @@ test.describe('Participant Profile', () => { await acceptConnectionRequest(userCPages); await test.step('Go to any 1:1 conversation', async () => { - await userAPages.conversationList().openConversation(userC.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userC.fullName, {protocol: 'mls'}).open(); }); await test.step('Open People popover', async () => { @@ -155,7 +155,7 @@ test.describe('Participant Profile', () => { }); await test.step('User C opens people popover', async () => { - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().clickConversationTitle(); }); @@ -238,7 +238,7 @@ test.describe('Participant Profile', () => { await createGroup(adminPage, groupName, [userB]); - await adminPage.conversationList().openConversation(groupName); + await adminPage.conversationList().getConversationLocator(groupName).open(); await adminPage.conversation().clickConversationInfoButton(); await adminPage.conversation().makeUserAdmin(userB.fullName); diff --git a/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts b/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts index 761cf23ec82..97856f3bdb0 100644 --- a/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts @@ -52,13 +52,13 @@ test.describe('Ping', () => { if (scenario.isGroup) { await createGroup(userAPages, conversationName, [userB]); - await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversationList().getConversationLocator(conversationName).open(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); } else { - await userBPages.conversationList().openConversation(userA.fullName); + await userBPages.conversationList().getConversationLocator(userA.fullName).open(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); } await expect(userAPages.conversation().getPing()).toBeVisible(); @@ -72,12 +72,12 @@ test.describe('Ping', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().openConversation(userA.fullName); + await userBPages.conversationList().getConversationLocator(userA.fullName).open(); await userBPages.conversation().sendPing(); await expect(userBPages.conversation().pingButton).toBeDisabled(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await expect(userAPages.conversation().getPing()).toHaveCount(2); }); @@ -98,7 +98,7 @@ test.describe('Ping', () => { const conversationName = 'Test Group'; await createGroup(userAPages, conversationName, [userB, ...usersForBigGroup]); - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendPing(); await expect(userAModals.confirm().modal).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts b/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts index 1cb8ffe7528..9010ec7bcf3 100644 --- a/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts @@ -92,10 +92,10 @@ test.describe('Reactions', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await c.sendFromUserB(userBPage); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); const messageFromUserB = userAPages.conversation().getMessage({sender: userB}); await expect(messageFromUserB).toBeVisible(); @@ -127,7 +127,7 @@ test.describe('Reactions', () => { }); }); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); const messageWithLink = userAPages.conversation().getMessage({sender: userB}); await userAPages.conversation().reactOnMessage(messageWithLink, 'heart'); @@ -160,8 +160,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); const messageUserB = userAPages.conversation().getMessage({sender: userB}); @@ -190,8 +190,8 @@ test.describe('Reactions', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); const messageUserB = userAPages.conversation().getMessage({sender: userB}); @@ -217,8 +217,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message to react to'); const userBMessage = userBPages.conversation().getMessage({sender: userA}); @@ -241,8 +241,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message to react to'); const userBMessage = userBPages.conversation().getMessage({sender: userA}); @@ -283,8 +283,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message to react to'); const messageInUserA = userAPages.conversation().getMessage({sender: userB}); diff --git a/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts b/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts index 18723775f35..9860956fb98 100644 --- a/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts @@ -65,8 +65,8 @@ test.describe('Reply', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test'); const messageToReplyTo = userBPages.conversation().getMessage({content: 'Test'}); @@ -299,8 +299,8 @@ test.describe('Reply', () => { await createGroup(userAPages, 'Test Group', [userB]); await Promise.all([ - userAPages.conversationList().openConversation('Test Group'), - userBPages.conversationList().openConversation('Test Group'), + userAPages.conversationList().getConversationLocator('Test Group').open(), + userBPages.conversationList().getConversationLocator('Test Group').open(), ]); await userAPages.conversation().sendMessage('Message'); diff --git a/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts b/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts index 4088cce9b78..6f1678d53eb 100644 --- a/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts @@ -49,7 +49,7 @@ test.describe('Search', () => { const conversationName = 'Group conversation'; await createGroup(userAPages, conversationName, [userB]); - await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversationList().getConversationLocator(conversationName).open(); await userAPages.conversation().sendMessage(`@${userB.username} Group message with mention of User B`); await userAPages.conversationList().searchConversationsInput.fill(`@${userB.username}`); diff --git a/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts b/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts index 5e11d4b77ba..85196d73bc5 100644 --- a/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts @@ -41,8 +41,8 @@ test.describe('Self Deleting Messages', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Gone in 10s'); @@ -59,7 +59,7 @@ test.describe('Self Deleting Messages', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Gone in 10s'); @@ -115,7 +115,7 @@ test.describe('Self Deleting Messages', () => { // Re-open page reusing the same context so the login is not happening on a new device page = await createPage(context, withLogin(userA)); pages = PageManager.from(page).webapp.pages; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); const selfDeletingMessage = pages.conversation().getMessage({sender: userA}); await expect(selfDeletingMessage).toBeVisible(); @@ -134,8 +134,8 @@ test.describe('Self Deleting Messages', () => { const [userAPages, userBPages] = [userAPage, userBPage].map(page => PageManager.from(page).webapp.pages); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().openConversation('Test Group'); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); // User B should not read the message sent into the group immediately + await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User B should not read the message sent into the group immediately await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Test Message'); @@ -143,7 +143,7 @@ test.describe('Self Deleting Messages', () => { await userBPage.waitForTimeout(10_000); // Wait 10s before user B opens the group chat - await userBPages.conversationList().openConversation('Test Group'); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await expect(userBPages.conversation().getMessage({content: 'Test Message'})).toBeVisible(); }, ); @@ -159,8 +159,8 @@ test.describe('Self Deleting Messages', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().openConversation('Test Group'); - await userBPages.conversationList().openConversation('Test Group'); + await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversationLocator('Test Group').open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversationDetails().setSelfDeletingMessages('10 seconds'); @@ -220,8 +220,8 @@ test.describe('Self Deleting Messages', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Test'); diff --git a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts index ce6afdb8ecc..126845515da 100644 --- a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts @@ -69,7 +69,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages} = PageManager.from(userAPage).webapp; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await shareAssetHelper(getAudioFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add file'})); const message = pages.conversation().getMessage({sender: userA}); @@ -85,7 +85,7 @@ test.describe('Sending Assets', () => { const buffer = await fs.readFile(getTextFilePath()); await test.step('Go to any 1:1 conversation', async () => { - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); }); await test.step('User B can drag & drop a file into a conversation', async () => { @@ -140,7 +140,7 @@ test.describe('Sending Assets', () => { const pastedFileControls = userAPage.getByTestId('pasted-file-controls'); await test.step('Go to any 1:1 conversation', async () => { - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); }); await test.step('User A copy paste image into the conversation', async () => { @@ -162,7 +162,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const userAPages = PageManager.from(userAPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversationList().getConversationLocator(userB.fullName).open(); await userAPages.conversation().sendMessage('Message to copy'); const message = userAPages.conversation().getMessage({sender: userA}); @@ -198,10 +198,10 @@ test.describe('Sending Assets', () => { await test.step('User A opens the conversation', async () => { if (conversationType === '1on1') { - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); } else { await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().openConversation('Test Group'); + await pages.conversationList().getConversationLocator('Test Group').open(); } }); @@ -237,7 +237,7 @@ test.describe('Sending Assets', () => { // Verify uploading a file exceeding the limit isn't possible for both, 1on1 and group conversations for (const conversation of [userB.fullName, 'Test Group']) { await test.step(`Try sending a too big file to ${conversation}`, async () => { - await pages.conversationList().openConversation(conversation); + await pages.conversationList().getConversationLocator(conversation).open(); const [fileChooser] = await Promise.all([ page.waitForEvent('filechooser'), page.getByRole('button', {name: 'Add file'}).click(), @@ -270,8 +270,8 @@ test.describe('Sending Assets', () => { const pages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await pages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); const tempFilePath = path.join(tmpdir(), '25MB-testfile.tmp'); try { @@ -301,8 +301,8 @@ test.describe('Sending Assets', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getTextFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add file'})); @@ -327,7 +327,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages} = PageManager.from(userAPage).webapp; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await pages.conversation().enableSelfDeletingMessages(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); @@ -350,7 +350,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages, modals} = PageManager.from(userAPage).webapp; - await pages.conversationList().openConversation(userB.fullName); + await pages.conversationList().getConversationLocator(userB.fullName).open(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); await pages.conversation().clickImage(userA); diff --git a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts index 376eb7a0ffd..8ce4da588b5 100644 --- a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts @@ -157,7 +157,7 @@ test.describe('Status', () => { name: 'Rename group', sendAction: async ({userAPageManager}) => { const userAPages = userAPageManager.pages; - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName('New Group Name'); }, @@ -188,7 +188,7 @@ test.describe('Status', () => { name: 'Add member to group', sendAction: async ({userAPageManager}) => { const userAPages = userAPageManager.pages; - await userAPages.conversationList().openConversation('Test Group 2'); + await userAPages.conversationList().getConversationLocator('Test Group 2').open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().clickAddPeopleButton(); await userAPages.conversationDetails().addUsersToConversation([userC.fullName]); @@ -249,7 +249,7 @@ test.describe('Status', () => { // User B sends initial messages for replies for (const {targetForUserB, options} of scenarios) { - await userBPages.conversationList().openConversation(targetForUserB, options); + await userBPages.conversationList().getConversationLocator(targetForUserB, options).open(); await userBPages.conversation().sendMessage('Message to reply'); } @@ -261,7 +261,7 @@ test.describe('Status', () => { await updateUserStatus(userBPageManager, userB.fullName, config.status); // User B opens the third conversation to receive notifications - await userBPages.conversationList().openConversation(userC.fullName); + await userBPages.conversationList().getConversationLocator(userC.fullName).open(); const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); @@ -270,7 +270,7 @@ test.describe('Status', () => { const filteredCases = commonTestCases.filter( tc => config.status === UserStatus.Away || !['Mention', 'Reply'].includes(tc.name), ); - await userAPages.conversationList().openConversation(targetForUserA, options); + await userAPages.conversationList().getConversationLocator(targetForUserA, options).open(); for (const testCase of filteredCases) { await test.step(`Action: ${testCase.name} message in ${type}`, async () => { @@ -288,7 +288,7 @@ test.describe('Status', () => { // System Test Cases await test.step('User B should not receive any system notification', async () => { - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); for (const systemTestCase of systemTestCases) { await systemTestCase.sendAction({userAPageManager}); } @@ -309,7 +309,7 @@ test.describe('Status', () => { }, ]; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); for (const testCase of specialTestCases) { await test.step(`Action: ${testCase.name} message`, async () => { await testCase.sendAction({pageA: userAPage, api}); @@ -319,7 +319,7 @@ test.describe('Status', () => { }); } else { await test.step('User B should not receive calls notification (Away status)', async () => { - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().startCall(); await expect(userBPages.calling().callCell).toBeVisible(); await expect.poll(() => getUserBNotifications()).toHaveLength(0); @@ -368,10 +368,10 @@ test.describe('Status', () => { await createGroup(userAPages, groupName, [userB, userC]); // User B sends initial message and sets status to available - await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage('Message to reply'); - await pages.conversationList().openConversation(groupName); + await pages.conversationList().getConversationLocator(groupName).open(); await pages.conversation().sendMessage('Message to reply'); await updateUserStatus(userBPageManager, userB.fullName, UserStatus.Available); @@ -394,7 +394,7 @@ test.describe('Status', () => { for (const {type, target, options} of scenarios) { await test.step(`User A opens the ${type} conversation`, async () => { - await userAPages.conversationList().openConversation(target, options); + await userAPages.conversationList().getConversationLocator(target, options).open(); }); await test.step(`User B should receive all conversation notifications`, async () => { @@ -409,7 +409,7 @@ test.describe('Status', () => { } await test.step(`User B should receive system notifications only for group creation and renaming`, async () => { - await userAPages.conversationList().openConversation(groupName); + await userAPages.conversationList().getConversationLocator(groupName).open(); for (const systemTestCase of systemTestCases) { await test.step(`Action: ${systemTestCase.name}`, async () => { await systemTestCase.sendAction({userAPageManager}); @@ -434,11 +434,11 @@ test.describe('Status', () => { const userBPages = userBPageManager.webapp.pages; // User B verify no status is set in conversation list - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - const {statusAvailabilityIcon} = userBPages + const conversation = await userBPages .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}); - await expect(statusAvailabilityIcon).not.toBeVisible(); + .getConversationLocator(userA.fullName, {protocol: 'mls'}) + .open(); + await expect(conversation.statusAvailabilityIcon).not.toBeVisible(); // User A opens preferences by clicking the gear button await components.conversationSidebar().clickPreferencesButton(); @@ -459,8 +459,8 @@ test.describe('Status', () => { await expect(pages.account().statusOption(UserStatus.None)).not.toHaveAccessibleName('Selected, None'); // User B verifies status is Available in conversation list - await expect(statusAvailabilityIcon).toBeVisible(); - await expect(statusAvailabilityIcon).toHaveAttribute('data-uie-value', 'available'); + await expect(conversation.statusAvailabilityIcon).toBeVisible(); + await expect(conversation.statusAvailabilityIcon).toHaveAttribute('data-uie-value', 'available'); }); test( @@ -482,7 +482,7 @@ test.describe('Status', () => { await expect(components.conversationSidebar().personalStatusIcon).toBeVisible(); // User B should see the BUSY status in the searchable group participant list - await userBPages.conversationList().openConversation(groupName); + await userBPages.conversationList().getConversationLocator(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toBeVisible(); await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toHaveAttribute( diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index f860fd28d40..f580c44ca04 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -59,7 +59,7 @@ export const sendTextMessageToConversation = async ( message: string, ) => { const {pages} = pageManager.webapp; - await pages.conversationList().openConversation(conversation); + await pages.conversationList().getConversationLocator(conversation).open(); await pages.conversation().sendMessage(message); }; From 13f0476ceb12e53228e6ab8ac4c59fd2ffff68a4 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 14 Apr 2026 19:38:06 +0200 Subject: [PATCH 23/49] refactor: remove util "openConversation" --- .../pageManager/webapp/pages/conversationList.page.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts index fc95ac2da59..edf01044ac1 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts @@ -38,10 +38,6 @@ export class ConversationListPage { this.conversationListHeaderTitle = page.locator('[data-uie-name="conversation-list-header-title"]'); } - async openConversation(conversationName: string, options?: Parameters[1]) { - await this.getConversationLocator(conversationName, options).click(); - } - async openPendingConnectionRequest() { await this.pendingConnectionRequest.click(); } From 5532101fecbad07403d27bb5e783ddd2cf8fe7d4 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Thu, 30 Apr 2026 11:40:34 +0200 Subject: [PATCH 24/49] style: rename "getConversationLocator" to just "getConversation" --- .../webapp/pages/conversationList.page.ts | 2 +- .../specs/Accessibility/Accessibility.spec.ts | 10 +-- .../AccountSettings/accountSettings.spec.ts | 6 +- .../e2e_tests/specs/Archive/archive.spec.ts | 12 +-- .../Authentication/authentication.spec.ts | 8 +- .../test/e2e_tests/specs/Block/block.spec.ts | 36 ++++---- .../e2e_tests/specs/Calling/calling.spec.ts | 50 +++++------ .../clearConversationContent.spec.ts | 30 +++---- .../specs/Connections/Connections.spec.ts | 10 +-- .../specs/Conversations/conversations.spec.ts | 60 +++++++------- .../addMembersToChat-TC-8631.spec.ts | 22 ++--- .../backupRestoration-TC-8634.spec.ts | 4 +- .../CriticalFlow/channelsCall-TC-8755.spec.ts | 4 +- .../channelsManagement-TC-8752.spec.ts | 18 ++-- .../conversationManagement-TC-8636.spec.ts | 2 +- .../CriticalFlow/groupCalls-TC-8632.spec.ts | 4 +- .../groupVideoCall-TC-8637.spec.ts | 6 +- .../CriticalFlow/joinTeam-TC-8635.spec.ts | 8 +- .../messagesIn1On1-TC-8750.spec.ts | 4 +- .../messagesInChannels-TC-8753.spec.ts | 6 +- .../messagesInGroups-TC-8751.spec.ts | 6 +- .../CriticalFlow/oneOnOneCall-TC-8754.spec.ts | 2 +- .../personalAccountLifecycle-TC-8638.spec.ts | 16 ++-- .../specs/DeepLinks/deepLinks.spec.ts | 10 +-- .../e2e_tests/specs/Delete/delete.spec.ts | 36 ++++---- .../test/e2e_tests/specs/Drive/drive.spec.ts | 16 ++-- .../test/e2e_tests/specs/Edit/edit.spec.ts | 24 +++--- .../e2e_tests/specs/Folders/folders.spec.ts | 14 ++-- .../groupConversation.spec.ts | 28 +++---- .../specs/Guestroom/guestroom.spec.ts | 20 ++--- .../specs/HistoryBackup/historyBackup.spec.ts | 46 +++++------ .../inConversationSearch.spec.ts | 64 +++++++-------- .../specs/LinkPreview/linkPreview.spec.ts | 4 +- .../specs/Localization/localization.spec.ts | 2 +- .../test/e2e_tests/specs/Logs/logs.spec.ts | 4 +- .../e2e_tests/specs/Markdown/markdown.spec.ts | 22 ++--- .../e2e_tests/specs/Mention/mention.spec.ts | 82 +++++++++---------- .../specs/Notifications/notifications.spec.ts | 56 ++++++------- .../participantProfile.spec.ts | 12 +-- .../test/e2e_tests/specs/Ping/ping.spec.ts | 14 ++-- .../specs/Reactions/reactions.spec.ts | 26 +++--- .../test/e2e_tests/specs/Reply/reply.spec.ts | 8 +- .../e2e_tests/specs/Search/search.spec.ts | 6 +- .../selfDeletingMessages.spec.ts | 22 ++--- .../specs/SendingAssets/sendingAssets.spec.ts | 26 +++--- .../e2e_tests/specs/Status/status.spec.ts | 33 ++++---- .../test/e2e_tests/utils/userActions.ts | 2 +- 47 files changed, 448 insertions(+), 455 deletions(-) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts index edf01044ac1..e0bf4a1ca77 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts @@ -57,7 +57,7 @@ export class ConversationListPage { * @param conversationName Name of the conversation to search for * @param options.protocol Only locate conversations matching this protocol (mls only works for 1on1 conversations as groups still use proteus) - Default: "mls" */ - getConversationLocator(conversationName: string, options?: {protocol?: 'mls' | 'proteus'}) { + getConversation(conversationName: string, options?: {protocol?: 'mls' | 'proteus'}) { let conversation = this.page.getByTestId('item-conversation').filter({hasText: conversationName}); if (options?.protocol) { diff --git a/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts b/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts index 3cf43c8d38e..f444e430912 100644 --- a/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Accessibility/Accessibility.spec.ts @@ -44,8 +44,8 @@ test.describe('Accessibility', () => { ]); await createGroup(userAPages, 'Accessible Group', [userB]); - await userAPages.conversationList().getConversationLocator('Accessible Group').open(); - await userBPages.conversationList().getConversationLocator('Accessible Group').open(); + await userAPages.conversationList().getConversation('Accessible Group').open(); + await userBPages.conversationList().getConversation('Accessible Group').open(); await test.step('User A starts typing in group and B sees typing indicator', async () => { await userAPages.conversation().messageInput.pressSequentially('Test', {delay: 100}); @@ -60,7 +60,7 @@ test.describe('Accessibility', () => { await test.step('User A types more into group', async () => { await userAPages.sidebar().allConversationsButton.click(); - await userAPages.conversationList().getConversationLocator('Accessible Group').open(); + await userAPages.conversationList().getConversation('Accessible Group').open(); await userAPages.conversation().messageInput.pressSequentially('Test', {delay: 100}); // Since A disabled the typing indicator B should not see it await expect(userBPages.conversation().typingIndicator).not.toBeVisible(); @@ -92,11 +92,11 @@ test.describe('Accessibility', () => { await pages.conversation().messageInput.fill('Draft Message'); await pages.conversation().backButton.click(); - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await expect(pages.conversation().messageInput).toBeEmpty(); await pages.conversation().backButton.click(); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await expect(pages.conversation().messageInput).toHaveText('Draft Message'); }, ); diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index 662943c26a7..99becb544cb 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -187,7 +187,7 @@ test.describe('account settings', () => { const memberAPages = PageManager.from(memberAPage).webapp.pages; const memberBPages = PageManager.from(memberBPage).webapp.pages; - await memberAPages.conversationList().getConversationLocator(memberB.fullName, {protocol: 'mls'}).open(); + await memberAPages.conversationList().getConversation(memberB.fullName, {protocol: 'mls'}).open(); await memberAPages.conversation().startCall(); await memberBPages.calling().clickAcceptCallButton(); @@ -213,7 +213,7 @@ test.describe('account settings', () => { }); await test.step('Conversation itself on top of users message', async () => { - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await pages.conversation().sendMessage('test'); await expect(pages.conversation().getMessage({content: 'test'})).toContainText(memberA.fullName); }); @@ -243,7 +243,7 @@ test.describe('account settings', () => { await pages.conversationList().clickCreateGroup(); await modals.createConversation().createChannel('Test Channel', {members: [memberB]}); - await pages.conversationList().getConversationLocator('Test Channel').open(); + await pages.conversationList().getConversation('Test Channel').open(); await pages.conversation().conversationInfoButton.click(); await expect(pages.conversationDetails().groupAdmins.filter({hasText: memberA.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts b/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts index 1d204779200..a78eb4ea944 100644 --- a/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Archive/archive.spec.ts @@ -40,7 +40,7 @@ test.describe('Archive', () => { const page = await createPage(withLogin(memberA), withConnectedUser(memberB)); const {pages, components} = PageManager.from(page).webapp; - const conversation = pages.conversationList().getConversationLocator(memberB.fullName); + const conversation = pages.conversationList().getConversation(memberB.fullName); let contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); await expect(conversation).not.toBeVisible(); @@ -68,9 +68,9 @@ test.describe('Archive', () => { const memberAConversation = await memberAPages .conversationList() - .getConversationLocator(memberB.fullName, {protocol: 'mls'}) + .getConversation(memberB.fullName, {protocol: 'mls'}) .open(); - await memberBPages.conversationList().getConversationLocator(memberA.fullName, {protocol: 'mls'}).open(); + await memberBPages.conversationList().getConversation(memberA.fullName, {protocol: 'mls'}).open(); await test.step('MemberA archives conversation with memberB', async () => { const contextMenu = await memberAConversation.openContextMenu(); @@ -99,7 +99,7 @@ test.describe('Archive', () => { const page = await createPage(withLogin(memberA), withConnectedUser(memberB)); const {pages, components} = PageManager.from(page).webapp; - const conversation = await pages.conversationList().getConversationLocator(memberB.fullName).open(); + const conversation = await pages.conversationList().getConversation(memberB.fullName).open(); if (tag === '@TC-103') { await test.step('User mutes the conversation', async () => { @@ -143,10 +143,10 @@ test.describe('Archive', () => { let conversation: Locator; if (tag === '@TC-104') { await createGroup(pages, 'Test Group', [memberB]); - conversation = pages.conversationList().getConversationLocator('Test Group'); + conversation = pages.conversationList().getConversation('Test Group'); } else { await connectWithUser(pageManager, memberB); - conversation = pages.conversationList().getConversationLocator(memberB.fullName); + conversation = pages.conversationList().getConversation(memberB.fullName); } await pages.conversation().conversationInfoButton.click(); diff --git a/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts b/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts index 0f1058b5106..2d06677d41e 100644 --- a/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Authentication/authentication.spec.ts @@ -162,7 +162,7 @@ test.describe('Authentication', () => { }); await test.step('Send a message', async () => { - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().sendMessage('Before refresh'); }); @@ -172,7 +172,7 @@ test.describe('Authentication', () => { await pageManager.refreshPage({waitUntil: 'load'}); - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await expect(message).toBeVisible(); }); }, @@ -213,7 +213,7 @@ test.describe('Authentication', () => { await test.step('Connect with and send message to userB', async () => { await connectWithUser(pageManager, userB); - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().sendMessage('Test message'); await expect(pages.conversation().getMessage({content: 'Test message'})).toBeVisible(); }); @@ -233,7 +233,7 @@ test.describe('Authentication', () => { }); await test.step('Verify previously sent message is gone', async () => { - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await expect(pages.conversation().getMessage({content: 'Test message'})).not.toBeAttached(); }); }, diff --git a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts index 251b3b59f52..33aa31b3ddd 100644 --- a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts @@ -32,7 +32,7 @@ import {createGroup, sendConnectionRequest} from '../../utils/userActions'; export async function blockUserFromConversationList(pageManager: PageManager, userToBlock: User) { const {pages, modals} = pageManager.webapp; - const conversation = await pages.conversationList().getConversationLocator(userToBlock.fullName).open(); + const conversation = await pages.conversationList().getConversation(userToBlock.fullName).open(); const contextMenu = await conversation.openContextMenu(); await contextMenu.blockButton.click(); await modals.blockWarning().clickBlock(); @@ -94,7 +94,7 @@ test.describe('User Blocking', () => { // Step 1: User A opens conversation with User B const conversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); // Step 2: User A opens the options menu for user B const contextMenu = await conversation.openContextMenu(); @@ -124,9 +124,9 @@ test.describe('User Blocking', () => { // Step 1: User A and B have a 1:1 conversation const conversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // Step 2: User A opens conversation info await userAPages.conversation().clickConversationInfoButton(); @@ -168,27 +168,27 @@ test.describe('User Blocking', () => { const conversationName = 'GroupConversation'; - await expect(userAPages.conversationList().getConversationLocator(userB.fullName)).toBeAttached(); + await expect(userAPages.conversationList().getConversation(userB.fullName)).toBeAttached(); await createGroup(userAPages, conversationName, [userB]); await test.step('Step 1: User B sends message to group chat with User A', async () => { - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage('message before block'); }); await test.step('Step 2: User A blocks User B from group conversation', async () => { // Ensures User A is in the group before blocking - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await blockUserFromOpenGroupProfileView(userAPageManager, userB); }); await test.step('Step 3: User B writes second message to the group chat after being blocked by User A', async () => { - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage('message after block'); }); await test.step('Step 4: User A receives message from User B in Group Chat even though User B is blocked', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await expect(userAPages.conversation().messages).toHaveCount(2); }); }, @@ -210,7 +210,7 @@ test.describe('User Blocking', () => { // Step 1: User A and B have a 1:1 conversation const conversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); // Step 2: User A blocks User B await blockUserFromProfileView(userAPageManagerInstance); @@ -223,7 +223,7 @@ test.describe('User Blocking', () => { await conversation.open(); await userAPages.conversation().sendMessage('Message after unblock'); // Step 5: User B receives message from User A - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await expect(userBPages.conversation().messages).toHaveCount(1); // Step 6: User B writes message to User A await userBPages.conversation().sendMessage('Message after being unblocked'); @@ -251,7 +251,7 @@ test.describe('User Blocking', () => { // Step 1: User B blocks User A await test.step('User B blocks User A', async () => { // Open the conversation with User C to prevent the "Wire cannot open this conversation" modal - await userAPages.conversationList().getConversationLocator(userC.fullName).open(); + await userAPages.conversationList().getConversation(userC.fullName).open(); await blockUserFromConversationList(userBPageManagerInstance, userA); }); @@ -301,7 +301,7 @@ test.describe('User Blocking', () => { await userBPages.connectRequest().clickConnectButton(); await test.step('User A blocks User B', async () => { - await userAPages.conversationList().getConversationLocator(userC.fullName).open(); + await userAPages.conversationList().getConversation(userC.fullName).open(); await blockUserFromConversationList(userAPageManagerInstance, userB); }); @@ -314,16 +314,16 @@ test.describe('User Blocking', () => { }); await test.step('User B receives message sent by User A', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message after unblocking'); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await expect(userBPages.conversation().messages).toHaveCount(1); }); await test.step('User A receives message sent by User B', async () => { - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message after being unblocked'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); const message = userAPages.conversation().getMessage({sender: userB}); await expect(message).toContainText('Message after being unblocked'); }); @@ -346,7 +346,7 @@ test.describe('User Blocking', () => { const userAPages = userAPageManager.pages; // Step 1: User A opens conversation with User B - const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await userAPages.conversationList().getConversation(userB.fullName).open(); // Step 2: User A opens the options menu for user B const contextMenu = await conversation.openContextMenu(); diff --git a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts index 3c64c4e75da..20a675039cb 100644 --- a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts @@ -53,13 +53,13 @@ test.describe('Calling', () => { ]); // User A has a call with user B - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().clickCallButton(); await userBPages.calling().clickAcceptCallButton(); await expect(userBPages.calling().callCell).toBeVisible(); // User A starts a call with user C while in a call with user B - await userAPages.conversationList().getConversationLocator(userC.fullName).open(); + await userAPages.conversationList().getConversation(userC.fullName).open(); await userAPages.conversation().clickCallButton(); // A modal is shown prompting him to confirm before cancelling the ongoing call @@ -83,7 +83,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // Establish group call; required precondition for hand-raise testing - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userBPages.calling().callCell).toBeVisible(); @@ -114,7 +114,7 @@ test.describe('Calling', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().clickCallButton(); await userBPages.calling().clickAcceptCallButton(); @@ -137,10 +137,10 @@ test.describe('Calling', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); - const {joinCallButton} = userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); + const {joinCallButton} = userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}); await expect(joinCallButton).toBeVisible(); await expect(userBPages.calling().acceptCallButton).toBeVisible(); @@ -174,10 +174,10 @@ test.describe('Calling', () => { const userBDevice2Pages = PageManager.from(userBPage2).webapp.pages; if (conversationType === '1on1') { - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); } else { await createGroup(userAPages, 'Calling group', [userB]); - await userAPages.conversationList().getConversationLocator('Calling group').open(); + await userAPages.conversationList().getConversation('Calling group').open(); } // Ensure no audio is playing on both devices initially @@ -211,7 +211,7 @@ test.describe('Calling', () => { ]); // User A calls user B - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().clickCallButton(); // User B declines the call @@ -219,7 +219,7 @@ test.describe('Calling', () => { await expect(userBPages.calling().callCell).not.toBeVisible(); // User B calls user C instead - await userBPages.conversationList().getConversationLocator(userC.fullName).open(); + await userBPages.conversationList().getConversation(userC.fullName).open(); await expect(userBPages.conversation().callButton).toBeEnabled(); await userBPages.conversation().startCall(); await expect(userBPages.calling().callCell).toBeVisible(); @@ -236,7 +236,7 @@ test.describe('Calling', () => { const userCPages = PageManager.from(userCPage).webapp.pages; await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -248,7 +248,7 @@ test.describe('Calling', () => { // // User C joins the ongoing call await userCPage.waitForTimeout(5000); - await userCPages.conversationList().getConversationLocator(groupName).joinCallButton.click(); + await userCPages.conversationList().getConversation(groupName).joinCallButton.click(); // Confirm that user C joined the call await expect(userCPages.calling().goFullScreen).toBeVisible(); @@ -263,7 +263,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -286,7 +286,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -301,7 +301,7 @@ test.describe('Calling', () => { await expect(userBPages.calling().callCell).toBeHidden(); // User B re-joins the ongoing call from the conversation list - await userBPages.conversationList().getConversationLocator(groupName).joinCallButton.click({timeout: 30_000}); + await userBPages.conversationList().getConversation(groupName).joinCallButton.click({timeout: 30_000}); await expect(userBPages.calling().goFullScreen).toBeVisible(); }); @@ -315,7 +315,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -343,7 +343,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); await test.step('User A starts a call and User B joins', async () => { - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -354,7 +354,7 @@ test.describe('Calling', () => { }); await test.step('User A joins then leaves the call from their second device', async () => { - await userADevice2Pages.conversationList().getConversationLocator(groupName).open(); + await userADevice2Pages.conversationList().getConversation(groupName).open(); await userADevice2Pages.conversation().clickCallButton(); await expect(userADevice2Pages.calling().callCell).toBeVisible(); @@ -371,7 +371,7 @@ test.describe('Calling', () => { await userAPages.calling().clickLeaveCallButton(); await expect(userAPages.calling().callCell).not.toBeAttached(); // Verify the call is still "joinable" (User B is still there) - await expect(userAPages.conversationList().getConversationLocator(groupName).joinCallButton).toBeVisible({ + await expect(userAPages.conversationList().getConversation(groupName).joinCallButton).toBeVisible({ timeout: 30_000, }); }); @@ -391,7 +391,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, userC]); // User A initiates the call - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); @@ -427,12 +427,12 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB]); // User A initiates the call - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPages.calling().callCell).toBeVisible(); await expect(userBPages.calling().callCell).toBeVisible(); - await expect(userBPages.conversationList().getConversationLocator(groupName).joinCallButton).toBeVisible(); + await expect(userBPages.conversationList().getConversation(groupName).joinCallButton).toBeVisible(); // User A removes User B from the group await userAPages.conversation().toggleGroupInformation(); @@ -443,7 +443,7 @@ test.describe('Calling', () => { // User B cannot join the group call await expect(userBPages.calling().callCell).not.toBeAttached(); - await expect(userBPages.conversationList().getConversationLocator(groupName).joinCallButton).not.toBeAttached(); + await expect(userBPages.conversationList().getConversation(groupName).joinCallButton).not.toBeAttached(); }, ); @@ -463,7 +463,7 @@ test.describe('Calling', () => { const userAPages = PageManager.from(userAPage).webapp.pages; await createGroup(userAPages, groupName, allMembers); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); await expect(userAPage.getByTestId('modal-without-title')).toContainText( @@ -493,7 +493,7 @@ test.describe('Calling', () => { await createGroup(userAPages, groupName, [userB, guestUser]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickCallButton(); }); diff --git a/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts b/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts index bf073ab8ebc..6e65915a971 100644 --- a/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ClearConversationContent/clearConversationContent.spec.ts @@ -65,13 +65,13 @@ test.describe('Clear Conversation Content', () => { await createGroup(userAPages, conversationName, [userB, userC]); // Step 2: Write messages in the group conversation - const conversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); + const conversation = await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); - await userCPages.conversationList().getConversationLocator(conversationName).open(); + await userCPages.conversationList().getConversation(conversationName).open(); await userCPages.conversation().sendMessage('Message from User C'); await expect(userAPages.conversation().messages).toHaveCount(3); @@ -116,13 +116,13 @@ test.describe('Clear Conversation Content', () => { // Step 1: Create a 1:1 conversation with User A and B const conversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); // Step 2: Write messages in the conversation await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); await expect(userAPages.conversation().messages).toHaveCount(2); @@ -172,14 +172,14 @@ test.describe('Clear Conversation Content', () => { // Step 1: Create a group conversation with User A, B and C const conversationName = conversationType === 'group' ? 'Group conversation' : userB.fullName; - let userAConversation: ReturnType; + let userAConversation: ReturnType; if (conversationType === 'group') { await createGroup(userAPages, conversationName, [userB, userC]); - userAConversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); + userAConversation = await userAPages.conversationList().getConversation(conversationName).open(); } else { userAConversation = await userAPages .conversationList() - .getConversationLocator(conversationName, {protocol: 'mls'}) + .getConversation(conversationName, {protocol: 'mls'}) .open(); } @@ -188,12 +188,12 @@ test.describe('Clear Conversation Content', () => { const userBConversation = await userBPages .conversationList() - .getConversationLocator(conversationType === 'group' ? conversationName : userA.fullName) + .getConversation(conversationType === 'group' ? conversationName : userA.fullName) .open(); await userBPages.conversation().sendMessage('Message from User B'); if (conversationType === 'group') { - await userCPages.conversationList().getConversationLocator(conversationName).open(); + await userCPages.conversationList().getConversation(conversationName).open(); await userCPages.conversation().sendMessage('Message from User C'); await expect(userAPages.conversation().messages).toHaveCount(3); } else { @@ -270,25 +270,25 @@ test.describe('Clear Conversation Content', () => { const userBPages = userBPageManager.webapp.pages; const conversationName = 'Group conversation'; - let userAConversation: ReturnType; + let userAConversation: ReturnType; if (conversationType === 'group') { await createGroup(userAPages, conversationName, [userB]); // Step 1: User A and B write in group conversation - userAConversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); + userAConversation = await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); } else { // Step 1: User A and B write in conversation userAConversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); } await expect(userAPages.conversation().messages).toHaveCount(2); diff --git a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts index 0e99d287847..6a4789a8f3a 100644 --- a/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Connections/Connections.spec.ts @@ -73,14 +73,14 @@ test.describe('Connections', () => { }); await test.step('A creates a group with B & C', async () => { - await expect(memberAPages.conversationList().getConversationLocator(memberB.fullName)).toBeAttached(); - await expect(memberAPages.conversationList().getConversationLocator(memberC.fullName)).toBeAttached(); + await expect(memberAPages.conversationList().getConversation(memberB.fullName)).toBeAttached(); + await expect(memberAPages.conversationList().getConversation(memberC.fullName)).toBeAttached(); await createGroup(memberAPages, 'Group', [memberB, memberC]); }); await test.step('B sends a connection request to C via the group conversation', async () => { - await memberBPages.conversationList().getConversationLocator('Group').open(); + await memberBPages.conversationList().getConversation('Group').open(); await memberBPages.conversation().conversationInfoButton.click(); await memberBPages.conversationDetails().openParticipantDetails(memberC.fullName); await memberBPages.participantDetails().sendConnectRequest(); @@ -105,7 +105,7 @@ test.describe('Connections', () => { await memberBPages.conversationList().pendingConnectionRequest.click(); await memberBPages.conversation().ignoreButton.click(); - await expect(memberBPages.conversationList().getConversationLocator(memberB.fullName)).not.toBeVisible(); + await expect(memberBPages.conversationList().getConversation(memberB.fullName)).not.toBeVisible(); }, ); @@ -117,7 +117,7 @@ test.describe('Connections', () => { await sendConnectionRequest(pageManager, memberB); const {pages} = pageManager.webapp; - const conversation = pages.conversationList().getConversationLocator(memberB.fullName); + const conversation = pages.conversationList().getConversation(memberB.fullName); const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index da6e6473f3e..d422e297ce4 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -44,7 +44,7 @@ test.describe('Conversations', () => { const userAPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); const firstMessage = userAPages.conversation().systemMessages.first(); await expect(firstMessage).toContainText(new RegExp(`You started the conversation\\s*${groupName}\\s*with`, 'i')); @@ -63,7 +63,7 @@ test.describe('Conversations', () => { ]); await createGroup(userAPages, groupName, [userB, userC]); - userBPages.conversationList().getConversationLocator(groupName).open(); + userBPages.conversationList().getConversation(groupName).open(); const pattern = new RegExp( `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, @@ -113,7 +113,7 @@ test.describe('Conversations', () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName('New Group Name'); @@ -137,11 +137,11 @@ test.describe('Conversations', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(adminPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(adminPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversationDetails().getUserRoleIcon(guestUser.fullName)).toHaveAttribute( 'data-uie-name', @@ -166,11 +166,11 @@ test.describe('Conversations', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(adminPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(adminPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); await createGroup(adminPages, groupName, [userB, guestUser]); - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); @@ -191,20 +191,20 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // Confirm that the userA is an admin in the group - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); expect(userBPages.conversation().adminsList).toBeVisible(); expect(userBPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); // User A leaves the group - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversation().leaveConversation(); await modals.leaveConversation().clickConfirm(); await expect(adminPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); // User B sees the empty admins section - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); expect(adminPages.conversation().adminsList).not.toBeVisible(); }, @@ -222,7 +222,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB]); // User B can see members section - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); expect(adminPages.conversation().membersList).toBeVisible(); expect(adminPages.conversation().membersList.getByRole('listitem')).toHaveCount(1); @@ -231,7 +231,7 @@ test.describe('Conversations', () => { await adminPages.conversation().makeUserAdmin(userB.fullName); // User A and B cannot see members section - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); for (const userPages of [adminPages, userBPages]) { await userPages.conversation().clickConversationInfoButton(); @@ -248,7 +248,7 @@ test.describe('Conversations', () => { const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(adminPages, groupName, [userB]); - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await expect(adminPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); @@ -269,7 +269,7 @@ test.describe('Conversations', () => { await createGroup(adminPage, groupName, [userB, externalUser]); - await adminPage.conversationList().getConversationLocator(groupName).open(); + await adminPage.conversationList().getConversation(groupName).open(); await adminPage.conversation().clickConversationInfoButton(); await expect(adminPage.conversation().membersList).toBeVisible(); await expect(adminPage.conversationDetails().getUserRoleIcon(externalUser.fullName)).toHaveAttribute( @@ -290,12 +290,12 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // User A makes userB an admin - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversation().makeUserAdmin(userB.fullName); // User B can see delete group and options in conversation details - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().deleteGroupButton).toBeVisible(); await expect(userBPages.conversationDetails().guestOptionsButton).toBeVisible(); @@ -316,10 +316,10 @@ test.describe('Conversations', () => { const totalMessages = 20; await test.step('User B opens conversation with User A to mark previous history as read', async () => { - await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User A sends "Read" messages while User B is looking at the chat - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); for (let i = 1; i <= totalMessages; i++) { await userAPages.conversation().sendMessage(`READ message: ${i}`); } @@ -333,7 +333,7 @@ test.describe('Conversations', () => { }); await test.step('User B switches context to another conversation', async () => { - await pages.conversationList().getConversationLocator(userC.fullName).open(); + await pages.conversationList().getConversation(userC.fullName).open(); }); await test.step('User A sends new unread messages to User B', async () => { @@ -341,15 +341,13 @@ test.describe('Conversations', () => { await userAPages.conversation().sendMessage(`UNREAD message: ${i}`); } - const conversationWithUserA = pages - .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}); + const conversationWithUserA = pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}); await expect(conversationWithUserA.getByTestId('status-unread')).toContainText(`${totalMessages}`); }); await test.step('User B opens the conversation and verifies scroll position', async () => { await components.conversationSidebar().clickAllConversationsButton(); - await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // 1. Verify conversation scroll is NOT at the very bottom (last unread shouldn't be visible yet) const lastUnreadMessage = pages @@ -370,9 +368,7 @@ test.describe('Conversations', () => { const lastMessage = pages.conversation().getMessage({sender: userA}).last(); await lastMessage.scrollIntoViewIfNeeded(); - const conversationWithUserA = pages - .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}); + const conversationWithUserA = pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}); await expect(conversationWithUserA.getByTestId('status-unread')).not.toBeVisible(); }); }, @@ -391,10 +387,10 @@ test.describe('Conversations', () => { await createGroup(userBPages, groupName, [userA, userC]); // User A and B both send messages to start conversation - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().sendMessage('Message 1'); - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().sendMessage('Message 2'); // User A reacts to Message 2 while still a member @@ -461,7 +457,7 @@ test.describe('Conversations', () => { pm => pm.webapp.pages, ); - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().messageInput.pressSequentially(':) ', {delay: 100}); expect(userAPages.conversation().messageInput).toContainText('🙂'); @@ -478,7 +474,7 @@ test.describe('Conversations', () => { await createGroup(adminPages, groupName, [userB, userC]); // User A renames the conversation - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversationDetails().changeConversationName('New Group Name'); @@ -502,11 +498,11 @@ test.describe('Conversations', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(adminPages, groupName, [userB, userC]); - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A makes User C an admin - await adminPages.conversationList().getConversationLocator(groupName).open(); + await adminPages.conversationList().getConversation(groupName).open(); await adminPages.conversation().toggleGroupInformation(); await adminPages.conversation().makeUserAdmin(userC.fullName); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts index 5628cb3ee66..34c8950770a 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts @@ -59,7 +59,7 @@ test( await test.step('Team owner creates group conversation with team members', async () => { const {pages} = ownerPageManager.webapp; await createGroup(pages, conversationName, [member1, member2]); - await expect(pages.conversationList().getConversationLocator(conversationName)).toBeVisible(); + await expect(pages.conversationList().getConversation(conversationName)).toBeVisible(); }); await test.step('Team owner adds a service to newly created group', async () => { @@ -76,11 +76,11 @@ test( await test.step('All group participants send messages in a group', async () => { const {pages} = ownerPageManager.webapp; // Member1 and Member2 open the conversation (establish encrypted session) - await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); - await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member1PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); + await member2PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); // Wait for encryption to be established by checking that conversation is fully loaded - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); await pages.conversation().conversationTitle.waitFor({state: 'visible'}); // Now all members can send and receive encrypted messages @@ -101,7 +101,7 @@ test( ).toBeVisible(); // Owner verifies all messages are visible - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); await expect(pages.conversation().getMessage({content: `Hello from ${member1.firstName}!`})).toBeVisible(); await expect(pages.conversation().getMessage({content: `Hello from ${member2.firstName}!`})).toBeVisible(); }); @@ -109,7 +109,7 @@ test( await test.step('Team owner and group members react on received messages with reactions', async () => { const {pages} = ownerPageManager.webapp; // Owner reacts to member1's message with +1 (thumbs up) - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); const member1MessageForOwner = pages.conversation().getMessage({content: `Hello from ${member1.firstName}!`}); await member1MessageForOwner.waitFor({state: 'visible'}); // Wait for message to be ready await pages.conversation().reactOnMessage(member1MessageForOwner, 'plus-one'); @@ -119,7 +119,7 @@ test( await pages.conversation().reactOnMessage(member2MessageForOwner, 'plus-one'); // Member1 reacts to owner's message with heart (❤️) - await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member1PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); const ownerMessageForMember1 = member1PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${owner.firstName}!`}); @@ -132,7 +132,7 @@ test( await member1PageManager.webapp.pages.conversation().reactOnMessage(member2MessageForMember1, 'heart'); // Member2 reacts to owner's message with joy (😂) - await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member2PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); const ownerMessageForMember2 = member2PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${owner.firstName}!`}); @@ -148,13 +148,13 @@ test( await test.step('All group participants make sure they see reactions from other group participants', async () => { const {pages} = ownerPageManager.webapp; // Owner verifies they can see heart (❤️) and joy (😂) reactions on their message from member1 and member2 - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); const ownerMessage = pages.conversation().getMessage({content: `Hello from ${owner.firstName}!`}); await expect(pages.conversation().getReactionOnMessage(ownerMessage, 'heart')).toBeVisible(); await expect(pages.conversation().getReactionOnMessage(ownerMessage, 'joy')).toBeVisible(); // Member1 verifies they can see thumbs up (+1) and joy (😂) reactions on their message from owner and member2 - await member1PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member1PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); const member1Message = member1PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${member1.firstName}!`}); @@ -166,7 +166,7 @@ test( ).toBeVisible(); // Member2 verifies they can see thumbs up (+1) and heart (❤️) reactions on their message from owner and member1 - await member2PageManager.webapp.pages.conversationList().getConversationLocator(conversationName).open(); + await member2PageManager.webapp.pages.conversationList().getConversation(conversationName).open(); const member2Message = member2PageManager.webapp.pages .conversation() .getMessage({content: `Hello from ${member2.firstName}!`}); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts index d45f6081944..8bf4e0540e8 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts @@ -41,8 +41,8 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] const pageManager = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))); const {pages, modals, components} = pageManager.webapp; - const userBConversation = pages.conversationList().getConversationLocator(userB.fullName); - const groupConversation = pages.conversationList().getConversationLocator(groupName); + const userBConversation = pages.conversationList().getConversation(userB.fullName); + const groupConversation = pages.conversationList().getConversation(groupName); await test.step('User generates data', async () => { await userBConversation.open(); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts index 2fab4709c48..260377b78de 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsCall-TC-8755.spec.ts @@ -48,7 +48,7 @@ test( await test.step('Team owner creates a channel with available member', async () => { await pages.conversationList().clickCreateGroup(); await modals.createConversation().createChannel(channelName, {members: [member]}); - await expect(pages.conversationList().getConversationLocator(channelName)).toBeVisible(); + await expect(pages.conversationList().getConversation(channelName)).toBeVisible(); }); await test.step('Owner starts a call in channel', async () => { @@ -56,7 +56,7 @@ test( const response = await api.callingService.createInstance(member.password, member.email); callingServiceInstanceId = response.id; await api.callingService.setAcceptNextCall(callingServiceInstanceId); - await pages.conversationList().getConversationLocator(channelName).open(); + await pages.conversationList().getConversation(channelName).open(); await pages.conversation().clickCallButton(); } catch (error: unknown) { console.error('Error during call initiation:', error); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts index 119135287f3..edbb19636fe 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/channelsManagement-TC-8752.spec.ts @@ -42,7 +42,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat await test.step('Team owner creates a channel with available member', async () => { await ownerPages.conversationList().clickCreateGroup(); await ownerModals.createConversation().createChannel(conversation1, {members: [member]}); - await expect(ownerPages.conversationList().getConversationLocator(conversation1)).toBeVisible(); + await expect(ownerPages.conversationList().getConversation(conversation1)).toBeVisible(); }); await test.step('Team members leave the conversation', async () => { @@ -60,14 +60,14 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team owner confirm member left', async () => { - await ownerPages.conversationList().getConversationLocator(conversation1).open(); + await ownerPages.conversationList().getConversation(conversation1).open(); await expect(ownerPages.conversation().systemMessages.filter({hasText: `${member.fullName} left`})).toBeVisible(); }); await test.step('Team owner creates another channel', async () => { await ownerPages.conversationList().clickCreateGroup(); await ownerModals.createConversation().createChannel(conversation2, {members: [member]}); - await expect(ownerPages.conversationList().getConversationLocator(conversation2)).toBeVisible(); + await expect(ownerPages.conversationList().getConversation(conversation2)).toBeVisible(); }); await test.step('Team owner makes the member an admin', async () => { @@ -76,7 +76,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member confirms admin status', async () => { - await memberPages.conversationList().getConversationLocator(conversation2).open(); + await memberPages.conversationList().getConversation(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect(memberPages.conversation().adminsList.filter({hasText: member.fullName})).toBeVisible(); }); @@ -88,12 +88,12 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member verifies they have been removed by the owner', async () => { - await memberPages.conversationList().getConversationLocator(conversation2).open(); + await memberPages.conversationList().getConversation(conversation2).open(); await expect(memberPages.conversation().messageInput).not.toBeAttached(); }); await test.step('Team owner add member back to the same conversation', async () => { - await ownerPages.conversationList().getConversationLocator(conversation2).open(); + await ownerPages.conversationList().getConversation(conversation2).open(); await expect( ownerPages.conversation().systemMessages.filter({hasText: `You removed ${member.fullName}`}), ).toBeVisible(); @@ -102,7 +102,7 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team member confirms they have been added back to the conversation', async () => { - await memberPages.conversationList().getConversationLocator(conversation2).open(); + await memberPages.conversationList().getConversation(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect( memberPages.conversation().systemMessages.filter({hasText: `${owner.fullName} added you to the conversation`}), @@ -110,12 +110,12 @@ test('Channels Management', {tag: ['@TC-8752', '@crit-flow-web']}, async ({creat }); await test.step('Team owner promote member to admin', async () => { - await ownerPages.conversationList().getConversationLocator(conversation2).open(); + await ownerPages.conversationList().getConversation(conversation2).open(); await ownerPages.conversation().makeUserAdmin(member.fullName); }); await test.step('Team member confirms admin status', async () => { - await memberPages.conversationList().getConversationLocator(conversation2).open(); + await memberPages.conversationList().getConversation(conversation2).open(); await memberPages.conversation().toggleGroupInformation(); await expect(memberPages.conversation().adminsList.filter({hasText: member.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts index f875dbc2e4f..1b5bace921b 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/conversationManagement-TC-8636.spec.ts @@ -40,7 +40,7 @@ test('Conversation Management', {tag: ['@TC-8636', '@crit-flow-web']}, async ({c ...memberPages.map(page => PageManager.from(page)), ]; - const conversation = ownerPageManager.webapp.pages.conversationList().getConversationLocator(conversationName); + const conversation = ownerPageManager.webapp.pages.conversationList().getConversation(conversationName); await test.step('Team owner creates a group with all the five members', async () => { const {pages} = ownerPageManager.webapp; diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts index 1145a48cfd3..1160a5f794d 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupCalls-TC-8632.spec.ts @@ -49,7 +49,7 @@ test( await test.step('Owner starts call', async () => { const {pages, components} = ownerPageManager.webapp; - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); await pages.conversation().startCall(); await expect(components.calling().callCell).toBeVisible(); @@ -57,7 +57,7 @@ test( await test.step('Member joins call and goes full screen', async () => { const {pages, components} = memberPageManager.webapp; - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); const memberCalling = components.calling(); await expect(memberCalling.callCell).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts index 6bbc287bc51..63c1d605e82 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts @@ -43,7 +43,7 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe await test.step('Guest user accepts connection request from owner', async () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(ownerPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(ownerPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); }); await test.step('Owner and team member are in a group conversation together', async () => { @@ -51,7 +51,7 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe }); await test.step('Owner invites guest user to the group', async () => { - await ownerPages.conversationList().getConversationLocator(conversationName).open(); + await ownerPages.conversationList().getConversation(conversationName).open(); await ownerPages.conversation().clickConversationTitle(); await ownerPages.conversationDetails().clickAddPeopleButton(); await ownerPages.conversationDetails().addUsersToConversation([guestUser.fullName]); @@ -60,7 +60,7 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe }); await test.step('Guest user joins the group', async () => { - await expect(guestPages.conversationList().getConversationLocator(conversationName)).toBeVisible(); + await expect(guestPages.conversationList().getConversation(conversationName)).toBeVisible(); }); await test.step('Owner calls the group', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts index d59d01297f4..28d44592212 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/joinTeam-TC-8635.spec.ts @@ -44,10 +44,10 @@ test( const userAPages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(owner))).webapp.pages; await test.step('A sends text to owner', async () => { - await userAPages.conversationList().getConversationLocator(owner.fullName).open(); + await userAPages.conversationList().getConversation(owner.fullName).open(); await userAPages.conversation().sendMessage('Hello Team Owner!'); - await ownerPages.conversationList().getConversationLocator(userA.fullName).open(); + await ownerPages.conversationList().getConversation(userA.fullName).open(); await expect(ownerPages.conversation().getMessage({content: 'Hello Team Owner!'})).toBeVisible(); }); @@ -57,7 +57,7 @@ test( }); await test.step('Owner adds A to group chat and mentions him', async () => { - await ownerPages.conversationList().getConversationLocator('Test Group').open(); + await ownerPages.conversationList().getConversation('Test Group').open(); // Add user A to group chat await ownerPages.conversation().toggleGroupInformation(); @@ -69,7 +69,7 @@ test( }); await test.step('User A receives mention in group chat', async () => { - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); await expect(userAPages.conversation().getMessage({content: `@${userA.fullName}`})).toBeVisible(); }); }, diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts index 835dc35d0c6..cc930db7540 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesIn1On1-TC-8750.spec.ts @@ -57,14 +57,14 @@ test('Messages in 1:1', {tag: ['@TC-8750', '@crit-flow-web']}, async ({createTea // When a conversation between two non team members is created proteus will be used by default and later upgrade to mls. // To avoid loosing messages during the fast execution with playwright we wait for the upgrade is finished to open the MLS conversation. - await pages.conversationList().getConversationLocator(memberB.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(memberB.fullName, {protocol: 'mls'}).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect(pages.conversation().getImageLocator(memberA)).toBeVisible(); }); await test.step('User B can see the image in the conversation', async () => { await memberBPageManager.webapp.pages .conversationList() - .getConversationLocator(memberA.fullName, {protocol: 'mls'}) + .getConversation(memberA.fullName, {protocol: 'mls'}) .open(); // Verify that the image is visible in the conversation diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts index ee24cf69358..80943d6fe05 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInChannels-TC-8753.spec.ts @@ -54,13 +54,13 @@ test( await test.step('User A mentions User B in the channel', async () => { const {pages} = userAPageManager.webapp; - await pages.conversationList().getConversationLocator(channelName).open(); + await pages.conversationList().getConversation(channelName).open(); await pages.conversation().sendMessageWithUserMention(userB.fullName, messageText); }); await test.step('User B should receive mention', async () => { const {pages} = userBPageManager.webapp; - const conversation = pages.conversationList().getConversationLocator(channelName); + const conversation = pages.conversationList().getConversation(channelName); await expect(conversation.mentionIndicator).toBeVisible(); await conversation.open(); @@ -69,7 +69,7 @@ test( await test.step('User A sends image', async () => { const {pages, components} = userAPageManager.webapp; - await pages.conversationList().getConversationLocator(channelName).open(); + await pages.conversationList().getConversation(channelName).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect( diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts index 257c254c185..be1a1487819 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/messagesInGroups-TC-8751.spec.ts @@ -52,13 +52,13 @@ test( await test.step('User A mentions User B in the group', async () => { const {pages} = userAPageManager.webapp; - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); await pages.conversation().sendMessageWithUserMention(userB.fullName, messageText); }); await test.step('User B should receive mention', async () => { const {pages} = userBPageManager.webapp; - const conversation = pages.conversationList().getConversationLocator(conversationName); + const conversation = pages.conversationList().getConversation(conversationName); await expect(conversation.mentionIndicator).toBeVisible(); await conversation.open(); @@ -67,7 +67,7 @@ test( await test.step('User A sends image', async () => { const {pages, components} = userAPageManager.webapp; - await pages.conversationList().getConversationLocator(conversationName).open(); + await pages.conversationList().getConversation(conversationName).open(); await components.inputBarControls().clickShareImage(imageFilePath); await expect( diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts index 8ff53195cc3..24bb66b1ed6 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/oneOnOneCall-TC-8754.spec.ts @@ -46,7 +46,7 @@ test( const {pages} = userAPageManager.webapp; await api.callingService.setAcceptNextCall(callingServiceInstanceId); - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversation().clickCallButton(); }); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts index 94d2295db01..ab724494090 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/personalAccountLifecycle-TC-8638.spec.ts @@ -90,7 +90,7 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A sends a connection request to personal user B', async () => { const {pages, modals} = pageManagerA.webapp; await modals.userProfile().clickConnectButton(); - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await expect(pages.outgoingConnection().uniqueUsernameOutgoing).toContainText(userB.username); await expect(pages.outgoingConnection().getPendingConnectionIconLocator(userB.fullName)).toBeVisible(); }); @@ -98,17 +98,17 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user B accepts request from A', async () => { await pageManagerB.webapp.pages.conversationList().openPendingConnectionRequest(); await pageManagerB.webapp.pages.connectRequest().clickConnectButton(); - await expect(pageManagerB.webapp.pages.conversationList().getConversationLocator(userA.fullName)).toBeVisible(); + await expect(pageManagerB.webapp.pages.conversationList().getConversation(userA.fullName)).toBeVisible(); }); await test.step('Personal user A send message to personal user B', async () => { const {pages} = pageManagerA.webapp; - await pages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage(`Hello! ${userA.firstName} here.`); }); await test.step('Personal user B can see the message from user A', async () => { - await pageManagerB.webapp.pages.conversationList().getConversationLocator(userA.fullName).open(); + await pageManagerB.webapp.pages.conversationList().getConversation(userA.fullName).open(); await expect( pageManagerB.webapp.pages.conversation().getMessage({content: `Hello! ${userA.firstName} here.`}), ).toBeVisible(); @@ -116,7 +116,7 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A blocks personal user B', async () => { const {pages, modals} = pageManagerA.webapp; - const conversation = pages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}); + const conversation = pages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}); await expect(conversation).not.toContainText('Blocked'); const contextMenu = await conversation.openContextMenu(); @@ -135,7 +135,7 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await pageManagerC.webapp.components.conversationSidebar().clickConnectButton(); await pageManagerC.webapp.pages.startUI().selectUsers(userA.username); await pageManagerC.webapp.modals.userProfile().clickConnectButton(); - await pageManagerC.webapp.pages.conversationList().getConversationLocator(userA.fullName).open(); + await pageManagerC.webapp.pages.conversationList().getConversation(userA.fullName).open(); await expect(pageManagerC.webapp.pages.outgoingConnection().uniqueUsernameOutgoing).toContainText(userA.username); await expect( pageManagerC.webapp.pages.outgoingConnection().getPendingConnectionIconLocator(userA.fullName), @@ -150,12 +150,12 @@ test('Personal Account Lifecycle', {tag: ['@TC-8638', '@crit-flow-web']}, async await test.step('Personal user A send message to personal user C', async () => { const {pages} = pageManagerA.webapp; - await pages.conversationList().getConversationLocator(userC.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userC.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage(`Hello! ${userA.firstName} here.`); }); await test.step('Personal user C can see the message from user A', async () => { - await pageManagerC.webapp.pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await pageManagerC.webapp.pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await expect( pageManagerC.webapp.pages.conversation().getMessage({content: `Hello! ${userA.firstName} here.`}), ).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts index a897b404729..765b9a9ba00 100644 --- a/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/DeepLinks/deepLinks.spec.ts @@ -153,7 +153,7 @@ test.describe('Deep Links', () => { ); await test.step('User B sends all profile links to User A', async () => { - await userBPages.conversationList().getConversationLocator(userA.fullName).open(); + await userBPages.conversationList().getConversation(userA.fullName).open(); const userLabels = ['A', 'B', 'C', 'D']; @@ -164,17 +164,17 @@ test.describe('Deep Links', () => { await test.step('User B creates group and sends conversation join link to User A', async () => { await createGroup(userBPages, groupName, []); - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await userBPages.conversationDetails().openGuestOptions(); const conversationJoinLink = await userBPages.guestOptions().createLink(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(`Group conversation: ${conversationJoinLink}`); }); await test.step('User A verifies information for every user profile modal', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); // Verify information for User A await userAPages.conversation().getMessage({content: 'User A:'}).getByRole('link').click(); @@ -219,7 +219,7 @@ test.describe('Deep Links', () => { await expect(userAModals.confirm().modal).toBeVisible(); await expect(userAModals.confirm().actionButton).toContainText('Join Conversation'); await userAModals.confirm().actionButton.click(); - await expect(userAPages.conversationList().getConversationLocator(groupName)).toBeVisible(); + await expect(userAPages.conversationList().getConversation(groupName)).toBeVisible(); }); }); } diff --git a/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts b/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts index 7fe985d9438..89cccc02238 100644 --- a/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Delete/delete.spec.ts @@ -43,7 +43,7 @@ test.describe('Delete', () => { const deviceA = (await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB)))).webapp.pages; const deviceB = (await PageManager.from(createPage(withLogin(userA, {confirmNewHistory: true})))).webapp.pages; - await deviceB.conversationList().getConversationLocator(userB.fullName).open(); + await deviceB.conversationList().getConversation(userB.fullName).open(); await deviceA.conversation().sendMessage('Test Message'); @@ -67,8 +67,8 @@ test.describe('Delete', () => { await test.step('Create test group', async () => { await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().getConversationLocator('Test Group').open(); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); }); let messageA: Locator; @@ -102,7 +102,7 @@ test.describe('Delete', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); const systemMessage = userAPages.conversation().systemMessages.last(); @@ -119,7 +119,7 @@ test.describe('Delete', () => { async ({createPage}) => { const {components, pages} = (await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB)))) .webapp; - const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await pages.conversationList().getConversation(userB.fullName).open(); let message: Locator; await test.step('Send and delete message', async () => { @@ -162,13 +162,13 @@ test.describe('Delete', () => { const userAPages = PageManager.from(userAPage).webapp.pages; let userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const messageA = userAPages.conversation().getMessage({sender: userA}); await expect(messageA).toContainText('Test Message'); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); let messageB = userBPages.conversation().getMessage({sender: userA}); await expect(messageB).toContainText('Test Message'); @@ -176,7 +176,7 @@ test.describe('Delete', () => { await userAPages.conversation().deleteMessage(messageA, 'Everyone'); userBPages = (await PageManager.from(createPage(context, withLogin(userB)))).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName).open(); + await userBPages.conversationList().getConversation(userA.fullName).open(); messageB = userBPages.conversation().getMessage({sender: userA}); @@ -194,8 +194,8 @@ test.describe('Delete', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const messageA = userAPages.conversation().getMessage({sender: userA}); @@ -224,14 +224,14 @@ test.describe('Delete', () => { await test.step('Create test group', async () => { await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); }); let messageA: Locator; let messageB: Locator; await test.step('Send and delete message from user A to B', async () => { - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().sendMessage('Test Message'); messageA = userAPages.conversation().getMessage({sender: userA}); @@ -328,8 +328,8 @@ test.describe('Delete', () => { const userBPages = PageManager.from(pageB).webapp.pages; await test.step('Await mls conversation creation', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); }); await test.step('Execute send action', async () => { @@ -367,16 +367,16 @@ test.describe('Delete', () => { await test.step('Create group as second conversation', async () => { // We need to create a second conversation in order to switch to it to ensure the unread marker can be shown on the not open conversation await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); }); await test.step('Send message from user A to B', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().sendMessage('Test Message'); }); await test.step('Check user B has a unread conversation with A', async () => { - const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); + const conversation = userBPages.conversationList().getConversation(userA.fullName); await expect(conversation.getByTestId('status-unread')).toBeVisible(); }); @@ -387,7 +387,7 @@ test.describe('Delete', () => { }); await test.step('Check B has no unread indicator anymore', async () => { - const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); + const conversation = userBPages.conversationList().getConversation(userA.fullName); await expect(conversation.getByTestId('status-unread')).not.toBeVisible(); await conversation.open(); diff --git a/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts b/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts index a5b0502d2ca..85108d48519 100644 --- a/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Drive/drive.spec.ts @@ -80,8 +80,8 @@ test.describe('Conversations', () => { }); await test.step('User A sends an image to User B in a group conversation', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().clickSendMessage(); @@ -127,11 +127,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a multipart message in a group conversation', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().setMessageInput(initialMessageText); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); const multipartMessage = userBPages.conversation().getMessage({sender: userA}); await expect(multipartMessage).toContainText(initialMessageText); @@ -168,11 +168,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a multipart message to User B in a group conversation', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().setMessageInput(initialMessageText); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); const multipartMessage = userBPages.conversation().getMessage({sender: userA}); await expect(multipartMessage).toContainText(initialMessageText); @@ -208,11 +208,11 @@ test.describe('Conversations', () => { }); await test.step('User A sends a message with assets in a group conversation', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAComponents.inputBarControls().clickShareFile(imageFilePath); await userAComponents.inputBarControls().clickShareFile(videoFilePath); await userAComponents.inputBarControls().clickSendMessage(); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await expect(getImageInMultipartMessageLocator(userBPages.conversation(), userA)).toBeVisible(); await expect(getVideoInMultipartMessageLocator(userBPages.conversation(), userA)).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts index 0a863fde488..e7f8e0fe655 100644 --- a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts @@ -51,7 +51,7 @@ test.describe('Edit', () => { const pages = (await PageManager.from(createPage(withLogin(userA)))).webapp.pages; await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await pages.conversation().sendMessage('Test Message'); const message = pages.conversation().getMessage({sender: userA}); @@ -73,7 +73,7 @@ test.describe('Edit', () => { // Device 2 is intentionally created after device 1 to ensure the history info warning is confirmed const deviceB = (await PageManager.from(createPage(withLogin(userA, {confirmNewHistory: true})))).webapp.pages; - await deviceB.conversationList().getConversationLocator(userB.fullName).open(); + await deviceB.conversationList().getConversation(userB.fullName).open(); await deviceA.conversation().sendMessage('Message from device 1'); @@ -97,8 +97,8 @@ test.describe('Edit', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); const message = userBPages.conversation().getMessage({sender: userA}); @@ -134,16 +134,16 @@ test.describe('Edit', () => { await test.step('Create group as second conversation', async () => { // We need to create a second conversation in order to switch to it to ensure the unread marker can be shown on the not open conversation await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); }); await test.step('Send message from user A to B', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test Message'); }); await test.step('Check user B has a unread conversation with A containing the sent message', async () => { - const conversation = userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); + const conversation = userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}); await expect(conversation.unreadIndicator).toBeVisible(); await conversation.open(); @@ -152,7 +152,7 @@ test.describe('Edit', () => { }); await test.step("Open group conversation to ensure new messages won't be read immediately", async () => { - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); await expect(userBPages.conversation().conversationTitle).toHaveText('Test Group'); }); @@ -164,7 +164,7 @@ test.describe('Edit', () => { }); await test.step('Check B received the updated message without marking the conversation as unread', async () => { - const conversation = userBPages.conversationList().getConversationLocator(userA.fullName); + const conversation = userBPages.conversationList().getConversation(userA.fullName); await expect(conversation.unreadIndicator).not.toBeVisible(); await conversation.open(); @@ -182,8 +182,8 @@ test.describe('Edit', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test'); const sentMessage = userAPages.conversation().getMessage({sender: userA}); @@ -207,7 +207,7 @@ test.describe('Edit', () => { const pages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); await createGroup(pages, 'Test Group', [userB]); // The message detail view is only available for group conversations - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await pages.conversation().sendMessage('Test message'); const message = pages.conversation().getMessage({sender: userA}); diff --git a/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts b/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts index f88b3ab7851..99fc0d17163 100644 --- a/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Folders/folders.spec.ts @@ -31,7 +31,7 @@ import {createGroup} from 'test/e2e_tests/utils/userActions'; */ async function createCustomFolder(pageManager: PageManager, conversationName: string, folderName: string) { const {pages, modals} = pageManager.webapp; - const contextMenu = await pages.conversationList().getConversationLocator(conversationName).openContextMenu(); + const contextMenu = await pages.conversationList().getConversation(conversationName).openContextMenu(); await contextMenu.moveToButton.click(); await contextMenu.getByRole('button', {name: 'Create new folder'}).click(); @@ -47,7 +47,7 @@ async function createCustomFolder(pageManager: PageManager, conversationName: st */ async function moveConversationToFolder(pageManager: PageManager, conversationName: string, folderName: string) { const pages = pageManager.webapp.pages; - const contextMenu = await pages.conversationList().getConversationLocator(conversationName).openContextMenu(); + const contextMenu = await pages.conversationList().getConversation(conversationName).openContextMenu(); await contextMenu.moveToButton.click(); await contextMenu.getByRole('button', {name: folderName, exact: true}).click(); } @@ -72,7 +72,7 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Step 1: User A opens 1:1 conversation with User B - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); // Step 2: User A moves 1:1 conversation with User B into new custom folder await createCustomFolder(userAPageManager, userB.fullName, customFolderName); // Step 3: 1:1 conversation with User B is in the custom folder @@ -90,7 +90,7 @@ test.describe('Folders', () => { await createGroup(userAPages, conversationName, [userB]); // Step 1: User A opens group conversation with User B - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); // Step 2: User A moves group conversation with User B in new custom folder await createCustomFolder(userAPageManager, conversationName, customFolderName); // Step 3: Group conversation with User B is in the custom folder @@ -157,7 +157,7 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Preconditions: Conversation is in custom folder - const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await userAPages.conversationList().getConversation(userB.fullName).open(); await createCustomFolder(userAPageManager, userB.fullName, customFolderName); await test.step("User A clicks 'remove from custom folder' button", async () => { @@ -182,7 +182,7 @@ test.describe('Folders', () => { const {pages: userAPages, modals: userAModals} = userAPageManager.webapp; // Step 1: User A opens 1:1 conversation with User B - const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await userAPages.conversationList().getConversation(userB.fullName).open(); // Step 2: User A wants to move 1:1 conversation with User B into custom folder const contextMenu = await conversation.openContextMenu(); await contextMenu.moveToButton.click(); @@ -198,7 +198,7 @@ test.describe('Folders', () => { const customFolderName = 'Custom-Folder'; // Preconditions: Conversation is in custom folder - const conversation = await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await userAPages.conversationList().getConversation(userB.fullName).open(); await createCustomFolder(userAPageManager, userB.fullName, customFolderName); await test.step('User A removes conversation with User B from custom folder', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts b/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts index a7e7448acb6..a4835dd1a3f 100644 --- a/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/GroupConversation/groupConversation.spec.ts @@ -41,7 +41,7 @@ test.describe('Group Conversation', () => { const userAPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(userAPages, groupName, []); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().sendMessage('Message'); const message = userAPages.conversation().getMessage({content: 'Message'}); @@ -95,8 +95,8 @@ test.describe('Group Conversation', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, groupName, [userB, userC]); - const adminGroupLocator = await userAPages.conversationList().getConversationLocator(groupName).open(); - const memberGroupLocator = userBPages.conversationList().getConversationLocator(groupName); + const adminGroupLocator = await userAPages.conversationList().getConversation(groupName).open(); + const memberGroupLocator = userBPages.conversationList().getConversation(groupName); // Pre-condition: Ensure the member can see the group before the creator deletes it await expect(memberGroupLocator).toBeVisible(); @@ -136,7 +136,7 @@ test.describe('Group Conversation', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, groupName, [userB]); - await expect(userBPages.conversationList().getConversationLocator(groupName)).toBeVisible(); + await expect(userBPages.conversationList().getConversation(groupName)).toBeVisible(); const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); @@ -144,7 +144,7 @@ test.describe('Group Conversation', () => { await blankTab.goto('about:blank'); await blankTab.bringToFront(); - const userAConversation = await userAPages.conversationList().getConversationLocator(groupName).open(); + const userAConversation = await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().deleteGroupButton.click(); await userAModals.confirm().actionButton.click(); @@ -188,11 +188,11 @@ test.describe('Group Conversation', () => { for (const group of groups) { const {pages, modals} = PageManager.from(group.owner).webapp; - await pages.conversationList().getConversationLocator(group.name).open(); + await pages.conversationList().getConversation(group.name).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversationDetails().deleteGroupButton.click(); await modals.confirm().actionButton.click(); - await expect(userCPages.conversationList().getConversationLocator(group.name)).not.toBeAttached({ + await expect(userCPages.conversationList().getConversation(group.name)).not.toBeAttached({ timeout: 20_000, }); @@ -212,10 +212,10 @@ test.describe('Group Conversation', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, groupName, [userA]); - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); // User A leaves conversation through options menu from conversation list - const contextMenu = await userAPages.conversationList().getConversationLocator(groupName).openContextMenu(); + const contextMenu = await userAPages.conversationList().getConversation(groupName).openContextMenu(); await contextMenu.leaveConversationButton.click(); await userAModals.leaveConversation().confirmButton.click(); @@ -230,7 +230,7 @@ test.describe('Group Conversation', () => { const pages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(pages, groupName, [userB, userC]); - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().toggleGroupInformation(); await expect(pages.conversationDetails().groupMembers.filter({hasText: userB.fullName})).toBeVisible(); @@ -252,7 +252,7 @@ test.describe('Group Conversation', () => { const pages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(pages, groupName, [userB]); - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().toggleGroupInformation(); await expect(pages.conversationDetails().groupMembers.filter({hasText: userB.fullName})).toBeVisible(); @@ -271,7 +271,7 @@ test.describe('Group Conversation', () => { ]); await createGroup(userAPages, groupName, [userB]); - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await expect(userBPages.conversationDetails().groupAdmins.filter({hasText: userA.fullName})).toBeVisible(); @@ -290,12 +290,12 @@ test.describe('Group Conversation', () => { await test.step('Setup: Create group and promote User B to Admin', async () => { await createGroup(userAPages, groupName, [userB, userC]); - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversation().makeUserAdmin(userB.fullName); // Verify User B is an admin - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().toggleGroupInformation(); await expect(userBPages.conversationDetails().groupAdmins.filter({hasText: userB.fullName})).toBeVisible(); }); diff --git a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts index 2f3b44cdcc3..8219941b1c7 100644 --- a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts @@ -14,7 +14,7 @@ import {createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActio */ const generateGroupGuestsLink = async (pages: PageManager['webapp']['pages'], groupName: string, password?: string) => { - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().toggleGroupInformation(); await pages.conversationDetails().openGuestOptions(); @@ -75,7 +75,7 @@ test.describe('Guestroom', () => { const {pages, modals} = PageManager.from(userAPage).webapp; await createGroup(pages, groupName, []); - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await test.step('User A sees an error message when trying to create a password secured link with a weak password', async () => { await pages.conversation().toggleGroupInformation(); @@ -179,11 +179,11 @@ test.describe('Guestroom', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await expect(ownerPages.conversationList().getConversationLocator(guestUser.fullName)).toBeAttached(); + await expect(ownerPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); await test.step('Owner creates a group with guest', async () => { await createGroup(ownerPages, groupName, [guestUser]); - await ownerPages.conversationList().getConversationLocator(groupName).open(); + await ownerPages.conversationList().getConversation(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); await expect(ownerPages.conversationDetails().groupMembers.filter({hasText: guestUser.fullName})).toBeVisible(); }); @@ -214,7 +214,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().getConversationLocator(groupName).open(); + await ownerPages.conversationList().getConversation(groupName).open(); // Owner creates a guest link await ownerPages.conversation().toggleGroupInformation(); @@ -243,7 +243,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(ownerPage).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().getConversationLocator(groupName).open(); + await ownerPages.conversationList().getConversation(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); await ownerPages.conversationDetails().openGuestOptions(); @@ -276,7 +276,7 @@ test.describe('Guestroom', () => { await ownerPages.groupCreation().setGroupName(groupName); await ownerPages.groupCreation().clickCreateGroupButton(); - await ownerPages.conversationList().getConversationLocator(groupName).open(); + await ownerPages.conversationList().getConversation(groupName).open(); await ownerPages.conversation().toggleGroupInformation(); // UserA sees guest options label shows ON in conversation details @@ -344,7 +344,7 @@ test.describe('Guestroom', () => { const ownerPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages; await createGroup(ownerPages, groupName, []); - await ownerPages.conversationList().getConversationLocator(groupName).open(); + await ownerPages.conversationList().getConversation(groupName).open(); await verify(ownerPages); await ownerPages.conversation().invitePeopleButton.click(); @@ -519,7 +519,7 @@ test.describe('Guestroom', () => { await guestPages.conversationJoin().joinAsMemberButton.click(); await guestPages.conversation().conversationTitle.waitFor({state: 'visible', timeout: LOGIN_TIMEOUT}); - await expect(guestPages.conversationList().getConversationLocator(groupName)).toBeVisible(); + await expect(guestPages.conversationList().getConversation(groupName)).toBeVisible(); }, ); @@ -621,7 +621,7 @@ test.describe('Guestroom', () => { }, ); - await expect(guestPages.conversationList().getConversationLocator(groupName)).toBeVisible(); + await expect(guestPages.conversationList().getConversation(groupName)).toBeVisible(); }, ); }); diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index f3ef9df75ed..a853f4ee24a 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -53,9 +53,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -104,7 +104,7 @@ test.describe('History Backup', () => { await test.step('Validate conversation is still visible with all messages after restoring backup', async () => { await userAComponents2.conversationSidebar().allConversationsButton.click(); - await userAPages2.conversationList().getConversationLocator(conversationName).open(); + await userAPages2.conversationList().getConversation(conversationName).open(); await expect(userAPages2.conversation().getMessage({sender: userB})).toContainText(messageUserB); await expect(userAPages2.conversation().getMessage({sender: userA})).toContainText(messageUserA); }); @@ -127,9 +127,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -177,9 +177,9 @@ test.describe('History Backup', () => { const renamedConversationName = 'renamedConversationName'; await test.step('User A and B write in their group conversation', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -189,7 +189,7 @@ test.describe('History Backup', () => { await userAComponents.conversationSidebar().clickPreferencesButton(); backupName = await createAndSaveBackup(testInfo, userAPageManager); await userAComponents.conversationSidebar().allConversationsButton.click(); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); }); await test.step('User B renames group conversation', async () => { @@ -205,10 +205,10 @@ test.describe('History Backup', () => { await test.step('Validate User A sees renamed conversation and system message', async () => { // User A sees renamed conversation await userAComponents.conversationSidebar().allConversationsButton.click(); - await expect(userAPages.conversationList().getConversationLocator(renamedConversationName)).toBeVisible(); + await expect(userAPages.conversationList().getConversation(renamedConversationName)).toBeVisible(); // User A sees system message that User B had renamed the conversation - await userAPages.conversationList().getConversationLocator(renamedConversationName).open(); + await userAPages.conversationList().getConversation(renamedConversationName).open(); const renamedSystemMessage = userAPages .conversation() .systemMessages.filter({hasText: `${userB.fullName} renamed the conversation`}); @@ -234,9 +234,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -246,7 +246,7 @@ test.describe('History Backup', () => { }); await test.step('User A archives 1:1 conversation with User B', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().conversationInfoButton.click(); await userAPages.conversationDetails().archiveButton.click(); }); @@ -271,11 +271,11 @@ test.describe('History Backup', () => { await test.step('Validate muted and archived state are the same', async () => { await userAComponents.conversationSidebar().allConversationsButton.click(); - const conversation = await userAPages.conversationList().getConversationLocator(conversationName).open(); + const conversation = await userAPages.conversationList().getConversation(conversationName).open(); await expect(conversation.mutedIndicator).toBeVisible(); await userAComponents.conversationSidebar().archiveButton.click(); - const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); + const archivedConversation = userAPages.conversationList().getConversation(userB.fullName); await expect(archivedConversation).toBeVisible(); }); }, @@ -297,9 +297,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -346,9 +346,9 @@ test.describe('History Backup', () => { const messageUserB = 'Message from User B'; await test.step('User A and User B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage(messageUserB); }); @@ -372,7 +372,7 @@ test.describe('History Backup', () => { await test.step('Validate deleted group conversation is no longer visible', async () => { await userAComponents.conversationSidebar().allConversationsButton.click(); - await expect(userAPages.conversationList().getConversationLocator(conversationName)).not.toBeVisible(); + await expect(userAPages.conversationList().getConversation(conversationName)).not.toBeVisible(); }); }, ); @@ -391,9 +391,9 @@ test.describe('History Backup', () => { await createGroup(userBPages, conversationName, [userA]); await test.step('User A and User B write messages to each other', async () => { - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage('Message from User A'); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendMessage('Message from User B'); await expect(userAPages.conversation().messageItems).toHaveCount(2); }); @@ -421,7 +421,7 @@ test.describe('History Backup', () => { await test.step('Validate user A cannot send messages in the left group', async () => { await userAComponents.conversationSidebar().allConversationsButton.click(); - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await expect(userAPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); await expect(userAPages.conversation().messageInput).toBeHidden(); }); diff --git a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts index 7ab882fb1d6..46b3779bb6c 100644 --- a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts @@ -46,7 +46,7 @@ test.describe('In Conversation Search', () => { const userBPages = PageManager.from(userBPage).webapp.pages; // Preconditions: User B sends media from all categories (images, links, audio and files) - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // Image await shareAssetHelper(getImageFilePath(), userBPage, userBPage.getByRole('button', {name: 'Add picture'})); // Audio @@ -54,7 +54,7 @@ test.describe('In Conversation Search', () => { // File await shareAssetHelper(getTextFilePath(), userBPage, userBPage.getByRole('button', {name: 'Add file'})); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toHaveCount(3); await userAPages.conversation().searchButton.click(); @@ -77,11 +77,11 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().enableSelfDeletingMessages(); await userBPages.conversation().sendMessage('Gone in 10s'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); await userAPage.waitForTimeout(10_000); @@ -106,11 +106,11 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().enableSelfDeletingMessages(); await userBPages.conversation().sendMessage('Gone in 10s'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); await userAPages.conversation().searchButton.click(); @@ -138,8 +138,8 @@ test.describe('In Conversation Search', () => { const conversationName = 'Test Group'; await createGroup(userAPages, conversationName, [userB]); - await userBPages.conversationList().getConversationLocator(conversationName).open(); - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); for (let imageCount = 0; imageCount < 10; imageCount++) { await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); @@ -167,10 +167,10 @@ test.describe('In Conversation Search', () => { const {pages: userAPages, modals: userAModals} = PageManager.from(pageA).webapp; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageA, pageA.getByRole('button', {name: 'Add picture'})); await userAPages.conversation().searchButton.click(); @@ -186,10 +186,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // Step: User B sends several links - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); // Step: User B sends several links await userAPages.conversation().searchButton.click(); @@ -205,13 +205,13 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(pageA).webapp.pages; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); for (let fileCount = 0; fileCount < 10; fileCount++) { await shareAssetHelper(getTextFilePath(), pageB, pageB.getByRole('button', {name: 'Add file'})); } - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); for (let fileCount = 0; fileCount < 10; fileCount++) { await shareAssetHelper(getTextFilePath(), pageA, pageA.getByRole('button', {name: 'Add file'})); } @@ -232,10 +232,10 @@ test.describe('In Conversation Search', () => { const userAPages = PageManager.from(pageA).webapp.pages; const userBPages = PageManager.from(pageB).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getImageFilePath(), pageB, pageB.getByRole('button', {name: 'Add picture'})); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); const messageWithImage = userAPages.conversation().getMessage({sender: userB}); await expect(messageWithImage).toBeVisible(); @@ -255,10 +255,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('User A Message'); await userAPages.conversation().searchButton.click(); @@ -283,10 +283,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Here is a link: [LinkPreview]https://www.kaufland.de'); await userAPages.conversation().searchButton.click(); @@ -304,10 +304,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('https://www.kaufland.de'); await expect(userBPages.conversation().getMessage({content: 'https://www.kaufland.de'})).toBeVisible(); @@ -326,10 +326,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message: Papaya'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('User A message: Guava'); const messageUserB = userBPages.conversation().getMessage({sender: userB}); @@ -351,13 +351,13 @@ test.describe('In Conversation Search', () => { ]); // User B sends the first (older) message - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Older Message from User B'); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); // User A sends the second (newer) message - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Newer Message from User A'); await userAPages.conversation().searchButton.click(); @@ -378,8 +378,8 @@ test.describe('In Conversation Search', () => { const specialWord = 'Crème brûlée'; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage(`Message with diacritical letter: ${specialWord}`); await expect(userAPages.conversation().getMessage({content: specialWord})).toBeAttached(); @@ -397,8 +397,8 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('User B message'); await expect(userAPages.conversation().getMessage({sender: userB})).toBeVisible(); @@ -419,10 +419,10 @@ test.describe('In Conversation Search', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Papaya'); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await expect(userAPages.conversation().getMessage({content: 'Papaya'})).toBeVisible(); await userAPages.conversation().sendMessage(`Message from User A: 1`); await userAPages.conversation().sendMessage('Empty\n'.repeat(50)); diff --git a/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts index 9c34098390a..e3b8ce4237c 100644 --- a/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/LinkPreview/linkPreview.spec.ts @@ -37,8 +37,8 @@ test.describe('Link Preview', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const linkConfigs = [ { diff --git a/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts b/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts index da2528a63ad..1d79b204512 100644 --- a/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Localization/localization.spec.ts @@ -140,7 +140,7 @@ test.describe('Localization', () => { await pages.startUI().component.getByRole('button', {name: userB.fullName}).click(); await modals.userProfile().startConversationButton.click(); - const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await pages.conversationList().getConversation(userB.fullName).open(); const messagePlaceholder = page.locator('[data-uie-name="input-placeholder"]'); await expect(messagePlaceholder).toHaveText(deTranslations['tooltipConversationInputPlaceholder']); diff --git a/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts b/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts index 3d250ca3b80..58ec70ce50c 100644 --- a/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Logs/logs.spec.ts @@ -27,8 +27,8 @@ test.describe('Logs', () => { const messageA = 'Hello from UserA! This is a secret message.'; const messageB = 'Hi from UserB! No logging allowed.'; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(messageA); await userBPages.conversation().sendMessage(messageB); diff --git a/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts b/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts index eaee7860521..6909ba7cabe 100644 --- a/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Markdown/markdown.spec.ts @@ -68,8 +68,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage(message); for (const pages of [userAPages, userBPages]) { @@ -88,7 +88,7 @@ test.describe('Markdown', () => { const userAPageManager = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))); const {pages, modals} = userAPageManager.webapp; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().sendTypedMessage(targetUrl); const message = pages.conversation().getMessage({sender: userA}); @@ -110,8 +110,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages), PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const longCodeMessage = '```\nconst a = 5;\nconst b = 10;\nconsole.log(a + b);\n```'; await userAPages.conversation().sendMessage(longCodeMessage); @@ -129,8 +129,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage('**Bold**, *Italic* and `Code`'); for (const pages of [userAPages, userBPages]) { @@ -148,8 +148,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendTypedMessage('Start **Bold** Message'); const sentMessageA = userAPages.conversation().getMessage({sender: userA}); @@ -179,8 +179,8 @@ test.describe('Markdown', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const linkText = 'Wire Website'; const url = 'https://wire.com'; diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 286243724a0..25d70b6a85f 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -23,11 +23,11 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Mention Group', [userB]); // User A sends a message with a mention - await userAPages.conversationList().getConversationLocator('Mention Group').open(); + await userAPages.conversationList().getConversation('Mention Group').open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey'); // User B receives the message - await userBPages.conversationList().getConversationLocator('Mention Group').open(); + await userBPages.conversationList().getConversation('Mention Group').open(); const messageOnUserB = userBPages.conversation().getMessage({content: 'Hey', sender: userA}); await expect(messageOnUserB).toBeVisible(); const mentionOnUserB = messageOnUserB.getByRole('button', {name: `@${userB.fullName}`}); @@ -45,11 +45,11 @@ test.describe('Mention', () => { ]); await createGroup(userAPages, 'Multi-Mention Group', [userB, userC]); - await userBPages.conversationList().getConversationLocator('Multi-Mention Group').open(); - await userCPages.conversationList().getConversationLocator('Multi-Mention Group').open(); + await userBPages.conversationList().getConversation('Multi-Mention Group').open(); + await userCPages.conversationList().getConversation('Multi-Mention Group').open(); // User A sends a message with multiple mentions - await userAPages.conversationList().getConversationLocator('Multi-Mention Group').open(); + await userAPages.conversationList().getConversation('Multi-Mention Group').open(); const conversationPageA = userAPages.conversation(); await conversationPageA.messageInput.fill('Hello '); @@ -79,9 +79,9 @@ test.describe('Mention', () => { await test.step('Create group', async () => { await createGroup(userAPages, 'Edit-Mention Group', [userB, userC]); - await userAPages.conversationList().getConversationLocator('Edit-Mention Group').open(); - await userBPages.conversationList().getConversationLocator('Edit-Mention Group').open(); - await userCPages.conversationList().getConversationLocator('Edit-Mention Group').open(); + await userAPages.conversationList().getConversation('Edit-Mention Group').open(); + await userBPages.conversationList().getConversation('Edit-Mention Group').open(); + await userCPages.conversationList().getConversation('Edit-Mention Group').open(); }); await test.step('User A sends an initial message mentioning userB', async () => { @@ -122,8 +122,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); @@ -145,8 +145,8 @@ test.describe('Mention', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User A sends a self deleting message with a mention await userAPages.conversation().enableSelfDeletingMessages(); @@ -174,8 +174,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User A sends a message with the same person mentioned twice const conversationPageA = userAPages.conversation(); @@ -199,7 +199,7 @@ test.describe('Mention', () => { {tag: ['@TC-3493', '@regression']}, async ({createPage}) => { const userAPages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); const conversationPageA = userAPages.conversation(); await conversationPageA.messageInput.fill(''); // Clear input @@ -223,8 +223,8 @@ test.describe('Mention', () => { createPage(withLogin(userA), withConnectedUser(userB)).then(page => PageManager.from(page).webapp.pages), createPage(withLogin(userB)).then(page => PageManager.from(page).webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const conversationPageA = userAPages.conversation(); @@ -256,7 +256,7 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Draft Group', [userB]); const conversationPageA = userAPages.conversation(); - const draftGroupConversation = userAPages.conversationList().getConversationLocator('Draft Group'); + const draftGroupConversation = userAPages.conversationList().getConversation('Draft Group'); await test.step('Draft a message with a mention in the group chat', async () => { await draftGroupConversation.open(); await conversationPageA.messageInput.fill('Draft message with '); @@ -267,7 +267,7 @@ test.describe('Mention', () => { }); await test.step('Switch to the 1:1 chat', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await expect(conversationPageA.messageInput).toBeEmpty(); }); @@ -288,8 +288,8 @@ test.describe('Mention', () => { pm => pm.webapp.pages, ); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User A sends a message with a mention to User B await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); @@ -313,15 +313,15 @@ test.describe('Mention', () => { // Create and open a group conversation for userB to ensure the message from A won't be read immediately await createGroup(userBPages, 'Distraction Group', [userC]); - await userBPages.conversationList().getConversationLocator('Distraction Group').open(); + await userBPages.conversationList().getConversation('Distraction Group').open(); // User A opens conversation with user B and sends a message with mention - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey, you have an unread mention!'); // User B is in the 'Distraction Group', so the conversation with user A is unread. // Now check for the mention indicator in the conversation list. - const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); + const {mentionIndicator} = userBPages.conversationList().getConversation(userA.fullName); await expect(mentionIndicator).toBeVisible(); }, ); @@ -338,11 +338,11 @@ test.describe('Mention', () => { await test.step('Create and open a distraction group conversation for User B', async () => { // userA creates the group, userB opens it. This is the distraction. await createGroup(userAPages, 'Distraction Group', [userB]); - await userBPages.conversationList().getConversationLocator('Distraction Group').open(); + await userBPages.conversationList().getConversation('Distraction Group').open(); }); await test.step('User A tries to call User B in 1:1 conversation but B declines', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().startCall(); await userBPages.calling().leaveCallButton.click(); await userAPages.calling().leaveCallButton.click(); @@ -361,7 +361,7 @@ test.describe('Mention', () => { }); await test.step('Verify User B sees both unread mention and unread message indicators for 1:1 conversation', async () => { - const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); + const {mentionIndicator} = userBPages.conversationList().getConversation(userA.fullName); await expect(mentionIndicator).toBeVisible(); }); }, @@ -377,11 +377,11 @@ test.describe('Mention', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName); - const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); + const {mentionIndicator} = userBPages.conversationList().getConversation(userA.fullName); await expect(mentionIndicator).toBeVisible(); await userAPages.conversation().deleteMessage(userAPages.conversation().getMessage({sender: userA}), 'Everyone'); @@ -398,8 +398,8 @@ test.describe('Mention', () => { PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage(`@${userB.fullName} Hello`); for (const pages of [userAPages, userBPages]) { @@ -415,7 +415,7 @@ test.describe('Mention', () => { {tag: ['@TC-3532', '@regression']}, async ({createPage}) => { const {pages} = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().messageInput.pressSequentially(`test@${userB.firstName}`); await expect(pages.conversation().mentionSuggestions).toHaveCount(0); @@ -429,7 +429,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); // It should be possible to mention userB as he's part of the group await pages.conversation().messageInput.pressSequentially(`@${userB.firstName}`); @@ -449,7 +449,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [userB, userC]); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); // It should be possible to mention userB as he's part of the group await pages.conversation().messageInput.pressSequentially(`@`); @@ -468,7 +468,7 @@ test.describe('Mention', () => { const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp; await createGroup(pages, 'Test Group', [memberWithStrangeName]); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await pages.conversation().messageInput.pressSequentially('@Gunter'); await expect(pages.conversation().mentionSuggestions).toHaveCount(1); @@ -484,7 +484,7 @@ test.describe('Mention', () => { await createGroup(pages, 'Test Group', []); await test.step('Create guest link for group & join as guest user', async () => { - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); await pages.conversation().clickConversationTitle(); const link = await pages.conversationDetails().createGuestLink(); await createPage(withGuestUser(link, 'Guest User')); @@ -517,19 +517,19 @@ test.describe('Mention', () => { await otherUserPages.conversationList().pendingConnectionRequest.click(); await otherUserPages.connectRequest().connectButton.click(); - await expect(userAPages.conversationList().getConversationLocator(otherUser.fullName)).toBeAttached(); + await expect(userAPages.conversationList().getConversation(otherUser.fullName)).toBeAttached(); await test.step('UserA creates a group including userB and otherUser', async () => { await createGroup(userAPages, 'Test Group', [userB, otherUser]); }); await test.step("UserB mentions otherUser in the group although they're not connected", async () => { - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); await userBPages.conversation().sendMessageWithUserMention(otherUser.fullName); }); await test.step('OtherUser receives the message from userB including the mention', async () => { - await otherUserPages.conversationList().getConversationLocator('Test Group').open(); + await otherUserPages.conversationList().getConversation('Test Group').open(); const mentionInMessage = otherUserPages .conversation() .getMessage({sender: userB}) @@ -547,7 +547,7 @@ test.describe('Mention', () => { await createGroup(userAPages, 'Test Group', [userB, userC]); await test.step('UserA removes userB from the group', async () => { - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().conversationTitle.click(); await userAPages.conversationDetails().openParticipantDetails(userB.fullName); await userAPages.participantDetails().removeFromGroup(); diff --git a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts index ea4556450cd..172ea6a30c5 100644 --- a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts @@ -27,7 +27,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // Send message from A to B in 1on1 conversation - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello'); // Check the notifications B received to contain the message from A @@ -61,13 +61,13 @@ test.describe('Notifications', () => { // Archive conversation for user B const conversation = await userBPages .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}) + .getConversation(userA.fullName, {protocol: 'mls'}) .open(); const contextMenu = await conversation.openContextMenu(); await contextMenu.archiveButton.click(); // Send message from A to B in 1on1 conversation - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Hello'); // Open the archived conversation and ensure the message was received @@ -86,7 +86,7 @@ test.describe('Notifications', () => { async ({createPage}) => { const pages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages; - const conversation = await pages.conversationList().getConversationLocator(userB.fullName).open(); + const conversation = await pages.conversationList().getConversation(userB.fullName).open(); const contextMenu = await conversation.openContextMenu(); await contextMenu.notificationsButton.click(); await pages.conversationDetails().selectNotificationsLevel('Nothing'); @@ -100,7 +100,7 @@ test.describe('Notifications', () => { {tag: ['@TC-1438', '@regression']}, async ({createPage}) => { const pages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages; - const conversation = pages.conversationList().getConversationLocator(userB.fullName); + const conversation = pages.conversationList().getConversation(userB.fullName); await test.step('Mute conversation', async () => { const contextMenu = await conversation.openContextMenu(); @@ -139,7 +139,7 @@ test.describe('Notifications', () => { // Depending on the current test case mute either the group or the 1on1 await userBPages .conversationList() - .getConversationLocator(conversationType === 'group' ? 'Test Group' : userA.fullName) + .getConversation(conversationType === 'group' ? 'Test Group' : userA.fullName) .open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().notificationsButton).toContainText('Everything'); @@ -154,7 +154,7 @@ test.describe('Notifications', () => { // User A sends a message to the muted conversation await userAPages .conversationList() - .getConversationLocator(conversationType === 'group' ? 'Test Group' : userB.fullName) + .getConversation(conversationType === 'group' ? 'Test Group' : userB.fullName) .open(); await userAPages.conversation().sendMessage('Test Message'); await expect.poll(() => getUserBNotifications()).toHaveLength(0); @@ -174,14 +174,14 @@ test.describe('Notifications', () => { // User B mutes the conversation with User A const conversation = await userBPages .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}) + .getConversation(userA.fullName, {protocol: 'mls'}) .open(); const contextMenu = await conversation.openContextMenu(); await contextMenu.notificationsButton.click(); await userBPages.conversationDetails().selectNotificationsLevel('Nothing'); // User A initiates a call to User B - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); // Verify that User B sees a "Join" button on the muted conversation @@ -203,11 +203,11 @@ test.describe('Notifications', () => { // Open 1on1 conversation between users const userAConversation = await userAPages .conversationList() - .getConversationLocator(userB.fullName, {protocol: 'mls'}) + .getConversation(userB.fullName, {protocol: 'mls'}) .open(); const userBConversation = await userBPages .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}) + .getConversation(userA.fullName, {protocol: 'mls'}) .open(); // User B mutes the conversation with User A via recent view @@ -217,7 +217,7 @@ test.describe('Notifications', () => { // Create group and open it for user B so the message won't be read immediately await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); // Start intercepting notifications for User B const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); @@ -246,7 +246,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A sends an ephemeral message - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Ephemeral Message'); @@ -351,10 +351,10 @@ test.describe('Notifications', () => { for (const conversation of ['1on1', 'group'] as const) { await test.step(`User A opens the ${conversation} conversation`, async () => { if (conversation === '1on1') { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); } else { await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); } }); @@ -392,13 +392,13 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, 'Test Group', [userA]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); // Start intercepting notifications const {clickNotification} = await interceptNotifications(userBPage); // User A pings User B - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendPing(); await clickNotification({title: userA.fullName, body: 'Pinged'}); @@ -420,12 +420,12 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userBPages, 'Test Group', [userA]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); const {clickNotification} = await interceptNotifications(userBPage); // User A initiates a call to User B - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().clickCallButton(); // Verify user B receives the call @@ -451,13 +451,13 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // Start intercepting notifications const {clickNotification} = await interceptNotifications(userBPage); // User A sends message in group - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().sendMessage('Test Message'); await clickNotification({title: `${userA.fullName} in Test Group`, body: 'Test Message'}); @@ -477,7 +477,7 @@ test.describe('Notifications', () => { ]); const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const {getNotifications} = await interceptNotifications(userBPage); @@ -508,7 +508,7 @@ test.describe('Notifications', () => { ]); const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User A creates a group with User B const originalGroupName = 'Original Group Name'; @@ -519,7 +519,7 @@ test.describe('Notifications', () => { const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); // User A opens ConversationDetailsPage and changes the group name - await userAPages.conversationList().getConversationLocator(originalGroupName).open(); + await userAPages.conversationList().getConversation(originalGroupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName(newGroupName); @@ -581,13 +581,13 @@ test.describe('Notifications', () => { const groupName = 'Image Group'; await createGroup(userAPages, groupName, [userB]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // Start intercepting notifications for User B const {getNotifications} = await interceptNotifications(userBPage); // User A sends an image to the group conversation - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); await expect @@ -615,12 +615,12 @@ test.describe('Notifications', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const {getNotifications} = await interceptNotifications(userBPage); // User A changes the conversation timer to 10 seconds - await userAPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversationDetails().setSelfDeletingMessages('10 seconds'); diff --git a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts index 7b59cbe03c6..0b1d8a23aa3 100644 --- a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts @@ -28,7 +28,7 @@ async function openParticipantDetailsFromGroup( groupName: string, participantName: string, ) { - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().clickConversationInfoButton(); await pages.conversationDetails().openParticipantDetails(participantName); } @@ -63,7 +63,7 @@ test.describe('Participant Profile', () => { await acceptConnectionRequest(userCPages); await test.step('Go to any 1:1 conversation', async () => { - await userAPages.conversationList().getConversationLocator(userC.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userC.fullName, {protocol: 'mls'}).open(); }); await test.step('Open People popover', async () => { @@ -102,7 +102,7 @@ test.describe('Participant Profile', () => { await sendConnectionRequest(userAPage, userC); await acceptConnectionRequest(userCPages); - await expect(userAPages.conversationList().getConversationLocator(userC.fullName)).toBeAttached(); + await expect(userAPages.conversationList().getConversation(userC.fullName)).toBeAttached(); await test.step('User C is in group with User A and B. User C is not connected to user B', async () => { await createGroup(userAPages, groupName, [userB, userC]); @@ -144,7 +144,7 @@ test.describe('Participant Profile', () => { await sendConnectionRequest(userAPageManager, userC); await acceptConnectionRequest(userCPageManager.webapp.pages); - await expect(pages.conversationList().getConversationLocator(userC.fullName)).toBeAttached(); + await expect(pages.conversationList().getConversation(userC.fullName)).toBeAttached(); await createGroup(pages, groupName, [userB, userC]); @@ -155,7 +155,7 @@ test.describe('Participant Profile', () => { }); await test.step('User C opens people popover', async () => { - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().clickConversationTitle(); }); @@ -238,7 +238,7 @@ test.describe('Participant Profile', () => { await createGroup(adminPage, groupName, [userB]); - await adminPage.conversationList().getConversationLocator(groupName).open(); + await adminPage.conversationList().getConversation(groupName).open(); await adminPage.conversation().clickConversationInfoButton(); await adminPage.conversation().makeUserAdmin(userB.fullName); diff --git a/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts b/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts index 97856f3bdb0..73f42ae5f97 100644 --- a/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Ping/ping.spec.ts @@ -52,13 +52,13 @@ test.describe('Ping', () => { if (scenario.isGroup) { await createGroup(userAPages, conversationName, [userB]); - await userBPages.conversationList().getConversationLocator(conversationName).open(); + await userBPages.conversationList().getConversation(conversationName).open(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); } else { - await userBPages.conversationList().getConversationLocator(userA.fullName).open(); + await userBPages.conversationList().getConversation(userA.fullName).open(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); } await expect(userAPages.conversation().getPing()).toBeVisible(); @@ -72,12 +72,12 @@ test.describe('Ping', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userBPages.conversationList().getConversationLocator(userA.fullName).open(); + await userBPages.conversationList().getConversation(userA.fullName).open(); await userBPages.conversation().sendPing(); await expect(userBPages.conversation().pingButton).toBeDisabled(); await userBPages.conversation().sendPing(); - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await expect(userAPages.conversation().getPing()).toHaveCount(2); }); @@ -98,7 +98,7 @@ test.describe('Ping', () => { const conversationName = 'Test Group'; await createGroup(userAPages, conversationName, [userB, ...usersForBigGroup]); - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendPing(); await expect(userAModals.confirm().modal).toBeVisible(); diff --git a/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts b/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts index 9010ec7bcf3..5868a840237 100644 --- a/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Reactions/reactions.spec.ts @@ -92,10 +92,10 @@ test.describe('Reactions', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await c.sendFromUserB(userBPage); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); const messageFromUserB = userAPages.conversation().getMessage({sender: userB}); await expect(messageFromUserB).toBeVisible(); @@ -127,7 +127,7 @@ test.describe('Reactions', () => { }); }); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); const messageWithLink = userAPages.conversation().getMessage({sender: userB}); await userAPages.conversation().reactOnMessage(messageWithLink, 'heart'); @@ -160,8 +160,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); const messageUserB = userAPages.conversation().getMessage({sender: userB}); @@ -190,8 +190,8 @@ test.describe('Reactions', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message from User B'); const messageUserB = userAPages.conversation().getMessage({sender: userB}); @@ -217,8 +217,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message to react to'); const userBMessage = userBPages.conversation().getMessage({sender: userA}); @@ -241,8 +241,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Message to react to'); const userBMessage = userBPages.conversation().getMessage({sender: userA}); @@ -283,8 +283,8 @@ test.describe('Reactions', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userBPages.conversation().sendMessage('Message to react to'); const messageInUserA = userAPages.conversation().getMessage({sender: userB}); diff --git a/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts b/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts index 9860956fb98..5e147d9f97c 100644 --- a/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Reply/reply.spec.ts @@ -65,8 +65,8 @@ test.describe('Reply', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('Test'); const messageToReplyTo = userBPages.conversation().getMessage({content: 'Test'}); @@ -299,8 +299,8 @@ test.describe('Reply', () => { await createGroup(userAPages, 'Test Group', [userB]); await Promise.all([ - userAPages.conversationList().getConversationLocator('Test Group').open(), - userBPages.conversationList().getConversationLocator('Test Group').open(), + userAPages.conversationList().getConversation('Test Group').open(), + userBPages.conversationList().getConversation('Test Group').open(), ]); await userAPages.conversation().sendMessage('Message'); diff --git a/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts b/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts index 6f1678d53eb..6d77147175d 100644 --- a/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Search/search.spec.ts @@ -49,12 +49,12 @@ test.describe('Search', () => { const conversationName = 'Group conversation'; await createGroup(userAPages, conversationName, [userB]); - await userAPages.conversationList().getConversationLocator(conversationName).open(); + await userAPages.conversationList().getConversation(conversationName).open(); await userAPages.conversation().sendMessage(`@${userB.username} Group message with mention of User B`); await userAPages.conversationList().searchConversationsInput.fill(`@${userB.username}`); - await expect(userAPages.conversationList().getConversationLocator(conversationName)).toBeVisible(); - await expect(userAPages.conversationList().getConversationLocator(userB.fullName)).toBeVisible(); + await expect(userAPages.conversationList().getConversation(conversationName)).toBeVisible(); + await expect(userAPages.conversationList().getConversation(userB.fullName)).toBeVisible(); }, ); diff --git a/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts b/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts index 85196d73bc5..3930a931227 100644 --- a/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/SelfDeletingMessages/selfDeletingMessages.spec.ts @@ -41,8 +41,8 @@ test.describe('Self Deleting Messages', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Gone in 10s'); @@ -59,7 +59,7 @@ test.describe('Self Deleting Messages', () => { const userBPages = PageManager.from(userBPage).webapp.pages; await createGroup(userAPages, 'Test Group', [userB]); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Gone in 10s'); @@ -115,7 +115,7 @@ test.describe('Self Deleting Messages', () => { // Re-open page reusing the same context so the login is not happening on a new device page = await createPage(context, withLogin(userA)); pages = PageManager.from(page).webapp.pages; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); const selfDeletingMessage = pages.conversation().getMessage({sender: userA}); await expect(selfDeletingMessage).toBeVisible(); @@ -134,8 +134,8 @@ test.describe('Self Deleting Messages', () => { const [userAPages, userBPages] = [userAPage, userBPage].map(page => PageManager.from(page).webapp.pages); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().getConversationLocator('Test Group').open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); // User B should not read the message sent into the group immediately + await userAPages.conversationList().getConversation('Test Group').open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); // User B should not read the message sent into the group immediately await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Test Message'); @@ -143,7 +143,7 @@ test.describe('Self Deleting Messages', () => { await userBPage.waitForTimeout(10_000); // Wait 10s before user B opens the group chat - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); await expect(userBPages.conversation().getMessage({content: 'Test Message'})).toBeVisible(); }, ); @@ -159,8 +159,8 @@ test.describe('Self Deleting Messages', () => { ]); await createGroup(userAPages, 'Test Group', [userB]); - await userAPages.conversationList().getConversationLocator('Test Group').open(); - await userBPages.conversationList().getConversationLocator('Test Group').open(); + await userAPages.conversationList().getConversation('Test Group').open(); + await userBPages.conversationList().getConversation('Test Group').open(); await userAPages.conversation().toggleGroupInformation(); await userAPages.conversationDetails().setSelfDeletingMessages('10 seconds'); @@ -220,8 +220,8 @@ test.describe('Self Deleting Messages', () => { PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().enableSelfDeletingMessages(); await userAPages.conversation().sendMessage('Test'); diff --git a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts index 126845515da..e6267b3a1a3 100644 --- a/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/SendingAssets/sendingAssets.spec.ts @@ -69,7 +69,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages} = PageManager.from(userAPage).webapp; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await shareAssetHelper(getAudioFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add file'})); const message = pages.conversation().getMessage({sender: userA}); @@ -85,7 +85,7 @@ test.describe('Sending Assets', () => { const buffer = await fs.readFile(getTextFilePath()); await test.step('Go to any 1:1 conversation', async () => { - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); }); await test.step('User B can drag & drop a file into a conversation', async () => { @@ -140,7 +140,7 @@ test.describe('Sending Assets', () => { const pastedFileControls = userAPage.getByTestId('pasted-file-controls'); await test.step('Go to any 1:1 conversation', async () => { - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); }); await test.step('User A copy paste image into the conversation', async () => { @@ -162,7 +162,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const userAPages = PageManager.from(userAPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName).open(); + await userAPages.conversationList().getConversation(userB.fullName).open(); await userAPages.conversation().sendMessage('Message to copy'); const message = userAPages.conversation().getMessage({sender: userA}); @@ -198,10 +198,10 @@ test.describe('Sending Assets', () => { await test.step('User A opens the conversation', async () => { if (conversationType === '1on1') { - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); } else { await createGroup(pages, 'Test Group', [userB]); - await pages.conversationList().getConversationLocator('Test Group').open(); + await pages.conversationList().getConversation('Test Group').open(); } }); @@ -237,7 +237,7 @@ test.describe('Sending Assets', () => { // Verify uploading a file exceeding the limit isn't possible for both, 1on1 and group conversations for (const conversation of [userB.fullName, 'Test Group']) { await test.step(`Try sending a too big file to ${conversation}`, async () => { - await pages.conversationList().getConversationLocator(conversation).open(); + await pages.conversationList().getConversation(conversation).open(); const [fileChooser] = await Promise.all([ page.waitForEvent('filechooser'), page.getByRole('button', {name: 'Add file'}).click(), @@ -270,8 +270,8 @@ test.describe('Sending Assets', () => { const pages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await pages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); const tempFilePath = path.join(tmpdir(), '25MB-testfile.tmp'); try { @@ -301,8 +301,8 @@ test.describe('Sending Assets', () => { const userAPages = PageManager.from(userAPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); - await userBPages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); + await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await shareAssetHelper(getTextFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add file'})); @@ -327,7 +327,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages} = PageManager.from(userAPage).webapp; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await pages.conversation().enableSelfDeletingMessages(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); @@ -350,7 +350,7 @@ test.describe('Sending Assets', () => { const userAPage = await createPage(withLogin(userA), withConnectedUser(userB)); const {pages, modals} = PageManager.from(userAPage).webapp; - await pages.conversationList().getConversationLocator(userB.fullName).open(); + await pages.conversationList().getConversation(userB.fullName).open(); await shareAssetHelper(getImageFilePath(), userAPage, userAPage.getByRole('button', {name: 'Add picture'})); await pages.conversation().clickImage(userA); diff --git a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts index 8ce4da588b5..0b76d3fee36 100644 --- a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts @@ -157,7 +157,7 @@ test.describe('Status', () => { name: 'Rename group', sendAction: async ({userAPageManager}) => { const userAPages = userAPageManager.pages; - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().changeConversationName('New Group Name'); }, @@ -188,7 +188,7 @@ test.describe('Status', () => { name: 'Add member to group', sendAction: async ({userAPageManager}) => { const userAPages = userAPageManager.pages; - await userAPages.conversationList().getConversationLocator('Test Group 2').open(); + await userAPages.conversationList().getConversation('Test Group 2').open(); await userAPages.conversation().clickConversationInfoButton(); await userAPages.conversationDetails().clickAddPeopleButton(); await userAPages.conversationDetails().addUsersToConversation([userC.fullName]); @@ -249,7 +249,7 @@ test.describe('Status', () => { // User B sends initial messages for replies for (const {targetForUserB, options} of scenarios) { - await userBPages.conversationList().getConversationLocator(targetForUserB, options).open(); + await userBPages.conversationList().getConversation(targetForUserB, options).open(); await userBPages.conversation().sendMessage('Message to reply'); } @@ -261,7 +261,7 @@ test.describe('Status', () => { await updateUserStatus(userBPageManager, userB.fullName, config.status); // User B opens the third conversation to receive notifications - await userBPages.conversationList().getConversationLocator(userC.fullName).open(); + await userBPages.conversationList().getConversation(userC.fullName).open(); const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); @@ -270,7 +270,7 @@ test.describe('Status', () => { const filteredCases = commonTestCases.filter( tc => config.status === UserStatus.Away || !['Mention', 'Reply'].includes(tc.name), ); - await userAPages.conversationList().getConversationLocator(targetForUserA, options).open(); + await userAPages.conversationList().getConversation(targetForUserA, options).open(); for (const testCase of filteredCases) { await test.step(`Action: ${testCase.name} message in ${type}`, async () => { @@ -279,7 +279,7 @@ test.describe('Status', () => { } await components.conversationSidebar().clickAllConversationsButton(); - await expect(userBPages.conversationList().getConversationLocator(targetForUserB, options)).toContainText( + await expect(userBPages.conversationList().getConversation(targetForUserB, options)).toContainText( /\d+ ping, \d+ messages/, ); await expect.poll(() => getUserBNotifications()).toHaveLength(0); @@ -288,7 +288,7 @@ test.describe('Status', () => { // System Test Cases await test.step('User B should not receive any system notification', async () => { - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); for (const systemTestCase of systemTestCases) { await systemTestCase.sendAction({userAPageManager}); } @@ -309,7 +309,7 @@ test.describe('Status', () => { }, ]; - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); for (const testCase of specialTestCases) { await test.step(`Action: ${testCase.name} message`, async () => { await testCase.sendAction({pageA: userAPage, api}); @@ -319,7 +319,7 @@ test.describe('Status', () => { }); } else { await test.step('User B should not receive calls notification (Away status)', async () => { - await userAPages.conversationList().getConversationLocator(userB.fullName, {protocol: 'mls'}).open(); + await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().startCall(); await expect(userBPages.calling().callCell).toBeVisible(); await expect.poll(() => getUserBNotifications()).toHaveLength(0); @@ -368,10 +368,10 @@ test.describe('Status', () => { await createGroup(userAPages, groupName, [userB, userC]); // User B sends initial message and sets status to available - await pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}).open(); + await pages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await pages.conversation().sendMessage('Message to reply'); - await pages.conversationList().getConversationLocator(groupName).open(); + await pages.conversationList().getConversation(groupName).open(); await pages.conversation().sendMessage('Message to reply'); await updateUserStatus(userBPageManager, userB.fullName, UserStatus.Available); @@ -394,7 +394,7 @@ test.describe('Status', () => { for (const {type, target, options} of scenarios) { await test.step(`User A opens the ${type} conversation`, async () => { - await userAPages.conversationList().getConversationLocator(target, options).open(); + await userAPages.conversationList().getConversation(target, options).open(); }); await test.step(`User B should receive all conversation notifications`, async () => { @@ -409,7 +409,7 @@ test.describe('Status', () => { } await test.step(`User B should receive system notifications only for group creation and renaming`, async () => { - await userAPages.conversationList().getConversationLocator(groupName).open(); + await userAPages.conversationList().getConversation(groupName).open(); for (const systemTestCase of systemTestCases) { await test.step(`Action: ${systemTestCase.name}`, async () => { await systemTestCase.sendAction({userAPageManager}); @@ -434,10 +434,7 @@ test.describe('Status', () => { const userBPages = userBPageManager.webapp.pages; // User B verify no status is set in conversation list - const conversation = await userBPages - .conversationList() - .getConversationLocator(userA.fullName, {protocol: 'mls'}) - .open(); + const conversation = await userBPages.conversationList().getConversation(userA.fullName, {protocol: 'mls'}).open(); await expect(conversation.statusAvailabilityIcon).not.toBeVisible(); // User A opens preferences by clicking the gear button @@ -482,7 +479,7 @@ test.describe('Status', () => { await expect(components.conversationSidebar().personalStatusIcon).toBeVisible(); // User B should see the BUSY status in the searchable group participant list - await userBPages.conversationList().getConversationLocator(groupName).open(); + await userBPages.conversationList().getConversation(groupName).open(); await userBPages.conversation().clickConversationInfoButton(); await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toBeVisible(); await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toHaveAttribute( diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index f580c44ca04..69e9e9e581b 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -59,7 +59,7 @@ export const sendTextMessageToConversation = async ( message: string, ) => { const {pages} = pageManager.webapp; - await pages.conversationList().getConversationLocator(conversation).open(); + await pages.conversationList().getConversation(conversation).open(); await pages.conversation().sendMessage(message); }; From 1f65311c323e6549a8fa8e99d80289102a517df2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:03:24 +0000 Subject: [PATCH 25/49] chore(deps): update dependency @swc/core to v1.15.32 --- libraries/api-client/package.json | 2 +- libraries/core/package.json | 2 +- yarn.lock | 108 +++++++++++++++--------------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/libraries/api-client/package.json b/libraries/api-client/package.json index 767ab4fe2fc..8acef77489d 100644 --- a/libraries/api-client/package.json +++ b/libraries/api-client/package.json @@ -33,7 +33,7 @@ "zod": "3.25.76" }, "devDependencies": { - "@swc/core": "1.15.30", + "@swc/core": "1.15.32", "@swc/jest": "0.2.39", "@types/jest": "30.0.0", "@types/pako": "2.0.4", diff --git a/libraries/core/package.json b/libraries/core/package.json index 7da77f79a3c..060e9887390 100644 --- a/libraries/core/package.json +++ b/libraries/core/package.json @@ -37,7 +37,7 @@ "lodash": "4.18.1" }, "devDependencies": { - "@swc/core": "1.15.30", + "@swc/core": "1.15.32", "@swc/jest": "0.2.39", "@types/lodash": "4.17.24", "commander": "14.0.3", diff --git a/yarn.lock b/yarn.lock index 0051570afd2..29d1e3ac782 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7358,106 +7358,106 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-darwin-arm64@npm:1.15.30" +"@swc/core-darwin-arm64@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-darwin-arm64@npm:1.15.32" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-darwin-x64@npm:1.15.30" +"@swc/core-darwin-x64@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-darwin-x64@npm:1.15.32" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.30" +"@swc/core-linux-arm-gnueabihf@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.32" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-arm64-gnu@npm:1.15.30" +"@swc/core-linux-arm64-gnu@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.32" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-arm64-musl@npm:1.15.30" +"@swc/core-linux-arm64-musl@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.32" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-ppc64-gnu@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-ppc64-gnu@npm:1.15.30" +"@swc/core-linux-ppc64-gnu@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-ppc64-gnu@npm:1.15.32" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-s390x-gnu@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-s390x-gnu@npm:1.15.30" +"@swc/core-linux-s390x-gnu@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-s390x-gnu@npm:1.15.32" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-x64-gnu@npm:1.15.30" +"@swc/core-linux-x64-gnu@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.32" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-linux-x64-musl@npm:1.15.30" +"@swc/core-linux-x64-musl@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-linux-x64-musl@npm:1.15.32" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-win32-arm64-msvc@npm:1.15.30" +"@swc/core-win32-arm64-msvc@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.32" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-win32-ia32-msvc@npm:1.15.30" +"@swc/core-win32-ia32-msvc@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.32" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core-win32-x64-msvc@npm:1.15.30" +"@swc/core-win32-x64-msvc@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.32" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:1.15.30": - version: 1.15.30 - resolution: "@swc/core@npm:1.15.30" +"@swc/core@npm:1.15.32": + version: 1.15.32 + resolution: "@swc/core@npm:1.15.32" dependencies: - "@swc/core-darwin-arm64": "npm:1.15.30" - "@swc/core-darwin-x64": "npm:1.15.30" - "@swc/core-linux-arm-gnueabihf": "npm:1.15.30" - "@swc/core-linux-arm64-gnu": "npm:1.15.30" - "@swc/core-linux-arm64-musl": "npm:1.15.30" - "@swc/core-linux-ppc64-gnu": "npm:1.15.30" - "@swc/core-linux-s390x-gnu": "npm:1.15.30" - "@swc/core-linux-x64-gnu": "npm:1.15.30" - "@swc/core-linux-x64-musl": "npm:1.15.30" - "@swc/core-win32-arm64-msvc": "npm:1.15.30" - "@swc/core-win32-ia32-msvc": "npm:1.15.30" - "@swc/core-win32-x64-msvc": "npm:1.15.30" + "@swc/core-darwin-arm64": "npm:1.15.32" + "@swc/core-darwin-x64": "npm:1.15.32" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.32" + "@swc/core-linux-arm64-gnu": "npm:1.15.32" + "@swc/core-linux-arm64-musl": "npm:1.15.32" + "@swc/core-linux-ppc64-gnu": "npm:1.15.32" + "@swc/core-linux-s390x-gnu": "npm:1.15.32" + "@swc/core-linux-x64-gnu": "npm:1.15.32" + "@swc/core-linux-x64-musl": "npm:1.15.32" + "@swc/core-win32-arm64-msvc": "npm:1.15.32" + "@swc/core-win32-ia32-msvc": "npm:1.15.32" + "@swc/core-win32-x64-msvc": "npm:1.15.32" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.26" peerDependencies: @@ -7490,7 +7490,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/4b021a9203dcc2896c71dda267a9668c7b7edf8927292df7025f527374f4d05bb6fb29cde8472368aa8b35ae56ead1dc102eac1f1f2c7bb4bffcb184029146ea + checksum: 10/917e1448ca7c37655ce693f7c75b8393bb7fd6cc202ab1a8b9dd5e2344ee4274b2a3dffad514eee2c5b7a37f2b4b08dd8478fea30b7dad375a03deedc2387d03 languageName: node linkType: hard @@ -9348,7 +9348,7 @@ __metadata: dependencies: "@aws-sdk/client-s3": "npm:3.1037.0" "@aws-sdk/lib-storage": "npm:3.1037.0" - "@swc/core": "npm:1.15.30" + "@swc/core": "npm:1.15.32" "@swc/jest": "npm:0.2.39" "@types/jest": "npm:30.0.0" "@types/pako": "npm:2.0.4" @@ -9451,7 +9451,7 @@ __metadata: version: 0.0.0-use.local resolution: "@wireapp/core@workspace:libraries/core" dependencies: - "@swc/core": "npm:1.15.30" + "@swc/core": "npm:1.15.32" "@swc/jest": "npm:0.2.39" "@types/lodash": "npm:4.17.24" "@wireapp/api-client": "npm:27.96.0" From 7dc6a53882fdb7509c7335048edddbe6d7aacbb6 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:07:30 +0200 Subject: [PATCH 26/49] test: improve TC-392 by waiting for all messages to arrive before deleting [WPB-22420] (#21188) If the message from A arrived late it could cause the deletion to fail since it tries to hover the message but the message would move in between. --- .../specs/InConversationSearch/inConversationSearch.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts index 46b3779bb6c..b3a0d7a3d52 100644 --- a/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/InConversationSearch/inConversationSearch.spec.ts @@ -331,6 +331,7 @@ test.describe('In Conversation Search', () => { await userAPages.conversationList().getConversation(userB.fullName, {protocol: 'mls'}).open(); await userAPages.conversation().sendMessage('User A message: Guava'); + await expect(userBPages.conversation().messages).toHaveCount(2); const messageUserB = userBPages.conversation().getMessage({sender: userB}); await userBPages.conversation().deleteMessage(messageUserB, 'Everyone'); From 68920f5a484a22a798711df0552eca8d9b1263b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:59:07 +0000 Subject: [PATCH 27/49] chore(deps): update dependency stylelint to v17.9.1 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bdfcd5e079a..88f97a03499 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "prettier": "3.8.3", "rimraf": "6.1.3", "simple-git": "3.36.0", - "stylelint": "17.9.0", + "stylelint": "17.9.1", "stylelint-config-idiomatic-order": "10.0.0", "ts-node": "10.9.2", "tsc-watch": "7.2.0", diff --git a/yarn.lock b/yarn.lock index 29d1e3ac782..001ee0fabfb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25299,9 +25299,9 @@ __metadata: languageName: node linkType: hard -"stylelint@npm:17.9.0": - version: 17.9.0 - resolution: "stylelint@npm:17.9.0" +"stylelint@npm:17.9.1": + version: 17.9.1 + resolution: "stylelint@npm:17.9.1" dependencies: "@csstools/css-calc": "npm:^3.2.0" "@csstools/css-parser-algorithms": "npm:^4.0.0" @@ -25341,7 +25341,7 @@ __metadata: write-file-atomic: "npm:^7.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10/c581060b92345195b097cb798918b02a006f235602fe3362e1a38fe55bc7ca9c567bca6fe9312409769b53127dca59fe4a6a148cede852ed83201ef2099f9a0c + checksum: 10/94200a4a8830a5b136df42e7cb060c0021ff73b40c86bed25c9e0b30e53c1c26a22850971ac90d2bd598bac594740e6cf2cda47dcc61cde601fe8e4d693e1d79 languageName: node linkType: hard @@ -27518,7 +27518,7 @@ __metadata: prettier: "npm:3.8.3" rimraf: "npm:6.1.3" simple-git: "npm:3.36.0" - stylelint: "npm:17.9.0" + stylelint: "npm:17.9.1" stylelint-config-idiomatic-order: "npm:10.0.0" ts-node: "npm:10.9.2" tsc-watch: "npm:7.2.0" From 3198568ddd4fbb48ad863e23fddf8a3b543b3f22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 06:20:00 +0200 Subject: [PATCH 28/49] chore(deps): update typescript-eslint to v8.59.1 (#21194) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 132 +++++++++++++++++++++++++-------------------------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index bdfcd5e079a..f6903d2553c 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "@types/jsdom": "21.1.7", "@types/node": "24.12.2", "@types/tough-cookie": "4.0.5", - "@typescript-eslint/eslint-plugin": "8.59.0", - "@typescript-eslint/parser": "8.59.0", + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", "@wireapp/copy-config": "2.3.13", "@wireapp/eslint-config": "3.1.9", "@wireapp/prettier-config": "0.6.11", diff --git a/yarn.lock b/yarn.lock index 29d1e3ac782..91843fadff5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8550,23 +8550,23 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.59.0" +"@typescript-eslint/eslint-plugin@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.59.1" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.59.0" - "@typescript-eslint/type-utils": "npm:8.59.0" - "@typescript-eslint/utils": "npm:8.59.0" - "@typescript-eslint/visitor-keys": "npm:8.59.0" + "@typescript-eslint/scope-manager": "npm:8.59.1" + "@typescript-eslint/type-utils": "npm:8.59.1" + "@typescript-eslint/utils": "npm:8.59.1" + "@typescript-eslint/visitor-keys": "npm:8.59.1" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.5.0" peerDependencies: - "@typescript-eslint/parser": ^8.59.0 + "@typescript-eslint/parser": ^8.59.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/fcf2c85cb37d61854d2c03fa11c66ad58d99f4eee731dd09663f20f0395e642b12edeab2a6c368ac1806505b2071a01de01bc30b9011fa309299836e868a293a + checksum: 10/c736ee32211a3751e31151b51dacc8cfa5bf18e086f2a87aba7ee325f7e2fa96d8b9febdbaf4dfa70d14954312b7b9740fbe5d5886b3f8561c4a94a9c7ff7688 languageName: node linkType: hard @@ -8588,19 +8588,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/parser@npm:8.59.0" +"@typescript-eslint/parser@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/parser@npm:8.59.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.59.0" - "@typescript-eslint/types": "npm:8.59.0" - "@typescript-eslint/typescript-estree": "npm:8.59.0" - "@typescript-eslint/visitor-keys": "npm:8.59.0" + "@typescript-eslint/scope-manager": "npm:8.59.1" + "@typescript-eslint/types": "npm:8.59.1" + "@typescript-eslint/typescript-estree": "npm:8.59.1" + "@typescript-eslint/visitor-keys": "npm:8.59.1" debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/b8990e1b67e6f55aa4884807e6c3b6bd08c58f96ea4f03f19e7aba3fc1b16f040fe58378490de9bd831c804eb48e633e30e5baf291b8e8dd53960424e5751391 + checksum: 10/b014b485e5ec9c7430a87117271836b86fd80083fe6b1d216167313518f26222f45c0ee3f4cbc0616dbd6335cbde50336d8953ca5ffefecc55b2d896ac7645f9 languageName: node linkType: hard @@ -8630,16 +8630,16 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/project-service@npm:8.59.0" +"@typescript-eslint/project-service@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/project-service@npm:8.59.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.59.0" - "@typescript-eslint/types": "npm:^8.59.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.59.1" + "@typescript-eslint/types": "npm:^8.59.1" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/b842f1e0623c3a679d21d76c7ca38698787681d40f6a9ce93c8983120fb6795a2395907d530e4f8d89b4ac5bc65e71bbfdf2d8060f210c8487cffdae40baea74 + checksum: 10/dd98f49a407cb21999d31ec527a0f8c2c34422dde9fdb21210d66c3cc3d498d9d3678d95c99d76450af68ce3392692902d9ba044718d6c99122655df7afdc0a7 languageName: node linkType: hard @@ -8683,13 +8683,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/scope-manager@npm:8.59.0" +"@typescript-eslint/scope-manager@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/scope-manager@npm:8.59.1" dependencies: - "@typescript-eslint/types": "npm:8.59.0" - "@typescript-eslint/visitor-keys": "npm:8.59.0" - checksum: 10/8bb1182559e7676120ba12bdac11edea9fb414bd33d379a1902b035b8b4b68d23ad239d845bfe6943b5da13ecd938ea1482c73e8c6ddb4d7e3e0f8e111467e28 + "@typescript-eslint/types": "npm:8.59.1" + "@typescript-eslint/visitor-keys": "npm:8.59.1" + checksum: 10/50c941d1af470d3e67a9bd2247c541a676ae6bb2931440a44458682d61382ba1194ce29d0388dd1e538c5a35d7a542febd9519d8170abe758692d1b6cd196eab languageName: node linkType: hard @@ -8711,12 +8711,12 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.59.0, @typescript-eslint/tsconfig-utils@npm:^8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.0" +"@typescript-eslint/tsconfig-utils@npm:8.59.1, @typescript-eslint/tsconfig-utils@npm:^8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.1" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/9c094c199be4803d696dbf7cb5cdb76741876e412bf97ddde0133a75e11bc47345354b3bb188a0ff101b7ce2c582187e758696ab89c1981892a43162f36d0af1 + checksum: 10/9e3351bb182bb02f6f140759472f08ce334c7c96f4ebfeec8e9e404fe60b8fe1865e1a6d1b50526f83f41e7224301485e46459df6c3675923f3b657177415cd7 languageName: node linkType: hard @@ -8737,19 +8737,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/type-utils@npm:8.59.0" +"@typescript-eslint/type-utils@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/type-utils@npm:8.59.1" dependencies: - "@typescript-eslint/types": "npm:8.59.0" - "@typescript-eslint/typescript-estree": "npm:8.59.0" - "@typescript-eslint/utils": "npm:8.59.0" + "@typescript-eslint/types": "npm:8.59.1" + "@typescript-eslint/typescript-estree": "npm:8.59.1" + "@typescript-eslint/utils": "npm:8.59.1" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.5.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/9c2d34c10676d5726f93b975136295971ac7d2a53f74bfba51ae71deefaa36292adda79d016782196b729429143634b7f90224c27dcdb3a884b9771128be7490 + checksum: 10/8a8a71656f8fab446024e55b24f6f6c4b3ee4d4cdcb593ff68ec0ca10530fcb4d451628c03898c929e91445a999cbe980c0cfaec1b53a7c5ddc8ac899ad665fa languageName: node linkType: hard @@ -8781,10 +8781,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.59.0, @typescript-eslint/types@npm:^8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/types@npm:8.59.0" - checksum: 10/51a773339c58a350d0ddaecba46ba735696f11829cab1f9b3d5d58a4bbd498693296ae742e3959d32f3bb29676c8e6bd120b970379d749a5a9b419393696930d +"@typescript-eslint/types@npm:8.59.1, @typescript-eslint/types@npm:^8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/types@npm:8.59.1" + checksum: 10/4d324a01c2314d8e196b43b9dc5fe9a4d82c1b65f4915cd2f965879c5565d4453603b6f7b6bcdc436fb629135f07ad0f9d274e4697b02ce8bc1c0310916f7ace languageName: node linkType: hard @@ -8870,14 +8870,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.59.0" +"@typescript-eslint/typescript-estree@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.59.1" dependencies: - "@typescript-eslint/project-service": "npm:8.59.0" - "@typescript-eslint/tsconfig-utils": "npm:8.59.0" - "@typescript-eslint/types": "npm:8.59.0" - "@typescript-eslint/visitor-keys": "npm:8.59.0" + "@typescript-eslint/project-service": "npm:8.59.1" + "@typescript-eslint/tsconfig-utils": "npm:8.59.1" + "@typescript-eslint/types": "npm:8.59.1" + "@typescript-eslint/visitor-keys": "npm:8.59.1" debug: "npm:^4.4.3" minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" @@ -8885,7 +8885,7 @@ __metadata: ts-api-utils: "npm:^2.5.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10/48eba6a117a36c4bf569aa1a728463619b131a45a6891cc0a5d2454828d9d3d07a499e9906de0df31de57761ce1d13aebb635a059782f3cc16563e3e63a29713 + checksum: 10/8ede99640ac8b08ac73905bbc66dd06b2c4dc211240a4a9cb532b0fcf5c36ec9e7639ed7e1c17f86a948499279ff93e9dbcdf9170661d9f8347fcb53e8266772 languageName: node linkType: hard @@ -8903,18 +8903,18 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/utils@npm:8.59.0" +"@typescript-eslint/utils@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/utils@npm:8.59.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.59.0" - "@typescript-eslint/types": "npm:8.59.0" - "@typescript-eslint/typescript-estree": "npm:8.59.0" + "@typescript-eslint/scope-manager": "npm:8.59.1" + "@typescript-eslint/types": "npm:8.59.1" + "@typescript-eslint/typescript-estree": "npm:8.59.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10/70547510f16459ca29e207584676f7c15626b5f7e2562643144fe037a1a9c4ca7116be99e67b9045f0de60db0022affb58c34c553a5370276ff8f542f7b05732 + checksum: 10/26ae39a574e56d92b6fc406113e797c354fce8b377721cc5dd50579a0e9f8c3efe23c7693826ce2c16be96490520dd6ce7e145c4c39c22d8d00f2614791603ba languageName: node linkType: hard @@ -9006,13 +9006,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.59.0": - version: 8.59.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.59.0" +"@typescript-eslint/visitor-keys@npm:8.59.1": + version: 8.59.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.59.1" dependencies: - "@typescript-eslint/types": "npm:8.59.0" + "@typescript-eslint/types": "npm:8.59.1" eslint-visitor-keys: "npm:^5.0.0" - checksum: 10/b81753b9ddddeb3564e44d1199ba5546028731c7b5b3270938525f1f2b549d1df5fa8f203d9b3eacc120fa6b5af314cb1fb69d3a12d1dcce18a52a0fe316628d + checksum: 10/5343f3424cafdcaf2550fade29eca6b86ad3f6ac953aef6ba1dccd39789a1c38520634fbbc0814419d9227f508789053c1c9f59c2841d72e56431c3fdd93ac65 languageName: node linkType: hard @@ -27475,8 +27475,8 @@ __metadata: "@types/jsdom": "npm:21.1.7" "@types/node": "npm:24.12.2" "@types/tough-cookie": "npm:4.0.5" - "@typescript-eslint/eslint-plugin": "npm:8.59.0" - "@typescript-eslint/parser": "npm:8.59.0" + "@typescript-eslint/eslint-plugin": "npm:8.59.1" + "@typescript-eslint/parser": "npm:8.59.1" "@wireapp/commons": "npm:5.4.13" "@wireapp/copy-config": "npm:2.3.13" "@wireapp/eslint-config": "npm:3.1.9" From 3a9eb34fb076e63fd9367d0e16909d920a8a1a40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 07:00:02 +0200 Subject: [PATCH 29/49] chore(deps): update dependency @babel/preset-env to v7.29.3 (#21196) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/webapp/package.json | 2 +- yarn.lock | 33 +++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 40a69a57938..65c9fba90b9 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -101,7 +101,7 @@ "devDependencies": { "@babel/core": "7.29.0", "@babel/plugin-proposal-decorators": "7.29.0", - "@babel/preset-env": "7.29.2", + "@babel/preset-env": "7.29.3", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", "@emotion/eslint-plugin": "11.12.0", diff --git a/yarn.lock b/yarn.lock index 91843fadff5..23252a6770f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -777,10 +777,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/compat-data@npm:7.29.0" - checksum: 10/7f21beedb930ed8fbf7eabafc60e6e6521c1d905646bf1317a61b2163339157fe797efeb85962bf55136e166b01fd1a6b526a15974b92a8b877d564dcb6c9580 +"@babel/compat-data@npm:^7.29.3": + version: 7.29.3 + resolution: "@babel/compat-data@npm:7.29.3" + checksum: 10/3c29661756a7c1cbc5248a7bdc657c0cb49f350e3157040c20486759f1f50a08a0b385fd7d813df50b96cd6fad5896d30ba6abab7602641bd1410ed346c1812f languageName: node linkType: hard @@ -1138,6 +1138,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@npm:^7.29.3": + version: 7.29.3 + resolution: "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@npm:7.29.3" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/fd13198afc9b72c6a4e4868f1592fc8010f390e7601148a71d2d6111664c0242d6d5ff27d8eb77ca4c35ef47f8416daf5dbc8d46a498ac706d69c6b3a0988cd7 + languageName: node + linkType: hard + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.27.1" @@ -2162,17 +2174,18 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.29.2": - version: 7.29.2 - resolution: "@babel/preset-env@npm:7.29.2" +"@babel/preset-env@npm:7.29.3": + version: 7.29.3 + resolution: "@babel/preset-env@npm:7.29.3" dependencies: - "@babel/compat-data": "npm:^7.29.0" + "@babel/compat-data": "npm:^7.29.3" "@babel/helper-compilation-targets": "npm:^7.28.6" "@babel/helper-plugin-utils": "npm:^7.28.6" "@babel/helper-validator-option": "npm:^7.27.1" "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "npm:^7.29.3" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.28.6" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" @@ -2238,7 +2251,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/25a2dd82483d0f5bc781a939cebf502b80415d057806c87073f00f9a943c440b9862a265ca445ea1cba1fa79ee6361d05485465cdfc7797a0ec6d6493cf5d95b + checksum: 10/723676e883ac22bdf24000aad81a2f0a86e9b17c520423df4d54d34b6e183c8c2609019d745bcbb453bd11d9273aee237e641d2cdc27744ef6a494df96f18fb8 languageName: node linkType: hard @@ -9708,7 +9721,7 @@ __metadata: dependencies: "@babel/core": "npm:7.29.0" "@babel/plugin-proposal-decorators": "npm:7.29.0" - "@babel/preset-env": "npm:7.29.2" + "@babel/preset-env": "npm:7.29.3" "@babel/preset-react": "npm:7.28.5" "@babel/preset-typescript": "npm:7.28.5" "@datadog/browser-logs": "npm:5.35.1" From da47945caa08a586ade51af022b0c1227a4fe5f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 07:00:28 +0200 Subject: [PATCH 30/49] fix(deps): update dependency @mediapipe/tasks-vision to v0.10.35 (#21195) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 65c9fba90b9..d2cc4c24b92 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -31,7 +31,7 @@ "@lexical/markdown": "0.27.2", "@lexical/react": "0.27.2", "@lexical/rich-text": "0.27.2", - "@mediapipe/tasks-vision": "0.10.34", + "@mediapipe/tasks-vision": "0.10.35", "@sindresorhus/is": "4.6.0", "@tanstack/react-table": "8.21.3", "@tanstack/react-virtual": "3.13.24", diff --git a/yarn.lock b/yarn.lock index 23252a6770f..0655c4aef0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5259,10 +5259,10 @@ __metadata: languageName: node linkType: hard -"@mediapipe/tasks-vision@npm:0.10.34": - version: 0.10.34 - resolution: "@mediapipe/tasks-vision@npm:0.10.34" - checksum: 10/d085377a4d60ecf30c9f27dc1ba8d21ffafdd320d20a4ce0fc82e79a57c69a8c600d3478c8348b089a7393bf5c095d02745931c37663a61939a0985065c06261 +"@mediapipe/tasks-vision@npm:0.10.35": + version: 0.10.35 + resolution: "@mediapipe/tasks-vision@npm:0.10.35" + checksum: 10/951afffdbf855dfdf04703f646f781f967cc3543221e018ea594a4a9267877d66c8b64738fa1149805811cdeaabfdcc98631fc09223a7a7090708ac61ff167ba languageName: node linkType: hard @@ -9736,7 +9736,7 @@ __metadata: "@lexical/markdown": "npm:0.27.2" "@lexical/react": "npm:0.27.2" "@lexical/rich-text": "npm:0.27.2" - "@mediapipe/tasks-vision": "npm:0.10.34" + "@mediapipe/tasks-vision": "npm:0.10.35" "@nx/webpack": "npm:22.7.0" "@playwright/test": "npm:1.59.1" "@roamhq/wrtc": "npm:0.10.0" From 592fa000f290aac26bc3c14c70bb1aa519347121 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 05:16:29 +0000 Subject: [PATCH 31/49] chore(deps): update dependency @nx/jest to v22.7.1 --- package.json | 2 +- yarn.lock | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 328 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f6903d2553c..edd37c26bf9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@enormora/objectory": "0.0.7", "@faker-js/faker": "9.9.0", "@ls-lint/ls-lint": "2.3.1", - "@nx/jest": "22.7.0", + "@nx/jest": "22.7.1", "@nx/node": "22.7.0", "@nx/workspace": "22.7.0", "@stylistic/eslint-plugin": "5.10.0", diff --git a/yarn.lock b/yarn.lock index 0655c4aef0b..901e7b755c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5362,6 +5362,23 @@ __metadata: languageName: node linkType: hard +"@nx/devkit@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/devkit@npm:22.7.1" + dependencies: + "@zkochan/js-yaml": "npm:0.0.7" + ejs: "npm:5.0.1" + enquirer: "npm:~2.3.6" + minimatch: "npm:10.2.4" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + peerDependencies: + nx: ">= 21 <= 23 || ^22.0.0-0" + checksum: 10/2682029d84fb622af104d3c1e3af0dd7bbaf4115dc30e5424c9d775271a01be2721abe635a7e46b5a117c18032971fff5ffc390ed37dcb15347cc2c99d17cfe5 + languageName: node + linkType: hard + "@nx/docker@npm:22.7.0": version: 22.7.0 resolution: "@nx/docker@npm:22.7.0" @@ -5418,6 +5435,29 @@ __metadata: languageName: node linkType: hard +"@nx/jest@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/jest@npm:22.7.1" + dependencies: + "@jest/reporters": "npm:^30.0.2" + "@jest/test-result": "npm:^30.0.2" + "@nx/devkit": "npm:22.7.1" + "@nx/js": "npm:22.7.1" + "@phenomnomnominal/tsquery": "npm:~6.1.4" + identity-obj-proxy: "npm:3.0.0" + jest-config: "npm:^30.0.2" + jest-resolve: "npm:^30.0.2" + jest-util: "npm:^30.0.2" + minimatch: "npm:10.2.4" + picocolors: "npm:^1.1.0" + resolve.exports: "npm:2.0.3" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + checksum: 10/6894c8f0372077960ebf01e85ee3f5339f7b3a50634a21262cd47affcd5ecbfaae3ae18c941037eeffc7366b9213fee0969e35d51ab67802157aeb2585ba8328 + languageName: node + linkType: hard + "@nx/js@npm:22.7.0": version: 22.7.0 resolution: "@nx/js@npm:22.7.0" @@ -5457,6 +5497,45 @@ __metadata: languageName: node linkType: hard +"@nx/js@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/js@npm:22.7.1" + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/plugin-proposal-decorators": "npm:^7.22.7" + "@babel/plugin-transform-class-properties": "npm:^7.22.5" + "@babel/plugin-transform-runtime": "npm:^7.23.2" + "@babel/preset-env": "npm:^7.23.2" + "@babel/preset-typescript": "npm:^7.22.5" + "@babel/runtime": "npm:^7.22.6" + "@nx/devkit": "npm:22.7.1" + "@nx/workspace": "npm:22.7.1" + "@zkochan/js-yaml": "npm:0.0.7" + babel-plugin-const-enum: "npm:^1.0.1" + babel-plugin-macros: "npm:^3.1.0" + babel-plugin-transform-typescript-metadata: "npm:^0.3.1" + chalk: "npm:^4.1.0" + columnify: "npm:^1.6.0" + detect-port: "npm:^1.5.1" + ignore: "npm:^5.0.4" + js-tokens: "npm:^4.0.0" + jsonc-parser: "npm:3.2.0" + npm-run-path: "npm:^4.0.1" + picocolors: "npm:^1.1.0" + picomatch: "npm:4.0.4" + semver: "npm:^7.6.3" + source-map-support: "npm:0.5.19" + tinyglobby: "npm:^0.2.12" + tslib: "npm:^2.3.0" + peerDependencies: + verdaccio: ^6.0.5 + peerDependenciesMeta: + verdaccio: + optional: true + checksum: 10/8e08791c7eea283f7b17305dbadd1df44b0c78a31f90f40c1bf7fd048473d0ddef99037e55eab7dec1b5d8086ed462ebd8b3e206ede0360ab9d6968c98f23fb3 + languageName: node + linkType: hard + "@nx/node@npm:22.7.0": version: 22.7.0 resolution: "@nx/node@npm:22.7.0" @@ -5480,6 +5559,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-arm64@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-darwin-arm64@npm:22.7.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-darwin-x64@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-darwin-x64@npm:22.7.0" @@ -5487,6 +5573,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-x64@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-darwin-x64@npm:22.7.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-freebsd-x64@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-freebsd-x64@npm:22.7.0" @@ -5494,6 +5587,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-freebsd-x64@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-freebsd-x64@npm:22.7.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-linux-arm-gnueabihf@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.0" @@ -5501,6 +5601,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm-gnueabihf@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@nx/nx-linux-arm64-gnu@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.0" @@ -5508,6 +5615,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-gnu@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-arm64-musl@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-arm64-musl@npm:22.7.0" @@ -5515,6 +5629,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-musl@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-linux-arm64-musl@npm:22.7.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-linux-x64-gnu@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-x64-gnu@npm:22.7.0" @@ -5522,6 +5643,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-gnu@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-linux-x64-gnu@npm:22.7.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-x64-musl@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-linux-x64-musl@npm:22.7.0" @@ -5529,6 +5657,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-musl@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-linux-x64-musl@npm:22.7.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-win32-arm64-msvc@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.0" @@ -5536,6 +5671,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-arm64-msvc@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-win32-x64-msvc@npm:22.7.0": version: 22.7.0 resolution: "@nx/nx-win32-x64-msvc@npm:22.7.0" @@ -5543,6 +5685,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-x64-msvc@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/nx-win32-x64-msvc@npm:22.7.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@nx/webpack@npm:22.7.0": version: 22.7.0 resolution: "@nx/webpack@npm:22.7.0" @@ -5604,6 +5753,23 @@ __metadata: languageName: node linkType: hard +"@nx/workspace@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/workspace@npm:22.7.1" + dependencies: + "@nx/devkit": "npm:22.7.1" + "@zkochan/js-yaml": "npm:0.0.7" + chalk: "npm:^4.1.0" + enquirer: "npm:~2.3.6" + nx: "npm:22.7.1" + picomatch: "npm:4.0.4" + semver: "npm:^7.6.3" + tslib: "npm:^2.3.0" + yargs-parser: "npm:21.1.1" + checksum: 10/c0404e3bdbb5aef7a96a7cdd37719359fd76cb3529ce73caa1513d420295a40e3e8b82a759944fa419b2511f0a4c13949257c7031dbb5d2c91b5037e9ecbdb04 + languageName: node + linkType: hard + "@open-draft/deferred-promise@npm:^2.2.0": version: 2.2.0 resolution: "@open-draft/deferred-promise@npm:2.2.0" @@ -20435,6 +20601,166 @@ __metadata: languageName: node linkType: hard +"nx@npm:22.7.1": + version: 22.7.1 + resolution: "nx@npm:22.7.1" + dependencies: + "@emnapi/core": "npm:1.4.5" + "@emnapi/runtime": "npm:1.4.5" + "@emnapi/wasi-threads": "npm:1.0.4" + "@jest/diff-sequences": "npm:30.0.1" + "@napi-rs/wasm-runtime": "npm:0.2.4" + "@nx/nx-darwin-arm64": "npm:22.7.1" + "@nx/nx-darwin-x64": "npm:22.7.1" + "@nx/nx-freebsd-x64": "npm:22.7.1" + "@nx/nx-linux-arm-gnueabihf": "npm:22.7.1" + "@nx/nx-linux-arm64-gnu": "npm:22.7.1" + "@nx/nx-linux-arm64-musl": "npm:22.7.1" + "@nx/nx-linux-x64-gnu": "npm:22.7.1" + "@nx/nx-linux-x64-musl": "npm:22.7.1" + "@nx/nx-win32-arm64-msvc": "npm:22.7.1" + "@nx/nx-win32-x64-msvc": "npm:22.7.1" + "@tybys/wasm-util": "npm:0.9.0" + "@yarnpkg/lockfile": "npm:1.1.0" + "@zkochan/js-yaml": "npm:0.0.7" + ansi-colors: "npm:4.1.3" + ansi-regex: "npm:5.0.1" + ansi-styles: "npm:4.3.0" + argparse: "npm:2.0.1" + asynckit: "npm:0.4.0" + axios: "npm:1.15.0" + balanced-match: "npm:4.0.3" + base64-js: "npm:1.5.1" + bl: "npm:4.1.0" + brace-expansion: "npm:5.0.2" + buffer: "npm:5.7.1" + call-bind-apply-helpers: "npm:1.0.2" + chalk: "npm:4.1.2" + cli-cursor: "npm:3.1.0" + cli-spinners: "npm:2.6.1" + cliui: "npm:8.0.1" + clone: "npm:1.0.4" + color-convert: "npm:2.0.1" + color-name: "npm:1.1.4" + combined-stream: "npm:1.0.8" + defaults: "npm:1.0.4" + define-lazy-prop: "npm:2.0.0" + delayed-stream: "npm:1.0.0" + dotenv: "npm:16.4.7" + dotenv-expand: "npm:12.0.3" + dunder-proto: "npm:1.0.1" + ejs: "npm:5.0.1" + emoji-regex: "npm:8.0.0" + end-of-stream: "npm:1.4.5" + enquirer: "npm:2.3.6" + es-define-property: "npm:1.0.1" + es-errors: "npm:1.3.0" + es-object-atoms: "npm:1.1.1" + es-set-tostringtag: "npm:2.1.0" + escalade: "npm:3.2.0" + escape-string-regexp: "npm:1.0.5" + figures: "npm:3.2.0" + flat: "npm:5.0.2" + follow-redirects: "npm:1.15.11" + form-data: "npm:4.0.5" + fs-constants: "npm:1.0.0" + function-bind: "npm:1.1.2" + get-caller-file: "npm:2.0.5" + get-intrinsic: "npm:1.3.0" + get-proto: "npm:1.0.1" + gopd: "npm:1.2.0" + has-flag: "npm:4.0.0" + has-symbols: "npm:1.1.0" + has-tostringtag: "npm:1.0.2" + hasown: "npm:2.0.2" + ieee754: "npm:1.2.1" + ignore: "npm:7.0.5" + inherits: "npm:2.0.4" + is-docker: "npm:2.2.1" + is-fullwidth-code-point: "npm:3.0.0" + is-interactive: "npm:1.0.0" + is-unicode-supported: "npm:0.1.0" + is-wsl: "npm:2.2.0" + json5: "npm:2.2.3" + jsonc-parser: "npm:3.2.0" + lines-and-columns: "npm:2.0.3" + log-symbols: "npm:4.1.0" + math-intrinsics: "npm:1.1.0" + mime-db: "npm:1.52.0" + mime-types: "npm:2.1.35" + mimic-fn: "npm:2.1.0" + minimatch: "npm:10.2.4" + minimist: "npm:1.2.8" + npm-run-path: "npm:4.0.1" + once: "npm:1.4.0" + onetime: "npm:5.1.2" + open: "npm:8.4.2" + ora: "npm:5.3.0" + path-key: "npm:3.1.1" + picocolors: "npm:1.1.1" + proxy-from-env: "npm:2.1.0" + readable-stream: "npm:3.6.2" + require-directory: "npm:2.1.1" + resolve.exports: "npm:2.0.3" + restore-cursor: "npm:3.1.0" + safe-buffer: "npm:5.2.1" + semver: "npm:7.7.4" + signal-exit: "npm:3.0.7" + smol-toml: "npm:1.6.1" + string-width: "npm:4.2.3" + string_decoder: "npm:1.3.0" + strip-ansi: "npm:6.0.1" + strip-bom: "npm:3.0.0" + supports-color: "npm:7.2.0" + tar-stream: "npm:2.2.0" + tmp: "npm:0.2.4" + tree-kill: "npm:1.2.2" + tsconfig-paths: "npm:4.2.0" + tslib: "npm:2.8.1" + util-deprecate: "npm:1.0.2" + wcwidth: "npm:1.0.1" + wrap-ansi: "npm:7.0.0" + wrappy: "npm:1.0.2" + y18n: "npm:5.0.8" + yaml: "npm:2.8.0" + yargs: "npm:17.7.2" + yargs-parser: "npm:21.1.1" + peerDependencies: + "@swc-node/register": ^1.11.1 + "@swc/core": ^1.15.8 + dependenciesMeta: + "@nx/nx-darwin-arm64": + optional: true + "@nx/nx-darwin-x64": + optional: true + "@nx/nx-freebsd-x64": + optional: true + "@nx/nx-linux-arm-gnueabihf": + optional: true + "@nx/nx-linux-arm64-gnu": + optional: true + "@nx/nx-linux-arm64-musl": + optional: true + "@nx/nx-linux-x64-gnu": + optional: true + "@nx/nx-linux-x64-musl": + optional: true + "@nx/nx-win32-arm64-msvc": + optional: true + "@nx/nx-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc-node/register": + optional: true + "@swc/core": + optional: true + bin: + nx: dist/bin/nx.js + nx-cloud: dist/bin/nx-cloud.js + checksum: 10/965fb8e376b10ebdaf495ae35e6b6d19a5e383d28f849e0c304954cd89320979a4588eeab841ae0adbb66c9a9e6a034290d992cb47cd055496d4554954d46d03 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -27474,7 +27800,7 @@ __metadata: "@enormora/objectory": "npm:0.0.7" "@faker-js/faker": "npm:9.9.0" "@ls-lint/ls-lint": "npm:2.3.1" - "@nx/jest": "npm:22.7.0" + "@nx/jest": "npm:22.7.1" "@nx/node": "npm:22.7.0" "@nx/workspace": "npm:22.7.0" "@stylistic/eslint-plugin": "npm:5.10.0" From 093ab648f27ebfc3f0f447cb19aba3da6b65f6c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 07:41:06 +0200 Subject: [PATCH 32/49] chore(deps): update dependency @formatjs/cli to v6.14.4 (#21197) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index d2cc4c24b92..af6d90dcb73 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -105,7 +105,7 @@ "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", "@emotion/eslint-plugin": "11.12.0", - "@formatjs/cli": "6.14.3", + "@formatjs/cli": "6.14.4", "@nx/webpack": "22.7.0", "@playwright/test": "1.59.1", "@roamhq/wrtc": "0.10.0", diff --git a/yarn.lock b/yarn.lock index 0655c4aef0b..30eaa833b45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3684,9 +3684,9 @@ __metadata: languageName: node linkType: hard -"@formatjs/cli@npm:6.14.3": - version: 6.14.3 - resolution: "@formatjs/cli@npm:6.14.3" +"@formatjs/cli@npm:6.14.4": + version: 6.14.4 + resolution: "@formatjs/cli@npm:6.14.4" peerDependencies: "@glimmer/syntax": ^0.84.3 || ^0.95.0 "@vue/compiler-core": ^3.5.0 @@ -3709,7 +3709,7 @@ __metadata: optional: true bin: formatjs: bin/formatjs - checksum: 10/e5e150dedcc09f1cfc62600f871f5c1bc9aba9ee2208cf895a34cd157c5986eebc0dc9fd96523ee5cd0a45eb5ec050349a3cdc3a0653d3578c9dadc84fff03b6 + checksum: 10/af2734cba409a18ea0be611e1bd6381495739df31ed9eda7b6d124d2532dd4f586b1f85eed0b3f2873e6b5ceb2cd679ed0f5f26f613c3bac45ab416a7a5d5676 languageName: node linkType: hard @@ -9728,7 +9728,7 @@ __metadata: "@datadog/browser-rum": "npm:5.35.1" "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" - "@formatjs/cli": "npm:6.14.3" + "@formatjs/cli": "npm:6.14.4" "@formkit/auto-animate": "npm:0.9.0" "@lexical/code": "npm:0.27.2" "@lexical/history": "npm:0.27.2" From 1a76b0c8ab84027a34a4ea746f255637348cdc3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 06:12:23 +0000 Subject: [PATCH 33/49] chore(deps): update dependency baseline-browser-mapping to v2.10.24 --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index af6d90dcb73..bfa576453f0 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -136,7 +136,7 @@ "babel-jest": "30.3.0", "babel-loader": "10.1.1", "babel-plugin-transform-import-meta": "2.3.3", - "baseline-browser-mapping": "2.10.23", + "baseline-browser-mapping": "2.10.24", "browserslist": "4.28.2", "copy-webpack-plugin": "13.0.1", "css-loader": "7.1.4", diff --git a/yarn.lock b/yarn.lock index 4ad5c86c397..87af4968bb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9947,7 +9947,7 @@ __metadata: babel-jest: "npm:30.3.0" babel-loader: "npm:10.1.1" babel-plugin-transform-import-meta: "npm:2.3.3" - baseline-browser-mapping: "npm:2.10.23" + baseline-browser-mapping: "npm:2.10.24" beautiful-react-hooks: "npm:5.0.3" browserslist: "npm:4.28.2" classnames: "npm:2.5.1" @@ -11151,12 +11151,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:2.10.23": - version: 2.10.23 - resolution: "baseline-browser-mapping@npm:2.10.23" +"baseline-browser-mapping@npm:2.10.24": + version: 2.10.24 + resolution: "baseline-browser-mapping@npm:2.10.24" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10/54c21dbc86b79f53b1c15fc7577695f1ccf6e1dba0aaa9161ad818848330c45c5d6c5debdcac16807037d04371b828213976a25ea77cc4b30466d9b52bbc702c + checksum: 10/2bca673c12bd24d66f1a28b9eee1ba4401eb0030fe8aef6c4f711318dd312039acc3698df40a582cba8ae3100af9bb2bc541e746cb833c61b9be2b093de99bd0 languageName: node linkType: hard From 1b9b1a6b3fa87d7c2ccc3c4fa0f6ca7c8b06a80c Mon Sep 17 00:00:00 2001 From: Zhanna Chaikovska <87633082+zhannach@users.noreply.github.com> Date: Mon, 4 May 2026 09:33:11 +0300 Subject: [PATCH 34/49] test: implement SSO login with claimed domain TC-8781 (#21192) --- apps/webapp/test/e2e_tests/.env.staging.tpl | 4 ++ .../loginWithClaimedDomain-TC-8781.spec.ts | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 apps/webapp/test/e2e_tests/specs/CriticalFlow/loginWithClaimedDomain-TC-8781.spec.ts diff --git a/apps/webapp/test/e2e_tests/.env.staging.tpl b/apps/webapp/test/e2e_tests/.env.staging.tpl index 74d1b6f106a..d54c7f8fe04 100644 --- a/apps/webapp/test/e2e_tests/.env.staging.tpl +++ b/apps/webapp/test/e2e_tests/.env.staging.tpl @@ -35,6 +35,10 @@ SCIM_USER_EMAIL=op://Test Automation/Staging SCIM user/okta username SCIM_USER_PASSWORD="{{ op://Test Automation/Staging SCIM user/okta password }}" SCIM_USER_SSO_CODE=op://Test Automation/Staging SCIM user/SSO code +SSO_CLAIMED_USER_EMAIL=op://Test Automation/Staging Claimed Domain User/email +SSO_CLAIMED_USER_PASSWORD=op://Test Automation/Staging Claimed Domain User/password +SSO_CLAIMED_DOMAIN_CODE=op://Test Automation/Staging Claimed Domain User/SSO code + INBUCKET_USERNAME=op://Test Automation/BackendConnection staging/inbucketUsername INBUCKET_PASSWORD="{{ op://Test Automation/BackendConnection staging/inbucketPassword }}" INBUCKET_URL=op://Test Automation/BackendConnection staging/inbucketUrl diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/loginWithClaimedDomain-TC-8781.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/loginWithClaimedDomain-TC-8781.spec.ts new file mode 100644 index 00000000000..f18787d26b7 --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/loginWithClaimedDomain-TC-8781.spec.ts @@ -0,0 +1,59 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {PageManager} from 'test/e2e_tests/pageManager'; + +import {test, expect, LOGIN_TIMEOUT} from '../../test.fixtures'; +import {getUser} from 'test/e2e_tests/data/user'; + +test('SSO login with claimed domain', {tag: ['@TC-8781', '@regression']}, async ({context, createPage}) => { + const page = await createPage(context); + const pageManager = PageManager.from(page); + await pageManager.openMainPage(); + + const ssoUser = getUser({ + email: process.env.SSO_CLAIMED_DOMAIN_CODE, + username: process.env.SSO_CLAIMED_USER_EMAIL, + password: process.env.SSO_CLAIMED_USER_PASSWORD, + }); + + const {pages, components} = pageManager.webapp; + const [idpPage] = await Promise.all([ + context.waitForEvent('page'), + pages.singleSignOn().enterEmailOnSSOPage(ssoUser.email), + ]); + + await test.step('Log in on IDP page', async () => { + await idpPage.getByRole('textbox', {name: 'Username'}).fill(ssoUser.username, {timeout: 20_000}); + await idpPage.getByRole('textbox', {name: 'Password'}).fill(ssoUser.password); + await idpPage.getByRole('button', {name: 'Sign In'}).click(); + }); + + await test.step('Remove an existing device and confirm new history', async () => { + // Since this test re-uses the same user over and over again we need to always remove one of the previously registered devices + await page.getByRole('button', {name: 'Remove device'}).first().click({timeout: LOGIN_TIMEOUT}); + // We will also always be prompted to confirm the new history on this device + await pages.historyInfo().clickConfirmButton(); + await expect(components.conversationSidebar().sidebar, `Login took more than ${LOGIN_TIMEOUT}s`).toBeVisible({ + timeout: LOGIN_TIMEOUT, + }); + }); + + await expect(pages.sidebar().allConversationsButton).toBeVisible(); +}); From e4c0b6a91650b7613a9b140d88cdda17045979bd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 06:41:36 +0000 Subject: [PATCH 35/49] chore(deps): update dependency jsdom to v29.1.1 (#21200) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9259c389d19..8229f50c119 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "jest": "30.3.0", "jest-environment-jsdom": "30.3.0", "js-yaml": "4.1.1", - "jsdom": "29.1.0", + "jsdom": "29.1.1", "nock": "14.0.13", "nx": "22.7.0", "playwright": "1.59.1", diff --git a/yarn.lock b/yarn.lock index badabd35844..358ed034750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18654,9 +18654,9 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:29.1.0": - version: 29.1.0 - resolution: "jsdom@npm:29.1.0" +"jsdom@npm:29.1.1": + version: 29.1.1 + resolution: "jsdom@npm:29.1.1" dependencies: "@asamuzakjp/css-color": "npm:^5.1.11" "@asamuzakjp/dom-selector": "npm:^7.1.1" @@ -18684,7 +18684,7 @@ __metadata: peerDependenciesMeta: canvas: optional: true - checksum: 10/91194be30c10b518c8e21c3a15cf1ef0e6c74722fb8fe402f201efed3e5de55f13005f2a4108d02ccde75d1e16719ca898690be93cd22d498a4c062d035adae5 + checksum: 10/344aed7f91839b6c7d1b40778c5542d6ded7d42d88e1b787e10bf12d4ccd65464a5f23f774eb84350885c75a48efc99f6972adbb94dffe324a1b065d3650843c languageName: node linkType: hard @@ -27848,7 +27848,7 @@ __metadata: jest: "npm:30.3.0" jest-environment-jsdom: "npm:30.3.0" js-yaml: "npm:4.1.1" - jsdom: "npm:29.1.0" + jsdom: "npm:29.1.1" logdown: "npm:3.3.1" long: "npm:5.3.2" nock: "npm:14.0.13" From eec5fead07d3b39370cdb5a3b8b3c503560de5fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 09:22:50 +0200 Subject: [PATCH 36/49] chore(deps): update dependency nock to v14.0.14 (#21201) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8229f50c119..362c63c7460 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "jest-environment-jsdom": "30.3.0", "js-yaml": "4.1.1", "jsdom": "29.1.1", - "nock": "14.0.13", + "nock": "14.0.14", "nx": "22.7.0", "playwright": "1.59.1", "prettier": "3.8.3", diff --git a/yarn.lock b/yarn.lock index 358ed034750..00b1579bf39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20257,14 +20257,14 @@ __metadata: languageName: node linkType: hard -"nock@npm:14.0.13": - version: 14.0.13 - resolution: "nock@npm:14.0.13" +"nock@npm:14.0.14": + version: 14.0.14 + resolution: "nock@npm:14.0.14" dependencies: "@mswjs/interceptors": "npm:^0.41.0" json-stringify-safe: "npm:^5.0.1" propagate: "npm:^2.0.0" - checksum: 10/302d65e7408ac3aea660cc5881caf303235e67f76c712b07e3b7db1d46dbd968515ed2bbbbefd44765bfa1178380f7ee4d409bae2ca79a7cc2cde02d46869e45 + checksum: 10/8408c1bd40cdea64f5b236813c713880f413c487589bff175cfc6089c135119c716a48cb93b6d1c1b51e7ef3a4870fc9e2ee7260435f33c4e951c06a2349bc58 languageName: node linkType: hard @@ -27851,7 +27851,7 @@ __metadata: jsdom: "npm:29.1.1" logdown: "npm:3.3.1" long: "npm:5.3.2" - nock: "npm:14.0.13" + nock: "npm:14.0.14" nx: "npm:22.7.0" playwright: "npm:1.59.1" prettier: "npm:3.8.3" From 4970f0e8dd4f0863a723c655c5c49e83cd3f35f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 07:38:47 +0000 Subject: [PATCH 37/49] chore(deps): update nx monorepo tooling to v22.7.1 --- package.json | 6 ++--- yarn.lock | 71 ++++++++++++++++++---------------------------------- 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 362c63c7460..b6b1e625c13 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "@faker-js/faker": "9.9.0", "@ls-lint/ls-lint": "2.3.1", "@nx/jest": "22.7.1", - "@nx/node": "22.7.0", - "@nx/workspace": "22.7.0", + "@nx/node": "22.7.1", + "@nx/workspace": "22.7.1", "@stylistic/eslint-plugin": "5.10.0", "@tony.ganchev/eslint-plugin-header": "3.4.4", "@types/eslint": "9.6.1", @@ -60,7 +60,7 @@ "js-yaml": "4.1.1", "jsdom": "29.1.1", "nock": "14.0.14", - "nx": "22.7.0", + "nx": "22.7.1", "playwright": "1.59.1", "prettier": "3.8.3", "rimraf": "6.1.3", diff --git a/yarn.lock b/yarn.lock index 00b1579bf39..9ea3b827741 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5379,28 +5379,28 @@ __metadata: languageName: node linkType: hard -"@nx/docker@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/docker@npm:22.7.0" +"@nx/docker@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/docker@npm:22.7.1" dependencies: - "@nx/devkit": "npm:22.7.0" + "@nx/devkit": "npm:22.7.1" enquirer: "npm:~2.3.6" tslib: "npm:^2.3.0" - checksum: 10/7a4ba33beabafb58ac824be2f439de67ef287035666ee757913759f8e3ec68bce6739b13362ad1a296695cf817966699adb40cb1ea6516a818d082071059998e + checksum: 10/6e4c0452fc79fd27287bef1d17ed813412a70bf7d77bdb5f82e2aa7da847839b5536ab16c10ce217af4169d0f3a198a8abd4cd4c7143439dbfb014b47a4b6b18 languageName: node linkType: hard -"@nx/eslint@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/eslint@npm:22.7.0" +"@nx/eslint@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/eslint@npm:22.7.1" dependencies: - "@nx/devkit": "npm:22.7.0" - "@nx/js": "npm:22.7.0" + "@nx/devkit": "npm:22.7.1" + "@nx/js": "npm:22.7.1" semver: "npm:^7.6.3" tslib: "npm:^2.3.0" typescript: "npm:~5.9.2" peerDependencies: - "@nx/jest": 22.7.0 + "@nx/jest": 22.7.1 "@zkochan/js-yaml": 0.0.7 eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -5408,30 +5408,7 @@ __metadata: optional: true "@zkochan/js-yaml": optional: true - checksum: 10/b54891278a042c3adb44b6dbdfe0c19637a495e0c7e84e174fd60164e109ba2f7b4ff2b4d2d00a39d2ed797fec69a18fa9ab7705a881bd6aa497423147143005 - languageName: node - linkType: hard - -"@nx/jest@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/jest@npm:22.7.0" - dependencies: - "@jest/reporters": "npm:^30.0.2" - "@jest/test-result": "npm:^30.0.2" - "@nx/devkit": "npm:22.7.0" - "@nx/js": "npm:22.7.0" - "@phenomnomnominal/tsquery": "npm:~6.1.4" - identity-obj-proxy: "npm:3.0.0" - jest-config: "npm:^30.0.2" - jest-resolve: "npm:^30.0.2" - jest-util: "npm:^30.0.2" - minimatch: "npm:10.2.4" - picocolors: "npm:^1.1.0" - resolve.exports: "npm:2.0.3" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - checksum: 10/16e8b4cfbeba8372c6b8271c7613a87c9f12a4bead2e9a8b867d5b044c3be7e2925d9a24ccbf2088e03a3e2f851065da9c0690e77e5b6f8cbd8517364e17d33b + checksum: 10/df10c8e633a08b58ea6be06ce4a830336acb3799ac334dee3a585e79b456278c19276451d80e363e79d27eb7b60d1034aedbcd7dce450a10d3796da93bf76f7b languageName: node linkType: hard @@ -5536,19 +5513,19 @@ __metadata: languageName: node linkType: hard -"@nx/node@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/node@npm:22.7.0" +"@nx/node@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/node@npm:22.7.1" dependencies: - "@nx/devkit": "npm:22.7.0" - "@nx/docker": "npm:22.7.0" - "@nx/eslint": "npm:22.7.0" - "@nx/jest": "npm:22.7.0" - "@nx/js": "npm:22.7.0" + "@nx/devkit": "npm:22.7.1" + "@nx/docker": "npm:22.7.1" + "@nx/eslint": "npm:22.7.1" + "@nx/jest": "npm:22.7.1" + "@nx/js": "npm:22.7.1" kill-port: "npm:^1.6.1" tcp-port-used: "npm:^1.0.2" tslib: "npm:^2.3.0" - checksum: 10/7531681966f4792c582c10cb184e25e323ad67d46d4403767de4dea32b4f61936dde8e48ff537d9ed63b66174b91113c6e00f90060dc29ff585f95b19c53c559 + checksum: 10/df25e5cc3abd2ce6337144fb12cb0a8508d3aaa64a593a646bb3604c1c06977441fa5732a3a39b1fa60ec50480d9311537a906fa16d5580804a07f014a798af5 languageName: node linkType: hard @@ -27801,8 +27778,8 @@ __metadata: "@faker-js/faker": "npm:9.9.0" "@ls-lint/ls-lint": "npm:2.3.1" "@nx/jest": "npm:22.7.1" - "@nx/node": "npm:22.7.0" - "@nx/workspace": "npm:22.7.0" + "@nx/node": "npm:22.7.1" + "@nx/workspace": "npm:22.7.1" "@stylistic/eslint-plugin": "npm:5.10.0" "@tony.ganchev/eslint-plugin-header": "npm:3.4.4" "@types/eslint": "npm:9.6.1" @@ -27852,7 +27829,7 @@ __metadata: logdown: "npm:3.3.1" long: "npm:5.3.2" nock: "npm:14.0.14" - nx: "npm:22.7.0" + nx: "npm:22.7.1" playwright: "npm:1.59.1" prettier: "npm:3.8.3" rimraf: "npm:6.1.3" From 28ce4c7de8d12b7b0010c7e50528d113218dbff9 Mon Sep 17 00:00:00 2001 From: Christian Rackerseder Date: Wed, 29 Apr 2026 13:18:56 +0200 Subject: [PATCH 38/49] chore(webapp): enable strict null checks --- apps/webapp/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index 0f238a744aa..80621983d5f 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -27,7 +27,6 @@ "rootDirs": ["src"], "target": "ESNext", "strict": true, - "strictNullChecks": false, "strictFunctionTypes": false }, "types": ["@types/wicg-file-system-access", "jest", "@testing-library/jest-dom"], From b83a7fb02f2db2c8da499207dcb882472cfd4c39 Mon Sep 17 00:00:00 2001 From: Christian Rackerseder Date: Mon, 4 May 2026 09:50:28 +0200 Subject: [PATCH 39/49] chore(types): enable strict null checks and fix repo-wide type-check errors - guarantee that enabling strict null checks does not change message, calling, invitation, or notification behavior - keep nullability explicit and readable so invariants remain obvious at call sites - maintain repository-wide confidence by keeping both test behavior and type contracts aligned --- apps/webapp/src/script/auth/localeConfig.ts | 2 +- .../script/auth/module/action/AuthAction.ts | 4 +- .../script/auth/module/action/ClientAction.ts | 2 +- .../auth/module/action/InvitationAction.ts | 10 +- .../auth/module/action/NotificationAction.ts | 3 + .../webapp/src/script/auth/page/SetHandle.tsx | 6 +- .../src/script/auth/page/SingleSignOn.tsx | 16 +- .../src/script/auth/page/SingleSignOnForm.tsx | 8 +- .../src/script/auth/util/AccentColor.ts | 8 +- .../webapp/src/script/auth/util/errorUtil.tsx | 2 +- apps/webapp/src/script/auth/util/urlUtil.ts | 2 +- .../script/components/Avatar/AvatarImage.tsx | 10 +- .../EditedMessagePlugin.tsx | 6 +- .../MessagesList/Message/MessageWrapper.tsx | 2 +- .../CreateConversationSteps/Preference.tsx | 2 +- .../videoBackgroundPerformancePanel.tsx | 4 +- .../components/panel/ServiceDetails.tsx | 2 +- .../src/script/hooks/usePausableInterval.ts | 6 +- .../src/script/hooks/useRootFontSize.ts | 4 +- .../script/legal-hold/LegalHoldEvaluator.ts | 17 +- apps/webapp/src/script/main/app.ts | 3 + apps/webapp/src/script/message/QuoteEntity.ts | 3 +- .../src/script/page/AppLock/AppLock.tsx | 6 +- .../panels/Conversations/Conversations.tsx | 10 +- .../MainContent/panels/Collection/utils.tsx | 12 +- .../avPreferences/SaveCallLogs.tsx | 8 +- .../conversationDetails.tsx | 3 + .../conversationParticipants.tsx | 4 + .../groupParticipantService.tsx | 22 +- .../FeatureConfigChangeNotifier.ts | 6 +- .../repositories/backup/backupRepository.ts | 2 +- .../repositories/backup/backupService.ts | 5 +- .../src/script/repositories/calling/Call.ts | 6 +- .../repositories/calling/CallingRepository.ts | 17 +- .../repositories/calling/videoGridHandler.ts | 5 +- .../repositories/client/ClientMapper.ts | 15 +- .../repositories/client/ClientRepository.ts | 38 +- .../connection/connectionEntity.ts | 10 +- .../connection/connectionMapper.ts | 8 +- .../connection/connectionRepository.ts | 4 +- .../ConversationAccessPermission.ts | 6 +- .../conversation/ConversationCellState.ts | 31 +- .../ConversationEphemeralHandler.ts | 25 +- .../conversation/ConversationMapper.ts | 32 +- .../conversation/ConversationRepository.ts | 356 +++++++++++++----- .../ConversationRoleRepository.ts | 87 +++-- .../conversation/ConversationState.ts | 22 +- .../conversation/ConversationStateHandler.ts | 25 +- .../shared/conversation/index.ts | 18 +- .../conversation/EventBuilder/EventBuilder.ts | 43 ++- .../repositories/conversation/EventMapper.ts | 70 ++-- .../conversation/MessageRepository.ts | 36 +- .../cryptography/CryptographyMapper.ts | 82 ++-- .../repositories/entity/Conversation.ts | 95 +++-- .../script/repositories/entity/User/User.ts | 15 +- .../repositories/entity/message/Asset.ts | 1 + .../entity/message/CallMessage.ts | 2 +- .../entity/message/ContentMessage.ts | 11 +- .../repositories/entity/message/FileAsset.ts | 8 +- .../entity/message/MediumImage.ts | 2 +- .../entity/message/MemberMessage.ts | 24 +- .../repositories/entity/message/Message.ts | 13 +- .../message/MessageTimerUpdateMessage.ts | 4 +- .../entity/message/VerificationMessage.ts | 6 +- .../repositories/event/EventRepository.ts | 5 +- .../script/repositories/event/EventService.ts | 26 +- .../repositories/extension/GiphyRepository.ts | 8 +- .../media/MediaConstraintsHandler.ts | 2 +- .../script/repositories/media/MediaEmbeds.ts | 2 +- .../script/repositories/media/MediaParser.ts | 4 +- .../media/useBackgroundEffectsStore.ts | 2 +- .../notification/NotificationRepository.ts | 3 + .../PreferenceNotificationRepository.ts | 25 +- .../repositories/storage/StorageService.ts | 12 +- .../repositories/team/TeamRepository.ts | 19 +- .../tracking/EventTrackingRepository.ts | 8 +- .../script/repositories/tracking/Helpers.ts | 3 +- .../repositories/user/AppLockRepository.ts | 2 +- .../script/repositories/user/AppLockState.ts | 25 +- .../repositories/user/UserHandleGenerator.ts | 4 +- .../repositories/user/UserRepository.ts | 2 +- .../src/script/repositories/user/UserState.ts | 2 +- .../telemetry/app_init/AppInitTimings.ts | 2 +- .../src/script/time/serverTimeHandler.ts | 2 +- apps/webapp/src/script/ui/Shortcut.ts | 7 +- apps/webapp/src/script/util/arrayUtil.ts | 3 +- apps/webapp/src/script/util/clipboardUtil.ts | 14 +- apps/webapp/src/script/util/debugUtil.ts | 28 +- .../src/script/util/sanitizationUtil.ts | 10 +- .../script/util/test/mock/localStorageMock.ts | 2 +- apps/webapp/src/script/util/util.ts | 17 +- .../src/script/view_model/CallingViewModel.ts | 11 +- .../src/script/view_model/ListViewModel.ts | 25 +- apps/webapp/tsconfig.json | 1 + libraries/core/src/account.ts | 6 +- .../core/src/auth/loginSanitizer.test.ts | 2 +- libraries/core/src/auth/loginSanitizer.ts | 2 +- .../src/client/clientBackendRepository.ts | 2 +- .../src/client/clientDatabaseRepository.ts | 2 +- libraries/core/src/client/clientInfo.ts | 2 +- libraries/core/src/client/clientService.ts | 4 +- .../core/src/connection/connectionService.ts | 2 +- .../conversation/content/clientAddContent.ts | 2 +- .../content/contentType.guards.ts | 2 +- .../core/src/conversation/content/index.ts | 2 +- .../src/conversation/message/payloadBundle.ts | 2 +- .../conversation/message/recipientsHelper.ts | 2 +- .../src/cryptography/genericMessageMapper.ts | 2 +- libraries/core/src/giphy/giphyService.ts | 2 +- .../notificationBackendRepository.ts | 2 +- .../notificationDatabaseRepository.ts | 2 +- .../src/notification/notificationService.ts | 2 +- libraries/core/src/self/selfService.ts | 2 +- libraries/core/src/team/teamService.ts | 2 +- libraries/core/src/test/accountHelper.ts | 2 +- libraries/core/src/user/userService.ts | 2 +- libraries/core/src/util/typePredicateUtil.ts | 4 +- 117 files changed, 1098 insertions(+), 549 deletions(-) diff --git a/apps/webapp/src/script/auth/localeConfig.ts b/apps/webapp/src/script/auth/localeConfig.ts index bd57e1c2971..ab03c03f714 100644 --- a/apps/webapp/src/script/auth/localeConfig.ts +++ b/apps/webapp/src/script/auth/localeConfig.ts @@ -39,7 +39,7 @@ export function normalizeLanguage(language: string = DEFAULT_LANGUAGE): string { export function findLanguage(language: string = DEFAULT_LANGUAGE): string { language = normalizeLanguage(language); - return Locales.find(locale => locale.startsWith(language)); + return Locales.find(locale => locale.startsWith(language)) ?? DEFAULT_LANGUAGE; } export function mapLanguage(language: string = DEFAULT_LANGUAGE): string { diff --git a/apps/webapp/src/script/auth/module/action/AuthAction.ts b/apps/webapp/src/script/auth/module/action/AuthAction.ts index af47c38443e..7a3d2561da2 100644 --- a/apps/webapp/src/script/auth/module/action/AuthAction.ts +++ b/apps/webapp/src/script/auth/module/action/AuthAction.ts @@ -228,7 +228,7 @@ export class AuthAction { if (statusCode === HTTP_STATUS.NOT_FOUND) { return new BackendError('', BackendErrorLabel.NOT_FOUND, HTTP_STATUS.NOT_FOUND); } - if (statusCode >= HTTP_STATUS.INTERNAL_SERVER_ERROR) { + if (statusCode !== undefined && statusCode >= HTTP_STATUS.INTERNAL_SERVER_ERROR) { return new BackendError('', BackendErrorLabel.SERVER_ERROR, HTTP_STATUS.INTERNAL_SERVER_ERROR); } return new BackendError('', SyntheticErrorLabel.SSO_GENERIC_ERROR, HTTP_STATUS.INTERNAL_SERVER_ERROR); @@ -283,7 +283,7 @@ export class AuthAction { const clientType = ClientType.PERMANENT; registration.locale = currentLanguage(); registration.name = registration.name.trim(); - registration.email = registration.email.trim(); + registration.email = registration.email?.trim() ?? ''; dispatch(AuthActionCreator.startRegisterPersonal()); try { diff --git a/apps/webapp/src/script/auth/module/action/ClientAction.ts b/apps/webapp/src/script/auth/module/action/ClientAction.ts index d047e5b65ff..ebcd0641d7e 100644 --- a/apps/webapp/src/script/auth/module/action/ClientAction.ts +++ b/apps/webapp/src/script/auth/module/action/ClientAction.ts @@ -115,7 +115,7 @@ export class ClientAction { } return { classification: ClientClassification.DESKTOP, - cookieLabel: undefined, + cookieLabel: '', label: deviceLabel, model: deviceModel, }; diff --git a/apps/webapp/src/script/auth/module/action/InvitationAction.ts b/apps/webapp/src/script/auth/module/action/InvitationAction.ts index e8cdaf1747c..1c27b7c7b6d 100644 --- a/apps/webapp/src/script/auth/module/action/InvitationAction.ts +++ b/apps/webapp/src/script/auth/module/action/InvitationAction.ts @@ -37,7 +37,7 @@ export class InvitationAction { dispatch(InvitationActionCreator.startAddInvite()); const state = getState(); const inviteList = InviteSelector.getInvites(state); - const invitationEmail = invitation.email?.toLowerCase(); + const invitationEmail = invitation.email.toLowerCase(); const alreadyInvited = inviteList.find(inviteItem => inviteItem.email.toLowerCase() === invitationEmail); if (alreadyInvited) { const error = new BackendError( @@ -51,15 +51,15 @@ export class InvitationAction { const newInvite: NewTeamInvitation = { email: invitationEmail, - inviter_name: selfSelector.getSelfName(state), + inviter_name: selfSelector.getSelfName(state) ?? '', locale: languageSelector.getLanguage(state), role: Role.MEMBER, }; - const teamId = selfSelector.getSelfTeamId(state); - try { - const createdInvite = await apiClient.api.teams.invitation.postInvitation(teamId, newInvite); + const teamId = selfSelector.getSelfTeamId(state); + const ensuredTeamId = teamId ?? ''; + const createdInvite = await apiClient.api.teams.invitation.postInvitation(ensuredTeamId, newInvite); dispatch(InvitationActionCreator.successfulAddInvite(createdInvite)); } catch (error: unknown) { dispatch(InvitationActionCreator.failedAddInvite(toError(error))); diff --git a/apps/webapp/src/script/auth/module/action/NotificationAction.ts b/apps/webapp/src/script/auth/module/action/NotificationAction.ts index 3b5d09e3d35..e1a4cd86a49 100644 --- a/apps/webapp/src/script/auth/module/action/NotificationAction.ts +++ b/apps/webapp/src/script/auth/module/action/NotificationAction.ts @@ -22,6 +22,9 @@ import type {ThunkAction} from '../reducer'; export class NotificationAction { setLastEventDate = (lastEventDate: Date): ThunkAction => { return async (dispatch, getState, {core}) => { + if (core.service === undefined) { + throw new Error('Cannot update notification state before core service is available'); + } await core.service.notification.setLastEventDate(lastEventDate); }; }; diff --git a/apps/webapp/src/script/auth/page/SetHandle.tsx b/apps/webapp/src/script/auth/page/SetHandle.tsx index 264bcc52da6..8ac59c28a92 100644 --- a/apps/webapp/src/script/auth/page/SetHandle.tsx +++ b/apps/webapp/src/script/auth/page/SetHandle.tsx @@ -60,7 +60,7 @@ const SetHandleComponent = ({ name, removeLocalStorage, }: Props & ConnectedProps & DispatchProps) => { - const [error, setError] = useState(null); + const [error, setError] = useState(null); const [handle, setHandle] = useState(''); const {state} = useLocation(); const isNewAccount = state?.isNewAccount ?? false; @@ -78,7 +78,7 @@ const SetHandleComponent = ({ (async () => { doGetConsents(); try { - const suggestions = createSuggestions(name); + const suggestions = createSuggestions(name ?? ''); const handle = await checkHandles(suggestions); setHandle(handle); } catch (error: unknown) { @@ -159,7 +159,7 @@ const SetHandleComponent = ({ {t('chooseHandle.submitButton')} - {error && parseError(error)} + {error !== null ? parseError(error) : null} {!isFetching && hasUnsetMarketingConsent && ( diff --git a/apps/webapp/src/script/auth/page/SingleSignOn.tsx b/apps/webapp/src/script/auth/page/SingleSignOn.tsx index 2b53b4e6846..445d3782fd2 100644 --- a/apps/webapp/src/script/auth/page/SingleSignOn.tsx +++ b/apps/webapp/src/script/auth/page/SingleSignOn.tsx @@ -66,7 +66,7 @@ type Props = React.HTMLAttributes; const logger = getLogger('SingleSignOn'); const SingleSignOnComponent = ({hasDefaultSSOCode}: Props & ConnectedProps & DispatchProps) => { - const ssoWindowRef = useRef(); + const ssoWindowRef = useRef(null); const params = useParams<{code?: string}>(); const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); const [isOverlayOpen, setIsOverlayOpen] = useState(false); @@ -78,14 +78,18 @@ const SingleSignOnComponent = ({hasDefaultSSOCode}: Props & ConnectedProps & Dis const SSO_WINDOW_CLOSE_POLLING_INTERVAL = 1000; return new Promise((resolve, reject) => { - let timerId: number = undefined; - let onReceiveChildWindowMessage: (event: MessageEvent) => void = undefined; - let onParentWindowClose: (event: Event) => void = undefined; + let timerId: number | undefined = undefined; + let onReceiveChildWindowMessage: ((event: MessageEvent) => void) | undefined = undefined; + let onParentWindowClose: ((event: Event) => void) | undefined = undefined; const onChildWindowClose = () => { clearInterval(timerId); - window.removeEventListener('message', onReceiveChildWindowMessage); - window.removeEventListener('unload', onParentWindowClose); + if (onReceiveChildWindowMessage) { + window.removeEventListener('message', onReceiveChildWindowMessage); + } + if (onParentWindowClose) { + window.removeEventListener('unload', onParentWindowClose); + } setIsOverlayOpen(false); }; diff --git a/apps/webapp/src/script/auth/page/SingleSignOnForm.tsx b/apps/webapp/src/script/auth/page/SingleSignOnForm.tsx index 6fabc95aff5..a762e1d797a 100644 --- a/apps/webapp/src/script/auth/page/SingleSignOnForm.tsx +++ b/apps/webapp/src/script/auth/page/SingleSignOnForm.tsx @@ -72,11 +72,11 @@ const SingleSignOnFormComponent = ({ pushAccountRegistrationData, account, }: SingleSignOnFormProps & ConnectedProps & DispatchProps) => { - const codeOrMailInput = useRef(); - const [codeOrMail, setCodeOrMail] = useState(account.email || ''); + const codeOrMailInput = useRef(null); + const [codeOrMail, setCodeOrMail] = useState(account.email ?? ''); const [disableInput, setDisableInput] = useState(false); const navigate = useNavigate(); - const [clientType, setClientType] = useState(null); + const [clientType, setClientType] = useState(ClientType.PERMANENT); const [ssoError, setSsoError] = useState(null); const [isCodeOrMailInputValid, setIsCodeOrMailInputValid] = useState(true); const [validationError, setValidationError] = useState(); @@ -230,7 +230,7 @@ const SingleSignOnFormComponent = ({ const welcomeUrl = pathWithParams( path, {[QUERY_KEY.CLIENT_TYPE]: clientType, [QUERY_KEY.SSO_AUTO_LOGIN]: true}, - null, + undefined, query, ); diff --git a/apps/webapp/src/script/auth/util/AccentColor.ts b/apps/webapp/src/script/auth/util/AccentColor.ts index 728b4e24dab..f0e750c7ea2 100644 --- a/apps/webapp/src/script/auth/util/AccentColor.ts +++ b/apps/webapp/src/script/auth/util/AccentColor.ts @@ -70,6 +70,10 @@ export const ACCENT_COLORS: AccentColor[] = [ VIOLET, ]; -export const accentColorById = (id: number): AccentColor => ACCENT_COLORS.find(color => color.id === id); +export const accentColorById = (id: number): AccentColor => { + return ACCENT_COLORS.find(color => color.id === id) ?? STRONG_BLUE; +}; -export const random = (): AccentColor => RandomUtil.randomArrayElement(ACCENT_COLORS); +export const random = (): AccentColor => { + return RandomUtil.randomArrayElement(ACCENT_COLORS) ?? STRONG_BLUE; +}; diff --git a/apps/webapp/src/script/auth/util/errorUtil.tsx b/apps/webapp/src/script/auth/util/errorUtil.tsx index c59800e7704..fb7f4d02bbf 100644 --- a/apps/webapp/src/script/auth/util/errorUtil.tsx +++ b/apps/webapp/src/script/auth/util/errorUtil.tsx @@ -43,7 +43,7 @@ export function parseError(error: any): JSX.Element | null { } export function parseValidationErrors(errors: any | any[]): JSX.Element[] { - const errorMessages = [].concat(errors || []); + const errorMessages: any[] = ([] as any[]).concat(errors ?? []); return errorMessages.map(error => ( {validationErrorStrings.hasOwnProperty(error.label) ? ( diff --git a/apps/webapp/src/script/auth/util/urlUtil.ts b/apps/webapp/src/script/auth/util/urlUtil.ts index 29ab7fe2a62..98f08ca0395 100644 --- a/apps/webapp/src/script/auth/util/urlUtil.ts +++ b/apps/webapp/src/script/auth/util/urlUtil.ts @@ -54,7 +54,7 @@ export function hasURLParameter(parameterName: string): boolean { .includes(parameterName); } -export function openTab(url: string): Window { +export function openTab(url: string): Window | null { const newWindow = window.open(url); if (newWindow) { newWindow.opener = null; diff --git a/apps/webapp/src/script/components/Avatar/AvatarImage.tsx b/apps/webapp/src/script/components/Avatar/AvatarImage.tsx index 0beac2e7ba7..ed3d42ff7b7 100644 --- a/apps/webapp/src/script/components/Avatar/AvatarImage.tsx +++ b/apps/webapp/src/script/components/Avatar/AvatarImage.tsx @@ -38,8 +38,8 @@ interface AvatarImageProps { borderRadius?: string; devicePixelRatio?: number; isGrey?: boolean; - mediumPicture: AssetRemoteData; - previewPicture: AssetRemoteData; + mediumPicture: AssetRemoteData | undefined; + previewPicture: AssetRemoteData | undefined; } const AvatarImage: React.FunctionComponent = ({ @@ -64,10 +64,12 @@ const AvatarImage: React.FunctionComponent = ({ const isSmall = ![AVATAR_SIZE.LARGE, AVATAR_SIZE.X_LARGE].includes(avatarSize); const loadHiRes = !isSmall && devicePixelRatio > 1; - const pictureResource: AssetRemoteData = loadHiRes ? (mediumPicture ?? previewPicture) : previewPicture; + const pictureResource: AssetRemoteData | undefined = loadHiRes + ? (mediumPicture ?? previewPicture) + : previewPicture; (async () => { - if (pictureResource) { + if (pictureResource !== undefined) { const isCached = pictureResource.downloadProgress === 100; setShowTransition(!isCached && !isSmall); try { diff --git a/apps/webapp/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx b/apps/webapp/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx index dd2514795ee..b7aa3d260f7 100644 --- a/apps/webapp/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx +++ b/apps/webapp/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx @@ -56,7 +56,11 @@ export function EditedMessagePlugin({message, showMarkdownPreview}: Props): null return; } - const messageContent = message.getFirstAsset().text; + const firstAsset = message.getFirstAsset(); + if (firstAsset === undefined) { + return; + } + const messageContent = firstAsset.text; const mentionNodes = getMentionNodesFromMessage(message); diff --git a/apps/webapp/src/script/components/MessagesList/Message/MessageWrapper.tsx b/apps/webapp/src/script/components/MessagesList/Message/MessageWrapper.tsx index 4e24f44a5b0..1c52f2c4d51 100644 --- a/apps/webapp/src/script/components/MessagesList/Message/MessageWrapper.tsx +++ b/apps/webapp/src/script/components/MessagesList/Message/MessageWrapper.tsx @@ -118,7 +118,7 @@ export const MessageWrapper = ({ messageId, attachments: [], }); - } else if (file) { + } else if (file !== undefined && firstAsset !== undefined) { await messageRepository.retryUploadFile(conversation, file, firstAsset.isImage(), message.id); } }; diff --git a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx index c3f6ce41017..e9cb1c7fd33 100644 --- a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx +++ b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx @@ -55,7 +55,7 @@ export const Preference = () => { const isCellsOptionEnabled = isCellsEnabledForEnvironment && isCellsEnabledForTeam; const defaultProtocol = isMLSEnabled - ? teamState.teamFeatures()?.mls?.config.defaultProtocol + ? (teamState.teamFeatures()?.mls?.config.defaultProtocol ?? CONVERSATION_PROTOCOL.PROTEUS) : CONVERSATION_PROTOCOL.PROTEUS; // Read receipts are temorarily disabled for MLS groups and channels until it is supported diff --git a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx index 01ea5f779bf..894026a6968 100644 --- a/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx +++ b/apps/webapp/src/script/components/calling/VideoControls/videoBackgroundPerformancePanel/videoBackgroundPerformancePanel.tsx @@ -117,7 +117,7 @@ const MetricsDisplay = ({capabilityInfo}: MetricsDisplayProps) => { const renderMetrics = useBackgroundEffectsStore(state => state.metrics); const model = useBackgroundEffectsStore(state => state.model); - const metricRows = getMetricRows(renderMetrics); + const metricRows = renderMetrics ? getMetricRows(renderMetrics) : []; const capabilityRows = getCapabilityRows(capabilityInfo); @@ -281,7 +281,7 @@ export const VideoBackgroundPerformancePanel = ({backgroundEffectsHandler}: Perf - + {capabilityInfo && } )} diff --git a/apps/webapp/src/script/components/panel/ServiceDetails.tsx b/apps/webapp/src/script/components/panel/ServiceDetails.tsx index dbb85db446e..17473fee077 100644 --- a/apps/webapp/src/script/components/panel/ServiceDetails.tsx +++ b/apps/webapp/src/script/components/panel/ServiceDetails.tsx @@ -54,7 +54,7 @@ const ServiceDetails = ({service}: ServiceDetailsProps) => { -

{t('serviceDetailsAuthor', {author})}

+

{t('serviceDetailsAuthor', {author: author ?? ''})}

diff --git a/apps/webapp/src/script/hooks/usePausableInterval.ts b/apps/webapp/src/script/hooks/usePausableInterval.ts index e74174b3cf8..6ef2d3db53f 100644 --- a/apps/webapp/src/script/hooks/usePausableInterval.ts +++ b/apps/webapp/src/script/hooks/usePausableInterval.ts @@ -36,7 +36,11 @@ export const usePausableInterval = (callback: () => void, timer: number) => { useEffect(() => { const fn = () => { - intervalIdRef.current(); + const intervalCallback = intervalIdRef.current; + if (intervalCallback === undefined) { + return; + } + intervalCallback(); }; if (timer !== null) { diff --git a/apps/webapp/src/script/hooks/useRootFontSize.ts b/apps/webapp/src/script/hooks/useRootFontSize.ts index 86f66be1765..0f4dcc17aa0 100644 --- a/apps/webapp/src/script/hooks/useRootFontSize.ts +++ b/apps/webapp/src/script/hooks/useRootFontSize.ts @@ -42,7 +42,7 @@ export const useRootFontSize = ( rootFontSize: RootFontSize = RootFontSize.M, ): [RootFontSize, React.Dispatch>] => { const [storedRootFontSize, setStoredRootFontSize] = useLocalStorage(ROOT_FONT_SIZE_KEY, rootFontSize); - const [currentRootFontSize, setCurrentRootFontSize] = useState(storedRootFontSize); + const [currentRootFontSize, setCurrentRootFontSize] = useState(storedRootFontSize ?? rootFontSize); useEffect(() => { setStoredRootFontSize(currentRootFontSize); @@ -55,6 +55,6 @@ export const useRootFontSize = ( export const useInitializeRootFontSize = () => { const [storedRootFontSize] = useLocalStorage(ROOT_FONT_SIZE_KEY, RootFontSize.M); useEffect(() => { - setFontSizeToRoot(storedRootFontSize); + setFontSizeToRoot(storedRootFontSize ?? RootFontSize.M); }, []); }; diff --git a/apps/webapp/src/script/legal-hold/LegalHoldEvaluator.ts b/apps/webapp/src/script/legal-hold/LegalHoldEvaluator.ts index 82b833e8ae3..c13158c0909 100644 --- a/apps/webapp/src/script/legal-hold/LegalHoldEvaluator.ts +++ b/apps/webapp/src/script/legal-hold/LegalHoldEvaluator.ts @@ -44,24 +44,27 @@ export const areSomeUsersOnLegalHold = (users: User[]): boolean => { }; export const isConversationOnLegalHold = (conversation: Conversation): boolean => { - const amIonLegalHold = isUserOnLegalHold(conversation.selfUser()); + const selfUser = conversation.selfUser(); + const amIonLegalHold = selfUser !== undefined ? isUserOnLegalHold(selfUser) : false; const areOthersOnLegalHold = areSomeUsersOnLegalHold(conversation.participating_user_ets()); return amIonLegalHold || areOthersOnLegalHold; }; export const hasMessageLegalHoldFlag = (mappedEvent: MappedEvent): boolean => { const supportsLegalHoldFlag = [CONVERSATION.MESSAGE_ADD].includes(mappedEvent.type as CONVERSATION); + const mappedEventData = mappedEvent.data; const hasLegalHoldFlag = - mappedEvent.data && - typeof mappedEvent.data !== 'string' && - typeof mappedEvent.data.legal_hold_status !== 'undefined' && - mappedEvent.data.legal_hold_status !== LegalHoldStatus.UNKNOWN; + mappedEventData !== undefined && + typeof mappedEventData !== 'string' && + typeof mappedEventData.legal_hold_status !== 'undefined' && + mappedEventData.legal_hold_status !== LegalHoldStatus.UNKNOWN; return supportsLegalHoldFlag && hasLegalHoldFlag; }; export const renderLegalHoldMessage = (mappedEvent: MappedEvent, localConversationState: LegalHoldStatus): boolean => { - if (hasMessageLegalHoldFlag(mappedEvent)) { - return typeof mappedEvent.data !== 'string' && mappedEvent.data.legal_hold_status !== localConversationState; + const mappedEventData = mappedEvent.data; + if (hasMessageLegalHoldFlag(mappedEvent) && mappedEventData !== undefined && typeof mappedEventData !== 'string') { + return mappedEventData.legal_hold_status !== localConversationState; } return false; }; diff --git a/apps/webapp/src/script/main/app.ts b/apps/webapp/src/script/main/app.ts index d3f5acb7ff7..f8365863cb5 100644 --- a/apps/webapp/src/script/main/app.ts +++ b/apps/webapp/src/script/main/app.ts @@ -763,6 +763,9 @@ export class App { } } + if (this.core.storage === undefined) { + throw new Error('Cannot initialize self user before core storage is available'); + } container.resolve(StorageService).init(this.core.storage); this.repository.client.init(selfUser); await this.repository.properties.init(selfUser); diff --git a/apps/webapp/src/script/message/QuoteEntity.ts b/apps/webapp/src/script/message/QuoteEntity.ts index 2c3fc5c6471..3c8727a8fdb 100644 --- a/apps/webapp/src/script/message/QuoteEntity.ts +++ b/apps/webapp/src/script/message/QuoteEntity.ts @@ -55,9 +55,10 @@ export class QuoteEntity { } toProto(): Quote { + const quotedMessageSha256 = this.hash !== undefined ? new Uint8Array(this.hash) : new Uint8Array(); return new Quote({ quotedMessageId: this.messageId, - quotedMessageSha256: new Uint8Array(this.hash), + quotedMessageSha256, }); } } diff --git a/apps/webapp/src/script/page/AppLock/AppLock.tsx b/apps/webapp/src/script/page/AppLock/AppLock.tsx index 2b1249bad41..bdf3911ce30 100644 --- a/apps/webapp/src/script/page/AppLock/AppLock.tsx +++ b/apps/webapp/src/script/page/AppLock/AppLock.tsx @@ -198,7 +198,11 @@ const AppLock = ({ const startScheduledTimeout = () => { if (isScheduledAppLockEnabled()) { window.clearTimeout(scheduledTimeoutId); - setScheduledTimeoutId(window.setTimeout(showAppLock, getScheduledAppLockTimeoutInSeconds() * 1000)); + const scheduledAppLockTimeoutInSeconds = getScheduledAppLockTimeoutInSeconds(); + if (scheduledAppLockTimeoutInSeconds === null) { + return; + } + setScheduledTimeoutId(window.setTimeout(showAppLock, scheduledAppLockTimeoutInSeconds * 1000)); } }; diff --git a/apps/webapp/src/script/page/LeftSidebar/panels/Conversations/Conversations.tsx b/apps/webapp/src/script/page/LeftSidebar/panels/Conversations/Conversations.tsx index 0f1c32515cb..26532e04d1e 100644 --- a/apps/webapp/src/script/page/LeftSidebar/panels/Conversations/Conversations.tsx +++ b/apps/webapp/src/script/page/LeftSidebar/panels/Conversations/Conversations.tsx @@ -145,10 +145,12 @@ export const Conversations = ({ const {labels} = useKoSubscribableChildren(conversationLabelRepository, ['labels']); const favoriteLabel = conversationLabelRepository.getFavoriteLabel(); - const favoriteConversations = useMemo( - () => conversationLabelRepository.getLabelConversations(favoriteLabel, conversations), - [conversationLabelRepository, conversations, favoriteLabel], - ); + const favoriteConversations = useMemo(() => { + if (favoriteLabel === undefined) { + return []; + } + return conversationLabelRepository.getLabelConversations(favoriteLabel, conversations); + }, [conversationLabelRepository, conversations, favoriteLabel]); const isPreferences = currentTab === SidebarTabs.PREFERENCES; const isCells = currentTab === SidebarTabs.CELLS; diff --git a/apps/webapp/src/script/page/MainContent/panels/Collection/utils.tsx b/apps/webapp/src/script/page/MainContent/panels/Collection/utils.tsx index e21188a89bd..d9e24746bd3 100644 --- a/apps/webapp/src/script/page/MainContent/panels/Collection/utils.tsx +++ b/apps/webapp/src/script/page/MainContent/panels/Collection/utils.tsx @@ -24,16 +24,20 @@ import {MessageCategory} from '../../../../message/MessageCategory'; export type Category = 'images' | 'links' | 'files' | 'audio'; export const isOfCategory = (category: Category, message: ContentMessage) => { + const messageCategory = message.category; + if (messageCategory === undefined) { + return false; + } switch (category) { case 'images': - return message.category & MessageCategory.IMAGE && !(message.category & MessageCategory.GIF); + return messageCategory & MessageCategory.IMAGE && !(messageCategory & MessageCategory.GIF); case 'links': - return message.category & MessageCategory.LINK_PREVIEW; + return messageCategory & MessageCategory.LINK_PREVIEW; case 'audio': - return message.category & MessageCategory.FILE && message.getFirstAsset()?.isAudio(); + return messageCategory & MessageCategory.FILE && message.getFirstAsset()?.isAudio(); case 'files': return ( - message.category & MessageCategory.FILE && + messageCategory & MessageCategory.FILE && (message.getFirstAsset()?.isFile() || message.getFirstAsset()?.isVideo()) ); default: diff --git a/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/SaveCallLogs.tsx b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/SaveCallLogs.tsx index 210a53e0b98..01cd15509e1 100644 --- a/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/SaveCallLogs.tsx +++ b/apps/webapp/src/script/page/MainContent/panels/preferences/avPreferences/SaveCallLogs.tsx @@ -42,10 +42,14 @@ const SaveCallLogs = ({callingRepository, userState = container.resolve(UserStat const brandName = Config.getConfig().BRAND_NAME; const saveCallLogs = () => { const messageLog = callingRepository.getCallLog(); - if (messageLog) { + if (messageLog !== undefined) { + const selfUser = userState.self(); + if (selfUser === undefined) { + return; + } const callLog = [messageLog.join('\r\n')]; const blob = new Blob(callLog, {type: 'text/plain;charset=utf-8'}); - const truncatedId = userState.self().id.slice(0, OBFUSCATION_TRUNCATE_TO); + const truncatedId = selfUser.id.slice(0, OBFUSCATION_TRUNCATE_TO); const sanitizedBrandName = brandName.replace(/[^A-Za-z0-9_]/g, ''); const filename = `${sanitizedBrandName}-${truncatedId}-Calling_${getCurrentDate()}.log`; diff --git a/apps/webapp/src/script/page/RightSidebar/conversationDetails/conversationDetails.tsx b/apps/webapp/src/script/page/RightSidebar/conversationDetails/conversationDetails.tsx index 8fa684fdb99..a13e2c8de60 100644 --- a/apps/webapp/src/script/page/RightSidebar/conversationDetails/conversationDetails.tsx +++ b/apps/webapp/src/script/page/RightSidebar/conversationDetails/conversationDetails.tsx @@ -224,6 +224,9 @@ const ConversationDetails = forwardRef const showService = async (entity: ServiceEntity) => { if (entity.isService) { const serviceEntity = await integrationRepository.getServiceFromUser(entity); + if (serviceEntity === undefined) { + return; + } togglePanel(PanelState.GROUP_PARTICIPANT_SERVICE, serviceEntity); return; } diff --git a/apps/webapp/src/script/page/RightSidebar/conversationParticipants/conversationParticipants.tsx b/apps/webapp/src/script/page/RightSidebar/conversationParticipants/conversationParticipants.tsx index 352c30451b4..e915063c24a 100644 --- a/apps/webapp/src/script/page/RightSidebar/conversationParticipants/conversationParticipants.tsx +++ b/apps/webapp/src/script/page/RightSidebar/conversationParticipants/conversationParticipants.tsx @@ -78,6 +78,10 @@ const ConversationParticipants: FC = ({ return users; }, [participatingUserEts, isSelfUserRemoved, selfUser]); + if (selfUser === undefined) { + return null; + } + return (
= ({ return user.id === serviceEntity.id; }); - const showActions = isActiveParticipant && serviceUser && inTeam; + const showActions = isActiveParticipant && serviceUser !== undefined && inTeam; const onOpen = () => { actionsViewModel.open1to1ConversationWithService(serviceEntity); @@ -99,7 +99,7 @@ const GroupParticipantService: FC = ({ const onAdd = () => { if (serviceEntity.isService) { integrationRepository.addServiceToExistingConversation(activeConversation, serviceEntity); - } else if (serviceEntity.qualifiedId) { + } else if (serviceEntity.qualifiedId !== undefined) { conversationRepository.addUsers(activeConversation, [{qualifiedId: serviceEntity.qualifiedId}]); } goToRoot(); @@ -111,8 +111,8 @@ const GroupParticipantService: FC = ({ useEffect(() => { // Set the author of the Service / App to the name of the team the user is in - if (!is.nullOrUndefined(selfUser.teamId)) { - teamRepository.getTeamNameById(selfUser.teamId).then(name => serviceEntity.author(name)); + if (!is.nullOrUndefined(selfUser.teamId) && serviceEntity.author !== undefined) { + teamRepository.getTeamNameById(selfUser.teamId).then(name => serviceEntity.author?.(name)); } }, [teamRepository, selfUser.teamId]); @@ -127,7 +127,7 @@ const GroupParticipantService: FC = ({
{showActions && ( <> - {canChatWithServices() && ( + {canChatWithServices?.() === true && (
= ({ tabIndex={TabIndex.FOCUSABLE} className="panel__action-item" data-uie-name="do-remove" - onClick={() => onRemove(serviceUser)} + onClick={() => { + if (serviceUser !== undefined) { + onRemove(serviceUser); + } + }} onKeyDown={event => handleKeyDown({ event, - callback: () => onRemove(serviceUser), + callback: () => { + if (serviceUser !== undefined) { + onRemove(serviceUser); + } + }, keys: [KEY.ENTER, KEY.SPACE], }) } diff --git a/apps/webapp/src/script/page/components/FeatureConfigChange/FeatureConfigChangeNotifier/FeatureConfigChangeNotifier.ts b/apps/webapp/src/script/page/components/FeatureConfigChange/FeatureConfigChangeNotifier/FeatureConfigChangeNotifier.ts index 19905a24eab..25eeae81d83 100644 --- a/apps/webapp/src/script/page/components/FeatureConfigChange/FeatureConfigChangeNotifier/FeatureConfigChangeNotifier.ts +++ b/apps/webapp/src/script/page/components/FeatureConfigChange/FeatureConfigChangeNotifier/FeatureConfigChangeNotifier.ts @@ -117,7 +117,7 @@ const featureNotifications: Record F status: FEATURE_STATUS | undefined, ) => undefined | {htmlMessage: string; title: Title; primaryAction?: ButtonAction} = status => { if (newConfig && 'config' in newConfig) { - localStorage.setItem('enforcedDownloadLocation', newConfig.config.enforcedDownloadLocation); + localStorage.setItem('enforcedDownloadLocation', newConfig.config.enforcedDownloadLocation ?? ''); amplify.publish( WebAppEvents.TEAM.DOWNLOAD_PATH_UPDATE, newConfig.status === FEATURE_STATUS.ENABLED ? newConfig.config.enforcedDownloadLocation : undefined, @@ -169,11 +169,11 @@ const featureNotifications: Record F if (!status) { return undefined; } - if (!configStatus) { + if (configStatus === undefined) { return undefined; } - localStorage.setItem('enforcedDownloadLocation', newConfig.config.enforcedDownloadLocation); + localStorage.setItem('enforcedDownloadLocation', newConfig.config.enforcedDownloadLocation ?? ''); return handleDlPathChange(status); } return undefined; diff --git a/apps/webapp/src/script/repositories/backup/backupRepository.ts b/apps/webapp/src/script/repositories/backup/backupRepository.ts index de0673a7659..1b8e900324f 100644 --- a/apps/webapp/src/script/repositories/backup/backupRepository.ts +++ b/apps/webapp/src/script/repositories/backup/backupRepository.ts @@ -162,7 +162,7 @@ export class BackupRepository { // encode header const backupCoder = new BackUpHeader(user.id, password); const backupHeader = await this.generateBackupHeader(user, password, backupCoder).catch((error: unknown) => { - throw new Error('Backup error:', error); + throw new Error('Backup error', {cause: error}); }); // Encrypt the ZIP archive using the provided password diff --git a/apps/webapp/src/script/repositories/backup/backupService.ts b/apps/webapp/src/script/repositories/backup/backupService.ts index f99ffdfe895..4b3775b5627 100644 --- a/apps/webapp/src/script/repositories/backup/backupService.ts +++ b/apps/webapp/src/script/repositories/backup/backupService.ts @@ -100,7 +100,10 @@ export class BackupService { } for (const entity of entities) { - const key = generatePrimaryKey ? generatePrimaryKey(entity) : undefined; + const key = generatePrimaryKey?.(entity) ?? generateId?.(entity); + if (key === undefined) { + throw new Error(`Cannot import entity into '${tableName}' without a primary key`); + } await this.storageService.save(tableName, key, entity); } return entities.length; diff --git a/apps/webapp/src/script/repositories/calling/Call.ts b/apps/webapp/src/script/repositories/calling/Call.ts index 457aa246dd0..1e9c800e95c 100644 --- a/apps/webapp/src/script/repositories/calling/Call.ts +++ b/apps/webapp/src/script/repositories/calling/Call.ts @@ -118,7 +118,8 @@ export class Call { } getSelfParticipant(): Participant { - return this.participants().find(({user, clientId}) => user.isMe && this.selfClientId === clientId); + const selfParticipant = this.participants().find(({user, clientId}) => user.isMe && this.selfClientId === clientId); + return selfParticipant ?? this.selfParticipant; } addAudio(audioId: string, stream: MediaStream) { @@ -202,6 +203,7 @@ export class Call { const activeSpeakers = uniqueAudioLevels // Get the participants. .map(({userId, clientId}) => this.getParticipant(userId, clientId)) + .filter((participant): participant is Participant => participant !== undefined) // Limit them to 4. .slice(0, 4) // Sort them by name @@ -244,7 +246,7 @@ export class Call { const [withScreenShare, withVideo] = partition(withVideoAndScreenShare, participant => participant.sharesScreen()); const newPages = chunk( - [selfParticipant, ...withScreenShare, ...withVideo, ...withoutVideo].filter(Boolean), + [selfParticipant, ...withScreenShare, ...withVideo, ...withoutVideo], this.numberOfParticipantsInOnePage, ); diff --git a/apps/webapp/src/script/repositories/calling/CallingRepository.ts b/apps/webapp/src/script/repositories/calling/CallingRepository.ts index 3308b1e0b07..a4971574598 100644 --- a/apps/webapp/src/script/repositories/calling/CallingRepository.ts +++ b/apps/webapp/src/script/repositories/calling/CallingRepository.ts @@ -381,16 +381,21 @@ export class CallingRepository { // let's check if background should be disabled, then let's do it and go back to the original video if (!this.backgroundEffectsHandler.isBackgroundEffectEnabled()) { + const originalVideoStream = selfParticipant.videoStream(); + if (originalVideoStream === undefined) { + return undefined; + } selfParticipant.releaseProcessedVideoStream(); if (hasActiveVideo && changeAvsSendingMediaSource) { // So let's switch back to the original video source this.logger.info('Disable background effects.'); - this.changeMediaSource(selfParticipant.videoStream(), MediaType.VIDEO, false); + this.changeMediaSource(originalVideoStream, MediaType.VIDEO, false); } - return selfParticipant.videoStream(); + return originalVideoStream; } - if (!hasActiveVideo) { + const videoStream = selfParticipant.videoStream(); + if (hasActiveVideo === false || videoStream === undefined) { // no Video nothing to change!! this.logger.warn('No video exists to apply apply background effects'); return; @@ -399,7 +404,7 @@ export class CallingRepository { // Hold a reference to the old stream so we can release it AFTER the new one is assigned, const previousStream = selfParticipant.processedVideoStream(); - const {applied, media} = await this.backgroundEffectsHandler.applyBackgroundEffect(selfParticipant.videoStream()); + const {applied, media} = await this.backgroundEffectsHandler.applyBackgroundEffect(videoStream); // The BackgroundEffectsHandler decide not to change the video stream, so we're going on with the original video. if (!applied) { @@ -407,9 +412,9 @@ export class CallingRepository { selfParticipant.processedVideoStream(undefined); if (changeAvsSendingMediaSource) { this.logger.info('Background effect could not applied! Switch back to original video stream!'); - this.changeMediaSource(selfParticipant.videoStream(), MediaType.VIDEO, false); + this.changeMediaSource(videoStream, MediaType.VIDEO, false); } - return selfParticipant.videoStream(); + return videoStream; } // Assign new stream first, then release old. diff --git a/apps/webapp/src/script/repositories/calling/videoGridHandler.ts b/apps/webapp/src/script/repositories/calling/videoGridHandler.ts index d6b2ed9a848..49116bf817f 100644 --- a/apps/webapp/src/script/repositories/calling/videoGridHandler.ts +++ b/apps/webapp/src/script/repositories/calling/videoGridHandler.ts @@ -43,13 +43,10 @@ export function getGrid(call: Call) { } export const useVideoGrid = (call: Call): Grid => { - const [grid, setGrid] = useState(); + const [grid, setGrid] = useState(() => getGrid(call)); const {participants, currentPage, pages} = useKoSubscribableChildren(call, ['participants', 'currentPage', 'pages']); useEffect(() => { - if (!call) { - return setGrid(undefined); - } const updateGrid = () => { call.updatePages(); setGrid(getGrid(call)); diff --git a/apps/webapp/src/script/repositories/client/ClientMapper.ts b/apps/webapp/src/script/repositories/client/ClientMapper.ts index 680ccedac57..f2cddc97674 100644 --- a/apps/webapp/src/script/repositories/client/ClientMapper.ts +++ b/apps/webapp/src/script/repositories/client/ClientMapper.ts @@ -55,10 +55,15 @@ export class ClientMapper { } if (isClientRecord(clientPayload)) { - const {userId} = parseClientId(clientPayload.meta.primary_key); + clientEntity.meta.isVerified?.(clientPayload.meta.is_verified === true); - clientEntity.meta.isVerified?.(!!clientPayload.meta.is_verified); - clientEntity.meta.primaryKey = clientPayload.meta.primary_key; + const primaryKey = clientPayload.meta.primary_key; + if (primaryKey === undefined) { + return clientEntity; + } + const {userId} = parseClientId(primaryKey); + + clientEntity.meta.primaryKey = primaryKey; clientEntity.meta.userId = userId; } @@ -79,7 +84,7 @@ export class ClientMapper { static mapClients( clientRecords: ClientRecord[] | PublicClient[] | RegisteredClient[], isSelfClient: boolean, - domain: string | null = null, + domain: string | undefined = undefined, ): ClientEntity[] { return clientRecords.map(clientRecord => ClientMapper.mapClient(clientRecord, isSelfClient, domain)); } @@ -102,7 +107,7 @@ export class ClientMapper { if (isDataChange) { containsUpdate = true; - clientData[member] = updatePayload[member]; + (clientData as unknown as Record)[member] = updatePayload[member]; } } diff --git a/apps/webapp/src/script/repositories/client/ClientRepository.ts b/apps/webapp/src/script/repositories/client/ClientRepository.ts index a74f9444bf2..a3f29f170f6 100644 --- a/apps/webapp/src/script/repositories/client/ClientRepository.ts +++ b/apps/webapp/src/script/repositories/client/ClientRepository.ts @@ -31,7 +31,7 @@ import {WebAppEvents} from '@wireapp/webapp-events'; import {PrimaryModal} from 'Components/Modals/PrimaryModal'; import type {CryptographyRepository} from 'Repositories/cryptography/CryptographyRepository'; -import type {User} from 'Repositories/entity/User'; +import {User} from 'Repositories/entity/User'; import {ClientRecord} from 'Repositories/storage'; import {StorageKey} from 'Repositories/storage/StorageKey'; import {t} from 'Util/localizerUtil'; @@ -76,7 +76,7 @@ export class ClientRepository { private readonly core = container.resolve(Core), ) { this.cryptographyRepository = cryptographyRepository; - this.selfUser = ko.observable(undefined); + this.selfUser = ko.observable(new User('', '')); this.logger = getLogger('ClientRepository'); amplify.subscribe(WebAppEvents.LIFECYCLE.ASK_TO_CLEAR_DATA, this.logoutClient); @@ -114,8 +114,12 @@ export class ClientRepository { const skippedUserIds = [this.selfUser().id, ClientRepository.PRIMARY_KEY_CURRENT_CLIENT]; for (const clientRecord of clientRecords) { - const {userId} = parseClientId(clientRecord.meta.primary_key); - if (userId && !skippedUserIds.includes(userId)) { + const primaryKey = clientRecord.meta.primary_key; + if (primaryKey === undefined) { + continue; + } + const {userId} = parseClientId(primaryKey); + if (userId !== undefined && userId !== '' && !skippedUserIds.includes(userId)) { recipients[userId] ||= []; recipients[userId].push(ClientMapper.mapClient(clientRecord, false, clientRecord.domain)); } @@ -187,7 +191,7 @@ export class ClientRepository { */ private updateClientInDb(primaryKey: string, changes: Partial): Promise { // Preserve primary key on update - changes.meta.primary_key = primaryKey; + changes.meta = {...changes.meta, primary_key: primaryKey}; return this.clientService.updateClientInDb(primaryKey, changes); } @@ -236,8 +240,11 @@ export class ClientRepository { * @param clientType Temporary or permanent client type * @returns Cookie label key */ - constructCookieLabelKey(login: string, clientType: ClientType = this.loadCurrentClientType()): string { - const loginHash = murmurhash.v3(login || this.selfUser().id, 42); + constructCookieLabelKey( + login: string, + clientType: ClientType = this.loadCurrentClientType() ?? ClientType.PERMANENT, + ): string { + const loginHash = murmurhash.v3(login !== '' ? login : this.selfUser().id, 42); return `${StorageKey.AUTH.COOKIE_LABEL}@${loginHash}@${clientType}`; } @@ -366,8 +373,12 @@ export class ClientRepository { private async getClientByUserIdFromDb(userQualifiedId: QualifiedId): Promise { const clients = await this.clientService.loadAllClientsFromDb(); return clients.filter(client => { - const {userId, domain} = parseClientId(client.meta.primary_key); - return matchQualifiedIds({domain, id: userId}, userQualifiedId); + const primaryKey = client.meta.primary_key; + if (primaryKey === undefined) { + return false; + } + const {userId, domain} = parseClientId(primaryKey); + return matchQualifiedIds({domain: domain ?? null, id: userId ?? ''}, userQualifiedId); }); } @@ -459,7 +470,9 @@ export class ClientRepository { // Locally known client changed on backend if (wasUpdated) { // Clear the previous client in DB (in case the domain changes the primary key will also change, thus invalidating the previous client) - await this.clientService.deleteClientFromDb(client.meta.primary_key); + if (client.meta.primary_key !== undefined) { + await this.clientService.deleteClientFromDb(client.meta.primary_key); + } this.logger.debug(`Updating local client`); promises.push(this.saveClientInDb(userId, client)); continue; @@ -563,11 +576,12 @@ export class ClientRepository { */ private async onClientRemove(eventJson: UserClientRemoveEvent, source: EventSource): Promise { const clientId = eventJson?.client ? eventJson.client.id : undefined; - if (!clientId) { + if (clientId === undefined || clientId === '') { return; } - const isCurrentClient = clientId === this.clientState.currentClient.id; + const currentClientId = this.clientState.currentClient?.id; + const isCurrentClient = currentClientId !== undefined && clientId === currentClientId; if (isCurrentClient) { // If the current client has been removed, we need to sign out amplify.publish(WebAppEvents.LIFECYCLE.SIGN_OUT, SIGN_OUT_REASON.CLIENT_REMOVED, true); diff --git a/apps/webapp/src/script/repositories/connection/connectionEntity.ts b/apps/webapp/src/script/repositories/connection/connectionEntity.ts index 228bedd774b..e422a328e71 100644 --- a/apps/webapp/src/script/repositories/connection/connectionEntity.ts +++ b/apps/webapp/src/script/repositories/connection/connectionEntity.ts @@ -39,12 +39,12 @@ export class ConnectionEntity { userId: QualifiedId; constructor() { - this.conversationId = null; - this.from = null; - this.lastUpdate = null; - this.message = null; + this.conversationId = {domain: '', id: ''}; + this.from = ''; + this.lastUpdate = ''; + this.message = ''; this.status = ko.observable(ConnectionStatus.UNKNOWN); - this.userId = null; + this.userId = {domain: '', id: ''}; this.isBlocked = ko.pureComputed(() => this.status() === ConnectionStatus.BLOCKED); this.isMissingLegalHoldConsent = ko.pureComputed( diff --git a/apps/webapp/src/script/repositories/connection/connectionMapper.ts b/apps/webapp/src/script/repositories/connection/connectionMapper.ts index 706ae734f10..22e4b1727ca 100644 --- a/apps/webapp/src/script/repositories/connection/connectionMapper.ts +++ b/apps/webapp/src/script/repositories/connection/connectionMapper.ts @@ -58,11 +58,11 @@ export class ConnectionMapper { } = connectionData; connectionEntity.status(status); - connectionEntity.conversationId = qualified_conversation || {domain: '', id: conversation}; - connectionEntity.userId = qualified_to || {domain: '', id: remoteUserId}; + connectionEntity.conversationId = qualified_conversation ?? {domain: '', id: conversation ?? ''}; + connectionEntity.userId = qualified_to ?? {domain: '', id: remoteUserId ?? ''}; connectionEntity.from = from; - connectionEntity.lastUpdate = last_update; - connectionEntity.message = message; + connectionEntity.lastUpdate = last_update ?? ''; + connectionEntity.message = message ?? ''; return connectionEntity; } diff --git a/apps/webapp/src/script/repositories/connection/connectionRepository.ts b/apps/webapp/src/script/repositories/connection/connectionRepository.ts index 9fcaafd3862..5920d7f33f1 100644 --- a/apps/webapp/src/script/repositories/connection/connectionRepository.ts +++ b/apps/webapp/src/script/repositories/connection/connectionRepository.ts @@ -435,7 +435,7 @@ export class ConnectionRepository { private async sendNotification( connectionEntity: ConnectionEntity, source: EventSource, - previousStatus: ConnectionStatus, + previousStatus: ConnectionStatus | undefined, ): Promise { // We accepted the connection request or unblocked the user const expectedPreviousStatus = [ @@ -443,7 +443,7 @@ export class ConnectionRepository { ConnectionStatus.BLOCKED, ConnectionStatus.PENDING, ]; - const wasExpectedPreviousStatus = expectedPreviousStatus.includes(previousStatus); + const wasExpectedPreviousStatus = previousStatus !== undefined && expectedPreviousStatus.includes(previousStatus); const selfUserAccepted = connectionEntity.isConnected() && wasExpectedPreviousStatus; const isWebSocketEvent = source === EventRepository.SOURCE.WEB_SOCKET; diff --git a/apps/webapp/src/script/repositories/conversation/ConversationAccessPermission.ts b/apps/webapp/src/script/repositories/conversation/ConversationAccessPermission.ts index cc1c9a14898..7d0107b476e 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationAccessPermission.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationAccessPermission.ts @@ -83,9 +83,13 @@ export function featureFromStateChange(prevState: ACCESS_STATE, current: ACCESS_ if (prevState === current) { return {feature: undefined, featureName: undefined, isAvailable: undefined, bitmask: 0}; } - const [featureName, featureBitmask] = Object.entries(ACCESS).find( + const featureEntry = Object.entries(ACCESS).find( ([, bitmask]) => bitmask & (teamPermissionsForAccessState(prevState) ^ teamPermissionsForAccessState(current)), ); + if (featureEntry === undefined) { + return {feature: undefined, featureName: undefined, isAvailable: undefined, bitmask: 0}; + } + const [featureName, featureBitmask] = featureEntry; const featString = CONVERSATION_ACCESS_ROLE[featureName as keyof typeof CONVERSATION_ACCESS_ROLE]; return { feature: featString, diff --git a/apps/webapp/src/script/repositories/conversation/ConversationCellState.ts b/apps/webapp/src/script/repositories/conversation/ConversationCellState.ts index d6d4643bd43..bc14a043622 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationCellState.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationCellState.ts @@ -190,6 +190,9 @@ const _getStateDefault = { const _getStateGroupActivity = { description: (conversationEntity: Conversation): string => { const lastMessageEntity = conversationEntity.getNewestMessage(); + if (lastMessageEntity === undefined) { + return ''; + } if (lastMessageEntity.isMember()) { const userCount = (lastMessageEntity as MemberMessage).userEntities().length; @@ -207,6 +210,9 @@ const _getStateGroupActivity = { } const [remoteUserEntity] = (lastMessageEntity as MemberMessage).remoteUserEntities(); + if (remoteUserEntity === undefined) { + return ''; + } const userSelfJoined = lastMessageEntity.user().id === remoteUserEntity.id; const string = userSelfJoined ? t('conversationsSecondaryLinePersonAddedSelf', {user: remoteUserEntity.name()}) @@ -224,7 +230,7 @@ const _getStateGroupActivity = { if (remoteUserEntity) { if ((lastMessageEntity as MemberMessage).isTeamMemberLeave()) { - const name = (lastMessageEntity as MemberMessage).name() || remoteUserEntity.name(); + const name = (lastMessageEntity as MemberMessage).name() ?? remoteUserEntity.name(); return t('conversationsSecondaryLinePersonRemovedTeam', {user: name}); } @@ -290,14 +296,16 @@ const _getStateMuted = { const _getStateRemoved = { description: (conversationEntity: Conversation) => { const lastMessageEntity = conversationEntity.getNewestMessage(); - const selfUserId = conversationEntity.selfUser().id; + const selfUser = conversationEntity.selfUser(); + if (selfUser === undefined) { + return ''; + } + const selfUserId = selfUser.id; const isMemberRemoval = lastMessageEntity && lastMessageEntity.isMember() && lastMessageEntity.isMemberRemoval(); const wasSelfRemoved = isMemberRemoval && - !!(lastMessageEntity as MemberMessage) - .userIds() - .find(userId => matchQualifiedIds(userId, conversationEntity.selfUser())); + !!(lastMessageEntity as MemberMessage).userIds().find(userId => matchQualifiedIds(userId, selfUser)); if (wasSelfRemoved) { const selfLeft = lastMessageEntity.user().id === selfUserId; return selfLeft ? t('conversationsSecondaryLineYouLeft') : t('conversationsSecondaryLineYouWereRemoved'); @@ -323,13 +331,16 @@ const _getStateUnreadMessage = { if (messageEntity.isPing()) { string = t('notificationPing'); } else if (messageEntity.isContent() && messageEntity.hasAssetText()) { - const assetText = messageEntity.getFirstAsset().text; - string = getRenderedTextContent(assetText); + const assetEntity = messageEntity.getFirstAsset(); + if (assetEntity !== undefined) { + string = getRenderedTextContent(assetEntity.text); + } } else if (messageEntity.isContent() && messageEntity.hasAsset()) { const assetEntity = messageEntity.getFirstAsset(); - const isUploaded = (assetEntity as FileAsset).status() === AssetTransferState.UPLOADED; + const isUploaded = + assetEntity !== undefined && (assetEntity as FileAsset).status() === AssetTransferState.UPLOADED; - if (isUploaded) { + if (isUploaded && assetEntity !== undefined) { if (assetEntity.isAudio()) { string = t('notificationSharedAudio'); } else if (assetEntity.isVideo()) { @@ -388,7 +399,7 @@ const _getStateUserName = { const lastMessageEntity = conversationEntity.getNewestMessage(); const isMemberJoin = lastMessageEntity && lastMessageEntity.isMember() && (lastMessageEntity as MemberMessage).isMemberJoin(); - const isEmpty1to1Conversation = conversationEntity.is1to1() && isMemberJoin; + const isEmpty1to1Conversation = conversationEntity.is1to1() && isMemberJoin === true; return conversationEntity.isRequest() || isEmpty1to1Conversation; }, diff --git a/apps/webapp/src/script/repositories/conversation/ConversationEphemeralHandler.ts b/apps/webapp/src/script/repositories/conversation/ConversationEphemeralHandler.ts index 1f9e250a755..31888a62edb 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationEphemeralHandler.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationEphemeralHandler.ts @@ -58,7 +58,7 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl }; } - static validateTimer(messageTimer: number | null): number { + static validateTimer(messageTimer: number | null): number | null { const TIMER_RANGE = ConversationEphemeralHandler.CONFIG.TIMER_RANGE; const isTimerReset = messageTimer === null; return isTimerReset ? messageTimer : clamp(messageTimer, TIMER_RANGE.MIN, TIMER_RANGE.MAX); @@ -82,8 +82,8 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl let updateIntervalId: number | null = null; this.timedMessagesSubscription = this.timedMessages.subscribe(messageEntities => { - const shouldClearInterval = messageEntities.length === 0 && updateIntervalId; - if (shouldClearInterval) { + const shouldClearInterval = messageEntities.length === 0; + if (shouldClearInterval && updateIntervalId !== null) { window.clearInterval(updateIntervalId); updateIntervalId = null; return this.logger.info('Cleared ephemeral message check interval'); @@ -129,7 +129,9 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl ephemeral_started: Number(messageEntity.ephemeral_started()), }; - this.eventService.updateEvent(messageEntity.primary_key, changes); + if (messageEntity.primary_key !== undefined) { + this.eventService.updateEvent(messageEntity.primary_key, changes); + } break; } } @@ -170,6 +172,9 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl messageEntity.ephemeral_expires(true); const assetEntity = messageEntity.getFirstAsset(); + if (assetEntity === undefined) { + return; + } const changes: Pick, 'data' | 'ephemeral_expires'> = { data: { content_type: assetEntity.file_type, @@ -178,7 +183,9 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl ephemeral_expires: true, }; - this.eventService.updateEvent(messageEntity.primary_key, changes); + if (messageEntity.primary_key !== undefined) { + this.eventService.updateEvent(messageEntity.primary_key, changes); + } this.logger.info(`Obfuscated asset message '${messageEntity.id}'`); } @@ -197,7 +204,9 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl ephemeral_expires: true, }; - this.eventService.updateEvent(messageEntity.primary_key, changes); + if (messageEntity.primary_key !== undefined) { + this.eventService.updateEvent(messageEntity.primary_key, changes); + } this.logger.info(`Obfuscated image message '${messageEntity.id}'`); } @@ -245,7 +254,9 @@ export class ConversationEphemeralHandler extends AbstractConversationEventHandl ephemeral_expires: true, }; - this.eventService.updateEvent(messageEntity.primary_key, changes); + if (messageEntity.primary_key !== undefined) { + this.eventService.updateEvent(messageEntity.primary_key, changes); + } this.logger.info(`Obfuscated text message '${messageEntity.id}'`); } diff --git a/apps/webapp/src/script/repositories/conversation/ConversationMapper.ts b/apps/webapp/src/script/repositories/conversation/ConversationMapper.ts index 5547905d453..2e63f643fbe 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationMapper.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationMapper.ts @@ -176,7 +176,7 @@ export class ConversationMapper { if (muted_timestamp) { conversationEntity.setTimestamp(muted_timestamp, Conversation.TIMESTAMP_TYPE.MUTED); - conversationEntity.mutedState(selfState.muted_state); + conversationEntity.mutedState(selfState.muted_state ?? NOTIFICATION_STATE.EVERYTHING); } if (status !== undefined) { @@ -199,13 +199,13 @@ export class ConversationMapper { const {otr_archived, otr_muted_status: mutedState} = selfState; if (otr_archived !== undefined) { - const archivedTimestamp = new Date(selfState.otr_archived_ref).getTime(); + const archivedTimestamp = new Date(selfState.otr_archived_ref ?? 0).getTime(); conversationEntity.setTimestamp(archivedTimestamp, Conversation.TIMESTAMP_TYPE.ARCHIVED); conversationEntity.archivedState(otr_archived); } if (mutedState !== undefined) { - const mutedTimestamp = new Date(selfState.otr_muted_ref).getTime(); + const mutedTimestamp = new Date(selfState.otr_muted_ref ?? 0).getTime(); conversationEntity.setTimestamp(mutedTimestamp, Conversation.TIMESTAMP_TYPE.MUTED); conversationEntity.mutedState(mutedState); } @@ -425,7 +425,7 @@ export class ConversationMapper { add_permission, cells_state, } = remoteConversationData; - const {others: othersStates, self: selfState} = members; + const {others: othersStates = [], self: selfState} = members; const updates: Partial = { accessModes: access, @@ -440,7 +440,7 @@ export class ConversationMapper { protocol, receipt_mode, roles: {}, - status: (selfState as any).status, + status: (selfState as Partial<{status: number}> | undefined)?.status, team_id: team, type, group_conv_type, @@ -448,7 +448,9 @@ export class ConversationMapper { cells_state, }; - const qualified_others = othersStates?.filter(other => !!other.qualified_id).map(({qualified_id}) => qualified_id); + const qualified_others = othersStates + .map(({qualified_id}) => qualified_id) + .filter((qualifiedId): qualifiedId is QualifiedId => qualifiedId !== undefined); if (qualified_others.length) { updates.qualified_others = qualified_others; @@ -459,16 +461,18 @@ export class ConversationMapper { } // Add roles for self - if (selfState.conversation_role && !(selfState.id in updates.roles)) { - updates.roles[selfState.id] = selfState.conversation_role; + const roles = updates.roles ?? {}; + if (selfState?.conversation_role !== undefined && selfState.id !== undefined && !(selfState.id in roles)) { + roles[selfState.id] = selfState.conversation_role; } // Add roles for others othersStates.map(other => { - if (other.conversation_role && !(other.conversation_role in updates.roles)) { - updates.roles[other.id] = other.conversation_role; + if (other.conversation_role !== undefined && other.id !== undefined && !(other.conversation_role in roles)) { + roles[other.id] = other.conversation_role; } }); + updates.roles = roles; const mergedConversation: ConversationDatabaseData = {...localConversationData, ...updates}; @@ -498,20 +502,20 @@ export class ConversationMapper { // Some archived timestamp were not properly stored in the database. // To fix this we check if the remote one is newer and update our local timestamp. const {archived_state: archivedState, archived_timestamp: archivedTimestamp} = localConversationData; - const remoteArchivedTimestamp = new Date(selfState.otr_archived_ref).getTime(); + const remoteArchivedTimestamp = new Date(selfState?.otr_archived_ref ?? 0).getTime(); const isRemoteArchivedTimestampNewer = isRemoteTimestampNewer(archivedTimestamp, remoteArchivedTimestamp); if (isRemoteArchivedTimestampNewer || archivedState === undefined) { - mergedConversation.archived_state = selfState.otr_archived; + mergedConversation.archived_state = selfState?.otr_archived ?? false; mergedConversation.archived_timestamp = remoteArchivedTimestamp; } const {muted_state: mutedState, muted_timestamp: mutedTimestamp} = localConversationData; - const remoteMutedTimestamp = new Date(selfState.otr_muted_ref).getTime(); + const remoteMutedTimestamp = new Date(selfState?.otr_muted_ref ?? 0).getTime(); const isRemoteMutedTimestampNewer = isRemoteTimestampNewer(mutedTimestamp, remoteMutedTimestamp); if (isRemoteMutedTimestampNewer || mutedState === undefined) { - const remoteMutedState = selfState.otr_muted_status; + const remoteMutedState = selfState?.otr_muted_status ?? NOTIFICATION_STATE.EVERYTHING; mergedConversation.muted_state = remoteMutedState; mergedConversation.muted_timestamp = remoteMutedTimestamp; } diff --git a/apps/webapp/src/script/repositories/conversation/ConversationRepository.ts b/apps/webapp/src/script/repositories/conversation/ConversationRepository.ts index 9da3e857081..3dbab81d754 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationRepository.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationRepository.ts @@ -117,7 +117,7 @@ import {updateAccessRights} from './ConversationAccessPermission'; import {ConversationEphemeralHandler} from './ConversationEphemeralHandler'; import {ConversationFilter} from './ConversationFilter'; import {ConversationLabelRepository} from './ConversationLabelRepository'; -import {ConversationDatabaseData, ConversationMapper} from './ConversationMapper'; +import {ConversationDatabaseData, ConversationMapper, SelfStatusUpdateDatabaseData} from './ConversationMapper'; import {ConversationRoleRepository} from './ConversationRoleRepository'; import { isBackendProteus1to1Conversation, @@ -166,6 +166,7 @@ import {BASE_ERROR_TYPE, BaseError} from '../../error/BaseError'; import {ConversationError} from '../../error/ConversationError'; import {isMemberMessage} from '../../guards/Message'; import * as LegalHoldEvaluator from '../../legal-hold/LegalHoldEvaluator'; +import type {MappedEvent} from '../../legal-hold/LegalHoldEvaluator'; import {MessageCategory} from '../../message/MessageCategory'; import {SystemMessageType} from '../../message/SystemMessageType'; import {initMLSGroupConversation} from '../../mls'; @@ -253,13 +254,14 @@ export class ConversationRepository { const {missingClients, deletedClients, emptyUsers, missingUserIds} = extractClientDiff( filteredMismatch, - conversation?.allUserEntities(), + conversation?.allUserEntities() ?? [], domain, ); + const mismatchTimestamp = mismatch.time !== undefined ? new Date(mismatch.time).getTime() : Date.now(); if (conversation && missingUserIds.length) { // add/remove users from the conversation (if any) - await this.addMissingMember(conversation, missingUserIds, new Date(mismatch.time).getTime() - 1); + await this.addMissingMember(conversation, missingUserIds, mismatchTimestamp - 1); } // Remove clients that are not needed anymore @@ -276,16 +278,20 @@ export class ConversationRepository { await Promise.all( usersWithoutClients .filter(user => user.deleted) - .map(user => - this.teamMemberLeave( - this.teamState.team().id, + .map(user => { + const teamId = this.teamState.team().id; + if (teamId === undefined) { + throw new Error('Cannot remove team member without a team ID'); + } + return this.teamMemberLeave( + teamId, { domain: this.teamState.teamDomain(), id: user.id, }, - new Date(mismatch.time).getTime() - 1, - ), - ), + mismatchTimestamp - 1, + ); + }), ); } @@ -414,6 +420,12 @@ export class ConversationRepository { }: ConversationDBChange): Promise => { const qualifiedId = updatedEvent.qualified_conversation || {domain: '', id: updatedEvent.conversation}; const conversationEntity = this.conversationState.findConversation(qualifiedId); + if (conversationEntity === undefined) { + return; + } + if (oldEvent.id === undefined) { + return; + } const replacedMessageEntity = await this.replaceMessageInConversation( conversationEntity, oldEvent.id, @@ -428,7 +440,7 @@ export class ConversationRepository { private readonly deleteLocalMessageEntity = ({oldObj: deletedEvent}: ConversationDBChange): void => { const qualifiedId = deletedEvent.qualified_conversation || {domain: '', id: deletedEvent.conversation}; const conversationEntity = this.conversationState.findConversation(qualifiedId); - if (conversationEntity) { + if (conversationEntity && deletedEvent.id !== undefined) { conversationEntity.removeMessageById(deletedEvent.id); } }; @@ -493,10 +505,14 @@ export class ConversationRepository { */ let response: BaseCreateConversationResponse; const isMLSConversation = payload.protocol === CONVERSATION_PROTOCOL.MLS; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Cannot create conversation before self user is available'); + } if (isMLSConversation) { response = await this.core.service!.conversation.createMLSConversation( payload, - this.userState.self().qualifiedId, + selfUser.qualifiedId, this.core.clientId, ); } else { @@ -512,7 +528,7 @@ export class ConversationRepository { receipt_mode: undefined, ...response.conversation, }, - from: this.userState.self().id, + from: selfUser.id, qualified_conversation: response.conversation.qualified_id, time: new Date().toISOString(), type: CONVERSATION_EVENT.CREATE, @@ -529,7 +545,7 @@ export class ConversationRepository { const failedToAddUsersEvent = EventBuilder.buildFailedToAddUsersEvent( failedToAdd, conversationEntity, - this.userState.self().id, + selfUser.id, ); await this.eventRepository.injectEvent(failedToAddUsersEvent); } @@ -977,7 +993,8 @@ export class ConversationRepository { } const addCreationMessage = !conversationEntity.hasCreationMessage; if (addCreationMessage) { - await this.addCreationMessage(conversationEntity, this.userState.self().isTemporaryGuest()); + const selfUser = this.userState.self(); + await this.addCreationMessage(conversationEntity, selfUser?.isTemporaryGuest() ?? false); } } @@ -1241,7 +1258,11 @@ export class ConversationRepository { public async getAllUsersInConversation(conversationId: QualifiedId): Promise { const conversationEntity = await this.getConversationById(conversationId); - const users = [this.userState.self()].concat(conversationEntity.participating_user_ets()); + const selfUser = this.userState.self(); + const users = + selfUser !== undefined + ? [selfUser].concat(conversationEntity.participating_user_ets()) + : conversationEntity.participating_user_ets(); return users; } @@ -2658,8 +2679,12 @@ export class ConversationRepository { await this.eventRepository.injectEvent(memberJoinEvent, EventRepository.SOURCE.BACKEND_RESPONSE); } if (failedToAdd && failedToAdd.length) { + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Cannot build failed-to-add-users event before self user is available'); + } await this.eventRepository.injectEvent( - EventBuilder.buildFailedToAddUsersEvent(failedToAdd, conversation, this.userState.self().id), + EventBuilder.buildFailedToAddUsersEvent(failedToAdd, conversation, selfUser.id), EventRepository.SOURCE.INJECTED, ); } @@ -2674,8 +2699,12 @@ export class ConversationRepository { if (isMLSConversation(conversation)) { if (failedToAdd && failedToAdd.length) { + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Cannot build failed-to-add-users event before self user is available'); + } await this.eventRepository.injectEvent( - EventBuilder.buildFailedToAddUsersEvent(failedToAdd, conversation, this.userState.self().id), + EventBuilder.buildFailedToAddUsersEvent(failedToAdd, conversation, selfUser.id), EventRepository.SOURCE.INJECTED, ); } @@ -2857,10 +2886,11 @@ export class ConversationRepository { } async leaveGuestRoom(): Promise { - if (this.userState.self().isTemporaryGuest()) { + const selfUser = this.userState.self(); + if (selfUser !== undefined && selfUser.isTemporaryGuest()) { const conversation = this.conversationState.getMostRecentConversation(true); if (conversation) { - await this.conversationService.deleteMembers(conversation.qualifiedId, this.userState.self().qualifiedId); + await this.conversationService.deleteMembers(conversation.qualifiedId, selfUser.qualifiedId); } } } @@ -2910,7 +2940,11 @@ export class ConversationRepository { * @returns Resolves when the self user was removed from the conversation */ public async leaveConversation(conversation: Conversation) { - const userQualifiedId = this.userState.self().qualifiedId; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Cannot leave conversation before self user is available'); + } + const userQualifiedId = selfUser.qualifiedId; const events = await this.removeMembersFromConversation(conversation, [userQualifiedId]); await this.eventRepository.injectEvents(events, EventRepository.SOURCE.BACKEND_RESPONSE); @@ -2945,9 +2979,13 @@ export class ConversationRepository { // TODO: Can this even have a response? in the API Client it look like it always returns `void` const hasResponse = response?.event; const currentTimestamp = this.serverTimeHandler.toServerTimestamp(); + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Cannot build member-leave event before self user is available'); + } const event = hasResponse ? response.event - : EventBuilder.buildMemberLeave(conversationEntity, [user], this.userState.self().id, currentTimestamp); + : EventBuilder.buildMemberLeave(conversationEntity, [user], selfUser.id, currentTimestamp); this.eventRepository.injectEvent(event, EventRepository.SOURCE.BACKEND_RESPONSE); return event; @@ -3059,6 +3097,9 @@ export class ConversationRepository { messageTimer: number | null, ): Promise { messageTimer = ConversationEphemeralHandler.validateTimer(messageTimer); + if (messageTimer === null) { + messageTimer = 0; + } const response = await this.conversationService.updateConversationMessageTimer( conversationEntity.qualifiedId, @@ -3169,7 +3210,12 @@ export class ConversationRepository { try { await this.conversationService.updateMemberProperties(conversationEntity.qualifiedId, payload); - const response = {data: payload, from: this.userState.self().id}; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const response = {data: payload, from: selfUser.id}; this.onMemberUpdate(conversationEntity, response); const {otr_muted_ref: mutedRef, otr_muted_status: mutedStatus} = payload; @@ -3208,11 +3254,25 @@ export class ConversationRepository { } public async sendTypingStart(conversationEntity: Conversation) { - this.core.service!.conversation.sendTypingStart(conversationEntity.qualifiedId); + const coreService = this.core.service; + if (coreService === undefined) { + throw new ConversationError( + BaseError.TYPE.MISSING_PARAMETER as BASE_ERROR_TYPE, + BaseError.MESSAGE.MISSING_PARAMETER, + ); + } + coreService.conversation.sendTypingStart(conversationEntity.qualifiedId); } public async sendTypingStop(conversationEntity: Conversation) { - this.core.service!.conversation.sendTypingStop(conversationEntity.qualifiedId); + const coreService = this.core.service; + if (coreService === undefined) { + throw new ConversationError( + BaseError.TYPE.MISSING_PARAMETER as BASE_ERROR_TYPE, + BaseError.MESSAGE.MISSING_PARAMETER, + ); + } + coreService.conversation.sendTypingStop(conversationEntity.qualifiedId); } private async toggleArchiveConversation( @@ -3260,9 +3320,14 @@ export class ConversationRepository { }); await updatePromise; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + const response = { data: payload, - from: this.userState.self().id, + from: selfUser.id, }; this.onMemberUpdate(conversationEntity, response); } @@ -3340,12 +3405,18 @@ export class ConversationRepository { if (typeof legalHoldStatus === 'undefined') { return; } - if (!timestamp) { - const conversation = conversationEntity || this.conversationState.findConversation(conversationId); + if (timestamp === undefined) { + const conversation = conversationEntity ?? this.conversationState.findConversation(conversationId); + if (conversation === undefined) { + throw new ConversationError( + ConversationError.TYPE.CONVERSATION_NOT_FOUND, + ConversationError.MESSAGE.CONVERSATION_NOT_FOUND, + ); + } timestamp = conversation.getNextTimestamp(); } const legalHoldUpdateMessage = EventBuilder.buildLegalHoldMessage( - conversationId || conversationEntity?.qualifiedId, + conversationId, userId, timestamp, legalHoldStatus, @@ -3434,11 +3505,12 @@ export class ConversationRepository { } const {conversation, qualified_conversation, data: eventData, type} = eventJson; - const dataConversationId: string = (eventData as any)?.conversationId; + const dataConversationId: string | undefined = (eventData as any)?.conversationId; // data.conversationId is always the conversationId that should be read first. If not found we can fallback to qualified_conversation or conversation - const conversationId: QualifiedId = dataConversationId - ? {domain: '', id: dataConversationId} - : qualified_conversation || {domain: '', id: conversation}; + const conversationId: QualifiedId = + dataConversationId !== undefined + ? {domain: '', id: dataConversationId} + : (qualified_conversation ?? {domain: '', id: conversation}); const inSelfConversation = this.conversationState.isSelfConversation(conversationId); if (inSelfConversation) { @@ -3463,8 +3535,8 @@ export class ConversationRepository { : this.getConversationById(conversationId, true); return onEventPromise - .then((conversationEntity: Conversation) => { - if (conversationEntity) { + .then((conversationEntity: Conversation | null) => { + if (conversationEntity !== null) { const isBackendTimestamp = eventSource !== EventSource.INJECTED; const eventsToSkip: (CLIENT_CONVERSATION_EVENT | CONVERSATION_EVENT)[] = [ @@ -3476,7 +3548,7 @@ export class ConversationRepository { const shouldUpdateTimestampServer = !eventsToSkip.includes(type); if (shouldUpdateTimestampServer) { - conversationEntity.updateTimestampServer(eventJson.server_time || eventJson.time, isBackendTimestamp); + conversationEntity.updateTimestampServer(eventJson.server_time ?? eventJson.time, isBackendTimestamp); } } return conversationEntity; @@ -3512,20 +3584,25 @@ export class ConversationRepository { * @returns Resolves when the participant list has been checked */ private checkConversationParticipants( - conversationEntity: Conversation, + conversationEntity: Conversation | null, eventJson: IncomingEvent, eventSource: EventSource, ) { // We ignore injected events const isInjectedEvent = eventSource === EventRepository.SOURCE.INJECTED; - if (isInjectedEvent || !conversationEntity) { + if (isInjectedEvent || conversationEntity === null) { return conversationEntity; } const {from: senderId, type, time} = eventJson; - if (senderId) { - const allParticipants = conversationEntity.participating_user_ids().concat(this.userState.self().qualifiedId); + if (senderId !== undefined) { + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const allParticipants = conversationEntity.participating_user_ids().concat(selfUser.qualifiedId); const isFromUnknownUser = allParticipants.every(participant => participant.id !== senderId); if (isFromUnknownUser) { @@ -3553,13 +3630,21 @@ export class ConversationRepository { return conversationEntity; } - private async checkLegalHoldStatus(conversationEntity: Conversation, eventJson: IncomingEvent) { - if (!LegalHoldEvaluator.hasMessageLegalHoldFlag(eventJson)) { + private async checkLegalHoldStatus(conversationEntity: Conversation | null, eventJson: IncomingEvent) { + const mappedEvent = eventJson as MappedEvent; + if (!LegalHoldEvaluator.hasMessageLegalHoldFlag(mappedEvent)) { return conversationEntity; } + if (conversationEntity === null) { + throw new ConversationError( + ConversationError.TYPE.CONVERSATION_NOT_FOUND, + ConversationError.MESSAGE.CONVERSATION_NOT_FOUND, + ); + } + const renderLegalHoldMessage = LegalHoldEvaluator.renderLegalHoldMessage( - eventJson, + mappedEvent, conversationEntity.legalHoldStatus(), ); @@ -3576,8 +3661,8 @@ export class ConversationRepository { time: isoTimestamp, } = eventJson as any; const timestamp = new Date(isoTimestamp).getTime(); - const qualifiedConversation = qualified_conversation || {domain: '', id: conversationId}; - const qualifiedUser = qualified_from || {domain: '', id: userId}; + const qualifiedConversation = qualified_conversation ?? {domain: '', id: conversationId}; + const qualifiedUser = qualified_from ?? {domain: '', id: userId}; await this.injectLegalHoldMessage({ beforeTimestamp: true, @@ -3612,14 +3697,23 @@ export class ConversationRepository { * @returns Resolves when the event has been treated */ private async reactToConversationEvent( - conversationEntity: Conversation, + conversationEntity: Conversation | null, eventJson: IncomingEvent, eventSource: EventSource, ) { switch (eventJson.type) { case CONVERSATION_EVENT.CREATE: return this.onCreate(eventJson, eventSource); + } + if (conversationEntity === null) { + throw new ConversationError( + ConversationError.TYPE.CONVERSATION_NOT_FOUND, + ConversationError.MESSAGE.CONVERSATION_NOT_FOUND, + ); + } + + switch (eventJson.type) { case CONVERSATION_EVENT.DELETE: return this.deleteConversationLocally({domain: conversationEntity.domain, id: eventJson.conversation}, false); @@ -3632,11 +3726,16 @@ export class ConversationRepository { return Promise.all( deletedUsers.map(async qualifiedUserId => { const user = this.userState.users().find(user => matchQualifiedIds(user.qualifiedId, qualifiedUserId)); - return !user?.teamId - ? // If we are in the team, we display the team member removed from the team message - this.onMemberLeave(conversationEntity, eventJson) - : // in case we are not in a team, we just display the message that says a user left the conversation - this.teamMemberLeave(user.teamId, user.qualifiedId, new Date(eventJson.time).getTime()); + if (user === undefined) { + return this.onMemberLeave(conversationEntity, eventJson); + } + + const userTeamId = user.teamId; + const userHasNoTeamId = userTeamId === undefined || userTeamId === ''; + if (userHasNoTeamId) { + return this.onMemberLeave(conversationEntity, eventJson); + } + return this.teamMemberLeave(userTeamId, user.qualifiedId, new Date(eventJson.time).getTime()); }), ); } @@ -3734,7 +3833,11 @@ export class ConversationRepository { * @param eventSource Source of event * @returns Resolves when all the handlers have done their job */ - private async triggerFeatureEventHandlers(conversationEntity: Conversation, eventJson: IncomingEvent) { + private async triggerFeatureEventHandlers(conversationEntity: Conversation | null, eventJson: IncomingEvent) { + if (conversationEntity === null) { + return conversationEntity; + } + const conversationEventHandlers = [this.ephemeralHandler, this.stateHandler]; const handlePromises = conversationEventHandlers.map(handler => handler.handleConversationEvent(conversationEntity, eventJson), @@ -3844,18 +3947,24 @@ export class ConversationRepository { await this.eventRepository.injectEvents(events, EventRepository.SOURCE.BACKEND_RESPONSE); }; - private on1to1Creation(conversationEntity: Conversation, eventJson: OneToOneCreationEvent) { + private async on1to1Creation(conversationEntity: Conversation, eventJson: OneToOneCreationEvent) { const message = this.event_mapper.mapJsonEvent(eventJson, conversationEntity); - return this.updateMessageUserEntities(message).then((messageEntity: MemberMessage) => { - const userEntity = messageEntity.otherUser(); - const isOutgoingRequest = userEntity?.isOutgoingRequest(); - if (isOutgoingRequest) { - messageEntity.memberMessageType = SystemMessageType.CONNECTION_REQUEST; - } + if (message === undefined) { + throw new ConversationError( + ConversationError.TYPE.MESSAGE_NOT_FOUND, + ConversationError.MESSAGE.MESSAGE_NOT_FOUND, + ); + } - conversationEntity.addMessage(messageEntity); - return {conversationEntity}; - }); + const messageEntity = (await this.updateMessageUserEntities(message)) as MemberMessage; + const userEntity = messageEntity.otherUser(); + const isOutgoingRequest = userEntity?.isOutgoingRequest() === true; + if (isOutgoingRequest) { + messageEntity.memberMessageType = SystemMessageType.CONNECTION_REQUEST; + } + + conversationEntity.addMessage(messageEntity); + return {conversationEntity}; } /** @@ -3904,6 +4013,12 @@ export class ConversationRepository { private async onGroupCreation(conversationEntity: Conversation, eventJson: GroupCreationEvent) { const messageEntity = this.event_mapper.mapJsonEvent(eventJson, conversationEntity); + if (messageEntity === undefined) { + throw new ConversationError( + ConversationError.TYPE.MESSAGE_NOT_FOUND, + ConversationError.MESSAGE.MESSAGE_NOT_FOUND, + ); + } const creatorId = conversationEntity.creator; const creatorDomain = conversationEntity.domain; const createdByParticipant = !!conversationEntity @@ -3968,26 +4083,31 @@ export class ConversationRepository { } const eventData = eventJson.data; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } - if (eventData.users) { + if (eventData.users !== undefined) { eventData.users.forEach(otherMember => { - const otherId = otherMember.qualified_id || {domain: '', id: otherMember.id}; - const isSelfUser = matchQualifiedIds(otherId, this.userState.self()); - const isParticipatingUser = !!conversationEntity + const otherId = otherMember.qualified_id ?? {domain: '', id: otherMember.id}; + const isSelfUser = matchQualifiedIds(otherId, selfUser); + const isParticipatingUser = conversationEntity .participating_user_ids() - .find(participatingUser => - matchQualifiedIds(participatingUser, otherMember.qualified_id || {domain: '', id: otherMember.id}), + .some(participatingUser => + matchQualifiedIds(participatingUser, otherMember.qualified_id ?? {domain: '', id: otherMember.id}), ); if (!isSelfUser && !isParticipatingUser) { conversationEntity.participating_user_ids.push({ - domain: otherMember.qualified_id?.domain || null, + domain: otherMember.qualified_id?.domain ?? '', id: otherMember.id, }); } }); } else { - eventData.user_ids.forEach(userId => { - const isSelfUser = userId === this.userState.self().id; + const eventUserIds = eventData.user_ids ?? []; + eventUserIds.forEach(userId => { + const isSelfUser = userId === selfUser.id; const isParticipatingUser = conversationEntity.participating_user_ids().some(user => user.id === userId); if (!isSelfUser && !isParticipatingUser) { conversationEntity.participating_user_ids.push({domain: '', id: userId}); @@ -4004,10 +4124,11 @@ export class ConversationRepository { } } - const containsSelfId = eventData.user_ids.includes(this.userState.self().id); - const containsSelfQualifiedId = !!eventData.users?.some( - ({qualified_id: qualifiedId}) => qualifiedId && matchQualifiedIds(qualifiedId, this.userState.self().qualifiedId), - ); + const containsSelfId = (eventData.user_ids ?? []).includes(selfUser.id); + const containsSelfQualifiedId = + eventData.users?.some(({qualified_id: qualifiedId}) => { + return qualifiedId !== undefined && matchQualifiedIds(qualifiedId, selfUser.qualifiedId); + }) ?? false; const selfUserJoins = containsSelfId || containsSelfQualifiedId; @@ -4022,7 +4143,15 @@ export class ConversationRepository { : Promise.resolve(); const qualifiedUserIds = - eventData.users?.map(user => user.qualified_id) || eventData.user_ids.map(userId => ({domain: '', id: userId})); + eventData.users !== undefined + ? eventData.users + .map(user => user.qualified_id) + .filter((qualifiedId): qualifiedId is QualifiedId => { + return qualifiedId !== undefined; + }) + : (eventData.user_ids ?? []).map(userId => { + return {domain: '', id: userId}; + }); return updateSequence .then(() => this.updateParticipatingUserEntities(conversationEntity, false, true)) @@ -4045,7 +4174,12 @@ export class ConversationRepository { eventJson: ConversationMemberLeaveEvent | TeamMemberLeaveEvent | MemberLeaveEvent, ): Promise<{conversationEntity: Conversation; messageEntity: Message} | undefined> { const {data: eventData} = eventJson; - const removesSelfUser = eventData.user_ids.includes(this.userState.self().id); + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const removesSelfUser = eventData.user_ids.includes(selfUser.id); if (removesSelfUser) { conversationEntity.status(ConversationStatus.PAST_MEMBER); @@ -4054,8 +4188,8 @@ export class ConversationRepository { LEAVE_CALL_REASON.USER_IS_REMOVED_FROM_CONVERSATION, ); - if (this.userState.self().isTemporaryGuest()) { - eventJson.from = this.userState.self().id; + if (selfUser.isTemporaryGuest()) { + eventJson.from = selfUser.id; } if (isMLSCapableConversation(conversationEntity)) { @@ -4095,16 +4229,16 @@ export class ConversationRepository { eventJson: Pick & {conversation?: string}, ) { const {conversation, data: eventData, from} = eventJson; - const conversationId = {domain: '', id: conversation || '' /* TODO(federation) add domain on the sender side */}; + const conversationId = {domain: '', id: conversation ?? '' /* TODO(federation) add domain on the sender side */}; - const isConversationRoleUpdate = !!eventData.conversation_role; + const isConversationRoleUpdate = eventData.conversation_role !== undefined; if (isConversationRoleUpdate) { const {target, qualified_target, conversation_role} = eventData; - const userId = qualified_target || {domain: '', id: target}; + const userId = qualified_target ?? {domain: '', id: target}; const conversation = this.conversationState .conversations() .find(conversation => matchQualifiedIds(conversation, conversationId)); - if (conversation) { + if (conversation !== undefined && userId.id !== undefined && conversation_role !== undefined) { const roles = conversation.roles(); roles[userId.id] = conversation_role; conversation.roles(roles); @@ -4112,16 +4246,17 @@ export class ConversationRepository { return; } - const isBackendEvent = eventData.otr_archived_ref || eventData.otr_muted_ref; + const isBackendEvent = eventData.otr_archived_ref !== undefined || eventData.otr_muted_ref !== undefined; const inSelfConversation = this.conversationState.isSelfConversation(conversationId); - if (!inSelfConversation && conversation && !isBackendEvent) { + if (!inSelfConversation && conversation !== undefined && !isBackendEvent) { this.logger.warn( `A conversation update message was not sent in the selfConversation. Skipping conversation update`, ); return; } - const isFromSelf = !this.userState.self() || from === this.userState.self().id; + const selfUser = this.userState.self(); + const isFromSelf = selfUser === undefined || from === selfUser.id; if (!isFromSelf) { this.logger.warn(`A conversation update message was not sent by the self user. Skipping conversation update`); return; @@ -4131,7 +4266,7 @@ export class ConversationRepository { const nextConversationEntity = isActiveConversation ? this.getNextConversation(conversationEntity) : undefined; const previouslyArchived = conversationEntity.is_archived(); - ConversationMapper.updateSelfStatus(conversationEntity, eventData); + ConversationMapper.updateSelfStatus(conversationEntity, eventData as Partial); const wasUnarchived = previouslyArchived && !conversationEntity.is_archived(); if (wasUnarchived) { @@ -4155,7 +4290,12 @@ export class ConversationRepository { * @returns Resolves when the event was handled */ private async onAssetAdd(conversationEntity: Conversation, event: AssetAddEvent) { - const fromSelf = event.from === this.userState.self().id; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const fromSelf = event.from === selfUser.id; const isRemoteFailure = !fromSelf && event.data.status === AssetTransferState.UPLOAD_FAILED; const isLocalCancel = fromSelf && event.data.reason === ProtobufAsset.NotUploaded.CANCELLED; @@ -4174,7 +4314,7 @@ export class ConversationRepository { } if (!allowsAllFiles()) { - const fileName = event.data.info.name; + const fileName = event.data.info?.name ?? ''; const contentType = event.data.content_type; if (!isAllowedFile(fileName, contentType)) { // TODO(Federation): Update code once sending assets is implemented on the backend @@ -4190,6 +4330,9 @@ export class ConversationRepository { } const {messageEntity} = await this.addEventToConversation(conversationEntity, event); const firstAsset = (messageEntity as ContentMessage).getFirstAsset(); + if (firstAsset === undefined) { + return undefined; + } if (firstAsset.isImage() || (firstAsset as FileAsset).status() === AssetTransferState.UPLOADED) { return {conversationEntity, messageEntity}; } @@ -4217,7 +4360,12 @@ export class ConversationRepository { throw new ConversationError(ConversationError.TYPE.WRONG_USER, ConversationError.MESSAGE.WRONG_USER); } - const isFromSelf = from === this.userState.self().id; + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const isFromSelf = from === selfUser.id; if (!isFromSelf) { return this.addDeleteMessage(conversationEntity, eventId, time, deletedMessageEntity); } @@ -4243,7 +4391,7 @@ export class ConversationRepository { private async onMessageHidden(eventJson: MessageHiddenEvent) { const {conversation, qualified_conversation, data: eventData, from} = eventJson; - const conversationId = qualified_conversation || {id: conversation, domain: ''}; + const conversationId = qualified_conversation ?? {id: conversation, domain: ''}; try { const inSelfConversation = this.conversationState.isSelfConversation(conversationId); if (!inSelfConversation) { @@ -4253,7 +4401,8 @@ export class ConversationRepository { ); } - const isFromSelf = !this.userState.self() || from === this.userState.self().id; + const selfUser = this.userState.self(); + const isFromSelf = selfUser === undefined || from === selfUser.id; if (!isFromSelf) { throw new ConversationError(ConversationError.TYPE.WRONG_USER, ConversationError.MESSAGE.WRONG_USER); } @@ -4516,8 +4665,13 @@ export class ConversationRepository { return this.messageRepository.deleteMessage(conversationEntity, messageEntity); } + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + const userIds = conversationEntity.isGroupOrChannel() - ? [this.userState.self().qualifiedId, {domain: messageEntity.fromDomain ?? '', id: messageEntity.from}] + ? [selfUser.qualifiedId, {domain: messageEntity.fromDomain ?? '', id: messageEntity.from}] : undefined; return this.messageRepository.deleteMessageForEveryone(conversationEntity, messageEntity, { optimisticRemoval: true, @@ -4529,6 +4683,12 @@ export class ConversationRepository { private async initMessageEntity(conversationEntity: Conversation, eventJson: IncomingEvent): Promise { const messageEntity = this.event_mapper.mapJsonEvent(eventJson, conversationEntity); + if (messageEntity === undefined) { + throw new ConversationError( + ConversationError.TYPE.MESSAGE_NOT_FOUND, + ConversationError.MESSAGE.MESSAGE_NOT_FOUND, + ); + } return this.updateMessageUserEntities(messageEntity); } @@ -4560,12 +4720,10 @@ export class ConversationRepository { conversationEntity: Conversation, eventJson: IncomingEvent, ): Promise<{conversationEntity: Conversation; messageEntity: Message}> { - const messageEntity = (await this.initMessageEntity(conversationEntity, eventJson)) as Message; - if (conversationEntity && messageEntity) { - const wasAdded = conversationEntity.addMessage(messageEntity); - if (wasAdded) { - await this.ephemeralHandler.validateMessage(messageEntity as ContentMessage); - } + const messageEntity = await this.initMessageEntity(conversationEntity, eventJson); + const wasAdded = conversationEntity.addMessage(messageEntity); + if (wasAdded) { + await this.ephemeralHandler.validateMessage(messageEntity as ContentMessage); } return {conversationEntity, messageEntity}; } @@ -4637,7 +4795,7 @@ export class ConversationRepository { private async updateMessageUserEntities(messageEntity: Message, options: {localOnly?: boolean} = {}) { const userEntity = await this.userRepository.getUserById( { - domain: messageEntity.fromDomain, + domain: messageEntity.fromDomain ?? '', id: messageEntity.from, }, options, diff --git a/apps/webapp/src/script/repositories/conversation/ConversationRoleRepository.ts b/apps/webapp/src/script/repositories/conversation/ConversationRoleRepository.ts index 18df3088253..74dea44423d 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationRoleRepository.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationRoleRepository.ts @@ -98,13 +98,14 @@ export class ConversationRoleRepository { const roleUpdates: Record = {}; // Add role for self participant - if (remoteConversationData.members.self.conversation_role) { - roleUpdates[remoteConversationData.members.self.id] = remoteConversationData.members.self.conversation_role; + const selfMember = remoteConversationData.members.self; + if (selfMember !== undefined && selfMember.conversation_role !== undefined) { + roleUpdates[selfMember.id] = selfMember.conversation_role; } // Add roles for other participants remoteConversationData.members.others.forEach(other => { - if (other.conversation_role) { + if (other.conversation_role !== undefined) { roleUpdates[other.id] = other.conversation_role; } }); @@ -123,7 +124,7 @@ export class ConversationRoleRepository { }; private readonly getUserRole = (conversation: Conversation, userEntity: User): string => { - return conversation.roles()[userEntity.id]; + return conversation.roles()[userEntity.id] ?? DefaultRole.WIRE_MEMBER; }; readonly isUserGroupAdmin = (conversation: Conversation, userEntity: User): boolean => { @@ -140,7 +141,7 @@ export class ConversationRoleRepository { private readonly getUserPermissions = (conversation: Conversation, user: User): ConversationRole => { const conversationRoles = this.getConversationRoles(conversation); const userRole: string = this.getUserRole(conversation, user); - return conversationRoles?.find(({conversation_role}) => conversation_role === userRole) || defaultMemberRole; + return conversationRoles.find(({conversation_role}) => conversation_role === userRole) ?? defaultMemberRole; }; readonly hasPermission = (conversation: Conversation, user: User, permissionName: Permissions): boolean => { @@ -153,46 +154,82 @@ export class ConversationRoleRepository { return userRole.actions.includes(permissionName); }; - readonly canRenameGroup = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.renameConversation); + private readonly getPermissionSubjectUser = (user: User | undefined): User | undefined => { + return user ?? this.userState.self(); }; - readonly canAddParticipants = (conversation: Conversation, user: User = this.userState.self()): boolean => { + readonly canRenameGroup = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.renameConversation) + : false; + }; + + readonly canAddParticipants = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + if (permissionSubjectUser === undefined) { + return false; + } + return ( conversation.conversationModerator() === ADD_PERMISSION.EVERYONE || - this.hasPermission(conversation, user, Permissions.addParticipants) + this.hasPermission(conversation, permissionSubjectUser, Permissions.addParticipants) ); }; - readonly canRemoveParticipants = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.removeParticipants); + readonly canRemoveParticipants = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.removeParticipants) + : false; }; - readonly canChangeParticipantRoles = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.changeParticipantRoles); + readonly canChangeParticipantRoles = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.changeParticipantRoles) + : false; }; - readonly canToggleTimeout = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.toggleEphemeralTimer); + readonly canToggleTimeout = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.toggleEphemeralTimer) + : false; }; - readonly canToggleGuests = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.toggleGuestsAndServices); + readonly canToggleGuests = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.toggleGuestsAndServices) + : false; }; - readonly canToggleReadReceipts = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.toggleReadReceipts); + readonly canToggleReadReceipts = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.toggleReadReceipts) + : false; }; - readonly canDeleteGroup = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.deleteConversation); + readonly canDeleteGroup = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.deleteConversation) + : false; }; - readonly canLeaveGroup = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.leaveConversation); + readonly canLeaveGroup = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.leaveConversation) + : false; }; - readonly canToggleAddPermission = (conversation: Conversation, user: User = this.userState.self()): boolean => { - return this.hasPermission(conversation, user, Permissions.toggleAddPermission); + readonly canToggleAddPermission = (conversation: Conversation, user?: User): boolean => { + const permissionSubjectUser = this.getPermissionSubjectUser(user); + return permissionSubjectUser !== undefined + ? this.hasPermission(conversation, permissionSubjectUser, Permissions.toggleAddPermission) + : false; }; } diff --git a/apps/webapp/src/script/repositories/conversation/ConversationState.ts b/apps/webapp/src/script/repositories/conversation/ConversationState.ts index d824189b3d0..71ebcbbec70 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationState.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationState.ts @@ -129,14 +129,16 @@ export class ConversationState { }); this.connectedUsers = ko.pureComputed(() => { - const inviterId = this.teamState.memberInviters()[this.userState.self()?.id]; - const inviter = inviterId ? this.userState.users().find(({id}) => id === inviterId) : null; - const connectedUsers = inviter ? [inviter] : []; - const selfTeamId = this.userState.self()?.teamId; + const selfUser = this.userState.self(); + const selfUserId = selfUser?.id; + const inviterId = selfUserId !== undefined ? this.teamState.memberInviters()[selfUserId] : undefined; + const inviter = inviterId !== undefined ? this.userState.users().find(({id}) => id === inviterId) : undefined; + const connectedUsers = inviter !== undefined ? [inviter] : []; + const selfTeamId = selfUser?.teamId; for (const conversation of this.conversations()) { for (const user of conversation.participating_user_ets()) { - const isNotService = !user.isService; - const isNotIncluded = !connectedUsers.includes(user); + const isNotService = user.isService === false; + const isNotIncluded = connectedUsers.includes(user) === false; if (isNotService && isNotIncluded && (user.teamId === selfTeamId || user.isConnected())) { connectedUsers.push(user); } @@ -154,12 +156,14 @@ export class ConversationState { getSelfConversations(includeMLS: boolean): Conversation[] { const baseConversations = [this.selfProteusConversation()]; const selfConversations = includeMLS ? baseConversations.concat(this.selfMLSConversation()) : baseConversations; - return selfConversations.filter((conversation): conversation is Conversation => !!conversation); + return selfConversations.filter((conversation): conversation is Conversation => { + return conversation !== undefined; + }); } getSelfProteusConversation(): Conversation { const proteusConversation = this.selfProteusConversation(); - if (!proteusConversation) { + if (proteusConversation === undefined) { throw new Error('No proteus self conversation'); } return proteusConversation; @@ -167,7 +171,7 @@ export class ConversationState { getSelfMLSConversation(): MLSConversation { const mlsConversation = this.selfMLSConversation(); - if (!mlsConversation) { + if (mlsConversation === undefined) { throw new Error('No MLS self conversation'); } return mlsConversation; diff --git a/apps/webapp/src/script/repositories/conversation/ConversationStateHandler.ts b/apps/webapp/src/script/repositories/conversation/ConversationStateHandler.ts index 3149b76f4d7..89f03abea76 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationStateHandler.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationStateHandler.ts @@ -54,17 +54,18 @@ export class ConversationStateHandler extends AbstractConversationEventHandler { } async changeAccessState(conversationEntity: Conversation, accessState: ACCESS_STATE): Promise { - const isConversationInTeam = conversationEntity && conversationEntity.inTeam(); + const isConversationInTeam = conversationEntity.inTeam(); const isStateChange = conversationEntity.accessState() !== accessState; const prevAccessState = conversationEntity.accessState(); if (isConversationInTeam) { if (isStateChange) { const {accessModes, accessRole} = updateAccessRights(accessState); - if (accessModes && accessRole) { + if (accessModes !== undefined && accessRole !== undefined) { try { - if (!isGettingAccessToFeature(ACCESS_MODES.CODE, prevAccessState, accessState)) { - conversationEntity.accessCode(undefined); + const isGettingAccessCode = isGettingAccessToFeature(ACCESS_MODES.CODE, prevAccessState, accessState); + if (isGettingAccessCode === false) { + conversationEntity.accessCode(''); await this.revokeAccessCode(conversationEntity); } @@ -107,9 +108,9 @@ export class ConversationStateHandler extends AbstractConversationEventHandler { async requestAccessCode(conversationEntity: Conversation, password?: string): Promise { try { - const response = await this.conversationService.postConversationCode(conversationEntity.id, password); - const accessCode = response && response.data; - if (accessCode) { + const response = await this.conversationService.postConversationCode(conversationEntity.id, password ?? ''); + const accessCode = response?.data; + if (accessCode !== undefined) { ConversationMapper.mapAccessCode(conversationEntity, accessCode); } } catch (e: unknown) { @@ -120,7 +121,7 @@ export class ConversationStateHandler extends AbstractConversationEventHandler { async revokeAccessCode(conversationEntity: Conversation): Promise { try { await this.conversationService.deleteConversationCode(conversationEntity.id); - conversationEntity.accessCode(undefined); + conversationEntity.accessCode(''); } catch (e: unknown) { return this._showModal(t('modalConversationGuestOptionsRevokeCodeMessage')); } @@ -131,11 +132,15 @@ export class ConversationStateHandler extends AbstractConversationEventHandler { eventJson: ConversationEvent, ): void { const {access: accessModes, ...roles} = eventJson.data; - ConversationMapper.mapAccessState(conversationEntity, accessModes, roles?.access_role, roles?.access_role_v2); + const accessRole = roles.access_role; + if (accessRole === undefined) { + return; + } + ConversationMapper.mapAccessState(conversationEntity, accessModes, accessRole, roles.access_role_v2); } private _resetConversationAccessCode(conversationEntity: Conversation): void { - conversationEntity.accessCode(undefined); + conversationEntity.accessCode(''); } private _updateConversationAccessCode( diff --git a/apps/webapp/src/script/repositories/conversation/ConversationVerificationStateHandler/shared/conversation/index.ts b/apps/webapp/src/script/repositories/conversation/ConversationVerificationStateHandler/shared/conversation/index.ts index 0c790da9fbf..0c1321ee341 100644 --- a/apps/webapp/src/script/repositories/conversation/ConversationVerificationStateHandler/shared/conversation/index.ts +++ b/apps/webapp/src/script/repositories/conversation/ConversationVerificationStateHandler/shared/conversation/index.ts @@ -43,19 +43,27 @@ export const getActiveConversationsWithUsers = ({ return conversationState .filteredConversations() .map((conversationEntity: Conversation) => { - if (!conversationEntity.isSelfUserRemoved()) { - const userIdsInConversation = conversationEntity.participating_user_ids().concat(userState.self().qualifiedId); + if (conversationEntity.isSelfUserRemoved() === false) { + const selfUser = userState.self(); + if (selfUser === undefined) { + return undefined; + } + + const userIdsInConversation = conversationEntity.participating_user_ids().concat(selfUser.qualifiedId); const matchingUserIds = userIdsInConversation.filter(userIdInConversation => userIds.find(userId => matchQualifiedIds(userId, userIdInConversation)), ); - if (!!matchingUserIds.length) { + const hasMatchingUserIds = matchingUserIds.length > 0; + if (hasMatchingUserIds) { return {conversationEntity, userIds: matchingUserIds}; } } return undefined; }) - .flatMap(activeConversationInfo => (!!activeConversationInfo ? [activeConversationInfo] : [])); + .flatMap(activeConversationInfo => { + return activeConversationInfo !== undefined ? [activeConversationInfo] : []; + }); }; interface GetConversationByGroupIdParams { @@ -67,7 +75,7 @@ export const getConversationByGroupId = ({ groupId, }: GetConversationByGroupIdParams): MLSCapableConversation | undefined => { const conversation = conversationState.conversations().find(conversation => conversation.groupId === groupId); - return conversation && isMLSCapableConversation(conversation) ? conversation : undefined; + return conversation !== undefined && isMLSCapableConversation(conversation) ? conversation : undefined; }; /** diff --git a/apps/webapp/src/script/repositories/conversation/EventBuilder/EventBuilder.ts b/apps/webapp/src/script/repositories/conversation/EventBuilder/EventBuilder.ts index 66bdac9db77..6d35bc67198 100644 --- a/apps/webapp/src/script/repositories/conversation/EventBuilder/EventBuilder.ts +++ b/apps/webapp/src/script/repositories/conversation/EventBuilder/EventBuilder.ts @@ -308,6 +308,23 @@ function buildQualifiedId(conversation: QualifiedId | string) { }; } +function getConversationSelfUser(conversationEntity: Conversation): User { + const conversationSelfUser = conversationEntity.selfUser(); + if (conversationSelfUser !== undefined) { + return conversationSelfUser; + } + + const firstConversationParticipant = conversationEntity.participating_user_ets()[0]; + if (firstConversationParticipant !== undefined) { + return firstConversationParticipant; + } + + return { + id: '', + qualifiedId: {domain: '', id: ''}, + } as User; +} + export const EventBuilder = { build1to1Creation(conversationEntity: Conversation, timestamp: number = 0): OneToOneCreationEvent { const {creator: creatorId} = conversationEntity; @@ -326,23 +343,25 @@ export const EventBuilder = { }, build1to1MigratedToMLS(conversationEntity: Conversation, currentTimestamp: number): OneToOneMigratedToMlsEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), time: conversationEntity.getNextIsoDate(currentTimestamp), type: ClientEvent.CONVERSATION.ONE2ONE_MIGRATED_TO_MLS, - from: conversationEntity.selfUser().id, + from: selfUser.id, data: undefined, id: createUuid(), }; }, buildAllVerified(conversationEntity: Conversation): AllVerifiedEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), data: { type: VerificationMessageType.VERIFIED, }, - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), time: new Date(conversationEntity.getNextTimestamp()).toISOString(), type: ClientEvent.CONVERSATION.VERIFICATION, @@ -417,13 +436,14 @@ export const EventBuilder = { userIds: QualifiedId[], type: VerificationMessageType, ): DegradedMessageEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), data: { type, userIds, }, - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), time: new Date(conversationEntity.getNextTimestamp()).toISOString(), type: ClientEvent.CONVERSATION.VERIFICATION, @@ -475,13 +495,14 @@ export const EventBuilder = { timestamp: number = 0, ): GroupCreationEvent { const {creator: creatorId} = conversationEntity; - const selfUserId = conversationEntity.selfUser().id; + const selfUser = getConversationSelfUser(conversationEntity); + const selfUserId = selfUser.id; const isoDate = new Date(timestamp).toISOString(); const userIds = conversationEntity.participating_user_ids().slice(); const createdBySelf = creatorId === selfUserId || isTemporaryGuest; if (!createdBySelf) { - userIds.push(conversationEntity.selfUser().qualifiedId); + userIds.push(selfUser.qualifiedId); } return { @@ -612,9 +633,10 @@ export const EventBuilder = { }, buildMissed(conversationEntity: Conversation, currentTimestamp: number): MissedEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), time: conversationEntity.getNextIsoDate(currentTimestamp), type: ClientEvent.CONVERSATION.MISSED_MESSAGES, @@ -625,9 +647,10 @@ export const EventBuilder = { conversationEntity: Conversation, currentTimestamp: number, ): MLSConversationRecoveredEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), time: conversationEntity.getNextIsoDate(currentTimestamp), type: ClientEvent.CONVERSATION.MLS_CONVERSATION_RECOVERED, @@ -638,9 +661,10 @@ export const EventBuilder = { conversationEntity: Conversation, currentTimestamp: number, ): JoinedAfterMLSMigrationFinalisationEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), data: null, time: conversationEntity.getNextIsoDate(currentTimestamp), @@ -652,9 +676,10 @@ export const EventBuilder = { conversationEntity: Conversation, currentTimestamp: number, ): MLSMigrationFinalisationOngoingCallEvent { + const selfUser = getConversationSelfUser(conversationEntity); return { ...buildQualifiedId(conversationEntity), - from: conversationEntity.selfUser().id, + from: selfUser.id, id: createUuid(), data: null, time: conversationEntity.getNextIsoDate(currentTimestamp), diff --git a/apps/webapp/src/script/repositories/conversation/EventMapper.ts b/apps/webapp/src/script/repositories/conversation/EventMapper.ts index 78938597416..ef6d5c81d59 100644 --- a/apps/webapp/src/script/repositories/conversation/EventMapper.ts +++ b/apps/webapp/src/script/repositories/conversation/EventMapper.ts @@ -181,11 +181,16 @@ export class EventMapper { originalEntity.assets.push(textAsset); } else if (originalEntity.getFirstAsset) { const asset = originalEntity.getFirstAsset(); - if (eventData.status && (asset as FileAsset).status) { + if (asset === undefined) { + return originalEntity; + } + if (eventData.status !== undefined && (asset as FileAsset).status !== undefined) { const assetEntity = this._mapAsset(event); - originalEntity.assets([assetEntity]); + if (assetEntity !== undefined) { + originalEntity.assets([assetEntity]); + } } - if (eventData.previews) { + if (eventData.previews !== undefined) { if ((asset as TextAsset).previews().length !== eventData.previews.length) { const previews = this._mapAssetLinkPreviews(eventData.previews); (asset as TextAsset).previews(previews as LinkPreviewEntity[]); @@ -194,12 +199,12 @@ export class EventMapper { const { preview_key, - preview_domain = qualified_conversation?.domain || this.fallbackDomain, + preview_domain = qualified_conversation?.domain ?? this.fallbackDomain, preview_otr_key, preview_sha256, preview_token, } = eventData as AssetData; - if (preview_otr_key && preview_key && preview_domain) { + if (preview_otr_key !== undefined && preview_key !== undefined && preview_domain !== undefined) { const assetRemoteData = new AssetRemoteData({ assetKey: preview_key, assetDomain: preview_domain, @@ -212,32 +217,33 @@ export class EventMapper { } } - if (event.reactions) { + if (event.reactions !== undefined) { originalEntity.reactions(userReactionMapToReactionMap(event.reactions)); - originalEntity.version = event.version; + originalEntity.version = event.version ?? 1; } - if (event.failedToSend) { + if (event.failedToSend !== undefined) { originalEntity.failedToSend(event.failedToSend); } - if (event.fileData) { + if (event.fileData !== undefined) { originalEntity.fileData(event.fileData); } - if (event.selected_button_id) { - originalEntity.version = event.version; + if (event.selected_button_id !== undefined) { + originalEntity.version = event.version ?? 1; } - originalEntity.id = id; + originalEntity.id = id ?? ''; if (originalEntity.isContent() || (originalEntity as Message).isPing()) { originalEntity.status(event.status ?? StatusType.SENT); } originalEntity.replacing_message_id = eventData.replacing_message_id; - if (editedTime || eventData.edited_time) { - originalEntity.edited_timestamp(new Date(editedTime || eventData.edited_time).getTime()); + const editedTimestamp = editedTime ?? eventData.edited_time; + if (editedTimestamp !== undefined) { + originalEntity.edited_timestamp(new Date(editedTimestamp).getTime()); } return addMetadata(originalEntity, event); @@ -443,16 +449,16 @@ export class EventMapper { messageEntity.category = category; messageEntity.conversation_id = conversationEntity.id; - messageEntity.from = from; - messageEntity.fromDomain = qualified_from?.domain; - messageEntity.fromClientId = from_client_id; - messageEntity.id = id; + messageEntity.from = from ?? ''; + messageEntity.fromDomain = qualified_from?.domain ?? ''; + messageEntity.fromClientId = from_client_id ?? ''; + messageEntity.id = id ?? ''; messageEntity.primary_key = primary_key; messageEntity.timestamp(new Date(time).getTime()); messageEntity.type = type; - messageEntity.version = version || 1; + messageEntity.version = version ?? 1; - if (data) { + if (data !== undefined) { messageEntity.legalHoldStatus = data.legal_hold_status; } @@ -471,9 +477,10 @@ export class EventMapper { ); } - if (ephemeral_expires) { + if (ephemeral_expires !== undefined) { messageEntity.ephemeral_expires(ephemeral_expires); - messageEntity.ephemeral_started(Number(ephemeral_started) || 0); + const ephemeralStartedMilliseconds = Number(ephemeral_started ?? 0); + messageEntity.ephemeral_started(Number.isNaN(ephemeralStartedMilliseconds) ? 0 : ephemeralStartedMilliseconds); } if (isNaN(messageEntity.timestamp())) { @@ -519,6 +526,9 @@ export class EventMapper { const messageEntity = new ContentMessage(); const assetEntity = this._mapAsset(event); + if (assetEntity === undefined) { + throw new Error('Asset entity could not be mapped'); + } messageEntity.assets.push(assetEntity); return messageEntity; @@ -545,7 +555,7 @@ export class EventMapper { private _mapEventGroupCreation({data: eventData}: LegacyEventRecord) { const messageEntity = new MemberMessage(); messageEntity.memberMessageType = SystemMessageType.CONVERSATION_CREATE; - messageEntity.name(eventData.name || ''); + messageEntity.name(eventData.name ?? ''); messageEntity.userIds(eventData.userIds); messageEntity.allTeamMembers = eventData.allTeamMembers; return messageEntity; @@ -564,7 +574,7 @@ export class EventMapper { } _mapEventLegalHoldUpdate({data, timestamp}: LegacyEventRecord) { - return new LegalHoldMessage(data.legal_hold_status, timestamp); + return new LegalHoldMessage(data.legal_hold_status, timestamp ?? 0); } /** @@ -1087,12 +1097,12 @@ export class EventMapper { return new MentionEntity( protoMention.start, protoMention.length, - protoMention.qualifiedUserId?.id || protoMention.userId, - protoMention.qualifiedUserId?.domain, + protoMention.qualifiedUserId?.id ?? protoMention.userId ?? '', + protoMention.qualifiedUserId?.domain ?? undefined, ); }) .filter((mentionEntity, _, allMentions): boolean | void => { - if (mentionEntity) { + if (mentionEntity !== undefined) { try { return mentionEntity.validate(messageText, allMentions); } catch (error: unknown) { @@ -1111,14 +1121,14 @@ export class EventMapper { */ private _mapAssetText(eventData: MessageAddEvent['data']) { const {id, content, mentions, message, previews} = eventData; - const messageText = content || message; + const messageText = content ?? message ?? ''; const assetEntity = new Text(id, messageText); - if (mentions && mentions.length) { + if (mentions !== undefined && mentions.length > 0) { const mappedMentions = this._mapAssetMentions(mentions, messageText); assetEntity.mentions(mappedMentions); } - if (previews && previews.length) { + if (previews !== undefined && previews.length > 0) { const mappedLinkPreviews = this._mapAssetLinkPreviews(previews) as unknown as LinkPreviewEntity[]; assetEntity.previews(mappedLinkPreviews); } diff --git a/apps/webapp/src/script/repositories/conversation/MessageRepository.ts b/apps/webapp/src/script/repositories/conversation/MessageRepository.ts index e0b865f5e12..c356777b7b9 100644 --- a/apps/webapp/src/script/repositories/conversation/MessageRepository.ts +++ b/apps/webapp/src/script/repositories/conversation/MessageRepository.ts @@ -777,7 +777,12 @@ export class MessageRepository { } : undefined; - const retention = this.assetRepository.getAssetRetention(this.userState.self(), conversation); + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const retention = this.assetRepository.getAssetRetention(selfUser, conversation); const options = { legalHoldStatus: conversation.legalHoldStatus(), public: true, @@ -1381,15 +1386,24 @@ export class MessageRepository { .connectedUsers() // For the moment, we do not want to send status in federated env // we can remove the filter when we actually want this feature in federated env (and we will need to implement federation for the core broadcastService) - .filter(user => !user.isFederated) + .filter(user => { + return user.isFederated === false; + }) .sort(({id: idA}, {id: idB}) => idA.localeCompare(idB, undefined, {sensitivity: 'base'})); const [members, other] = partition(sortedUsers, user => this.teamState.isInTeam(user)); - const users = [this.userState.self(), ...members, ...other].slice( - 0, - UserRepository.CONFIG.MAXIMUM_TEAM_SIZE_BROADCAST, - ); + const selfUser = this.userState.self(); + if (selfUser === undefined) { + throw new Error('Self user is not available'); + } + + const users = [selfUser, ...members, ...other].slice(0, UserRepository.CONFIG.MAXIMUM_TEAM_SIZE_BROADCAST); + + const coreService = this.core.service; + if (coreService === undefined) { + throw new Error('Core service is not available'); + } - await this.core.service!.broadcast.broadcastGenericMessage( + await coreService.broadcast.broadcastGenericMessage( genericMessage, this.createRecipients(users), this.onClientMismatch, @@ -1701,13 +1715,15 @@ export class MessageRepository { default: break; } - if (actionType) { - const selfUserTeamId = this.userState.self().teamId; + if (actionType !== undefined) { + const selfUserTeamId = this.userState.self()?.teamId; const participants = conversationEntity.participating_user_ets(); const guests = participants.filter(user => user.isGuest()).length; const guestsWireless = participants.filter(user => user.isTemporaryGuest()).length; // guests that are from a different team - const guestsPro = participants.filter(user => !!user.teamId && user.teamId !== selfUserTeamId).length; + const guestsPro = participants.filter(user => { + return user.teamId !== undefined && user.teamId !== '' && user.teamId !== selfUserTeamId; + }).length; const services = participants.filter(user => user.isService).length; let segmentations: ContributedSegmentations = { diff --git a/apps/webapp/src/script/repositories/cryptography/CryptographyMapper.ts b/apps/webapp/src/script/repositories/cryptography/CryptographyMapper.ts index f97f5ef3235..e72e7eeb0b3 100644 --- a/apps/webapp/src/script/repositories/cryptography/CryptographyMapper.ts +++ b/apps/webapp/src/script/repositories/cryptography/CryptographyMapper.ts @@ -37,8 +37,6 @@ import { DataTransfer, External, GenericMessage, - IAsset, - IImageAsset, LastRead, LegalHoldStatus, LinkPreview, @@ -311,14 +309,20 @@ export class CryptographyMapper { return item; } - const {mentions: protoMentions, content} = item.text; + if (item.text === null || item.text === undefined) { + return item; + } + const {content} = item.text; + const protoMentions = item.text.mentions ?? []; - if (protoMentions && protoMentions.length > CryptographyMapper.CONFIG.MAX_MENTIONS_PER_MESSAGE) { + if (protoMentions.length > CryptographyMapper.CONFIG.MAX_MENTIONS_PER_MESSAGE) { this.logger.warn(`Message contains '${protoMentions.length}' mentions exceeding limit`); protoMentions.length = CryptographyMapper.CONFIG.MAX_MENTIONS_PER_MESSAGE; } - const mentions = protoMentions.map(protoMention => arrayToBase64(Mention.encode(protoMention).finish())); + const mentions = protoMentions.map((protoMention: Mention) => { + return arrayToBase64(Mention.encode(protoMention).finish()); + }); return { text: { @@ -356,18 +360,22 @@ export class CryptographyMapper { private _mapAsset(asset: Asset) { const {original, preview, uploaded, notUploaded} = asset; - let data: AssetData; + let data: AssetData = { + content_length: 0, + content_type: '', + info: {}, + }; - if (original) { + if (original !== null && original !== undefined) { data = { content_length: original.size as number, - content_type: original.mimeType, + content_type: original.mimeType ?? '', info: { - name: original.name || null, + name: original.name ?? undefined, }, }; - if (original.image) { + if (original.image !== null && original.image !== undefined) { data.info.height = original.image.height; data.info.width = original.image.width; } else { @@ -375,35 +383,39 @@ export class CryptographyMapper { } } - if (preview) { + if (preview !== null && preview !== undefined) { const remote = preview.remote; + if (remote === null || remote === undefined) { + return {data, type: ClientEvent.CONVERSATION.ASSET_ADD}; + } data = { ...data, - preview_domain: remote.assetDomain, - preview_key: remote.assetId, + preview_domain: remote.assetDomain ?? undefined, + preview_key: remote.assetId ?? undefined, preview_otr_key: new Uint8Array(remote.otrKey), preview_sha256: new Uint8Array(remote.sha256), - preview_token: remote.assetToken, + preview_token: remote.assetToken ?? undefined, }; } - const isImage = original && original.image; + const isImage = + original !== null && original !== undefined && original.image !== null && original.image !== undefined; if (isImage) { data.info.tag = 'medium'; } - if (asset.hasOwnProperty('uploaded') && uploaded !== null) { + if (asset.hasOwnProperty('uploaded') && uploaded !== null && uploaded !== undefined) { data = { ...data, - domain: uploaded.assetDomain, - key: uploaded.assetId, + domain: uploaded.assetDomain ?? undefined, + key: uploaded.assetId ?? undefined, otr_key: new Uint8Array(uploaded.otrKey), sha256: new Uint8Array(uploaded.sha256), status: AssetTransferState.UPLOADED, - token: uploaded.assetToken, + token: uploaded.assetToken ?? undefined, }; - } else if (asset.hasOwnProperty('notUploaded') && notUploaded !== null) { + } else if (asset.hasOwnProperty('notUploaded') && notUploaded !== null && notUploaded !== undefined) { data = {...data, reason: notUploaded, status: AssetTransferState.UPLOAD_FAILED}; } @@ -514,6 +526,12 @@ export class CryptographyMapper { } private _mapEphemeral(genericMessage: GenericMessage, event: EncryptedEvent) { + if (genericMessage.ephemeral === null || genericMessage.ephemeral === undefined) { + throw new CryptographyError( + CryptographyError.TYPE.NO_GENERIC_MESSAGE, + CryptographyError.MESSAGE.NO_GENERIC_MESSAGE, + ); + } const messageTimer = genericMessage.ephemeral[PROTO_MESSAGE_TYPE.EPHEMERAL_EXPIRATION]; (genericMessage.ephemeral as unknown as GenericMessage).messageId = genericMessage.messageId; @@ -581,6 +599,12 @@ export class CryptographyMapper { } private _mapDataTransfer(dataTransfer: DataTransfer) { + if (dataTransfer.trackingIdentifier === null || dataTransfer.trackingIdentifier === undefined) { + throw new CryptographyError( + CryptographyError.TYPE.NO_GENERIC_MESSAGE, + CryptographyError.MESSAGE.NO_GENERIC_MESSAGE, + ); + } return { data: { trackingIdentifier: dataTransfer.trackingIdentifier.identifier, @@ -677,15 +701,19 @@ export class CryptographyMapper { } } -function addMetadata( - mappedEvent: MappedAsset, - asset: (IAsset | IImageAsset) & Partial<{expectsReadConfirmation: boolean; legalHoldStatus: LegalHoldStatus}>, -) { +function addMetadata( + mappedEvent: MappedEventWithData, + asset: unknown, +): MappedEventWithData { + if (asset === null || asset === undefined || typeof asset !== 'object') { + return mappedEvent; + } + const metadata = asset as Partial<{expectsReadConfirmation: boolean; legalHoldStatus: LegalHoldStatus}>; mappedEvent.data = { ...mappedEvent.data, - expects_read_confirmation: asset.expectsReadConfirmation, - legal_hold_status: asset.legalHoldStatus, - }; + expects_read_confirmation: metadata.expectsReadConfirmation, + legal_hold_status: metadata.legalHoldStatus, + } as MappedEventWithData['data']; return mappedEvent; } diff --git a/apps/webapp/src/script/repositories/entity/Conversation.ts b/apps/webapp/src/script/repositories/entity/Conversation.ts index 97a695bddf8..b1c53be5617 100644 --- a/apps/webapp/src/script/repositories/entity/Conversation.ts +++ b/apps/webapp/src/script/repositories/entity/Conversation.ts @@ -212,13 +212,13 @@ export class Conversation { this.logger = getLogger(`Conversation (${this.id})`); this.initialProtocol = this.protocol; - this.accessState = ko.observable(); - this.accessCode = ko.observable(); + this.accessState = ko.observable(ACCESS_STATE.OTHER.UNKNOWN); + this.accessCode = ko.observable(''); this.accessCodeHasPassword = ko.observable(); - this.creator = undefined; - this.name = ko.observable(); - this.teamId = undefined; - this.type = ko.observable(); + this.creator = ''; + this.name = ko.observable(''); + this.teamId = ''; + this.type = ko.observable(CONVERSATION_TYPE.REGULAR); this.groupConversationType = ko.observable(GROUP_CONVERSATION_TYPE.GROUP_CONVERSATION); this.conversationModerator = ko.observable(ADD_PERMISSION.ADMINS); this.cellsState = ko.observable(CONVERSATION_CELLS_STATE.DISABLED); @@ -390,10 +390,14 @@ export class Conversation { this.legalHoldStatus.subscribe(legalHoldStatus => { if (!this.blockLegalHoldMessage && !isSelfConversation(this) && this.hasInitializedUsers()) { + const selfUser = this.selfUser(); + if (selfUser === undefined) { + return; + } amplify.publish(WebAppEvents.CONVERSATION.INJECT_LEGAL_HOLD_MESSAGE, { conversationEntity: this, legalHoldStatus, - userId: this.selfUser().qualifiedId, + userId: selfUser.qualifiedId, }); } }); @@ -420,8 +424,8 @@ export class Conversation { this.isMutable = ko.pureComputed(() => !this.isRequest() && !this.isSelfUserRemoved()); // Messages - this.localMessageTimer = ko.observable(null); - this.globalMessageTimer = ko.observable(null); + this.localMessageTimer = ko.observable(0); + this.globalMessageTimer = ko.observable(null); this.receiptMode = ko.observable(RECEIPT_MODE.OFF); @@ -451,15 +455,19 @@ export class Conversation { return enforcedTimeout; } // Otherwise, use global or local timer if available - if (this.globalMessageTimer() !== null) { - return this.globalMessageTimer(); + const globalMessageTimer = this.globalMessageTimer(); + if (globalMessageTimer !== null) { + return globalMessageTimer; } if (this.localMessageTimer()) { return this.localMessageTimer(); } return 0; }); - this.hasGlobalMessageTimer = ko.pureComputed(() => this.globalMessageTimer() > 0); + this.hasGlobalMessageTimer = ko.pureComputed(() => { + const globalMessageTimer = this.globalMessageTimer(); + return globalMessageTimer !== null && globalMessageTimer > 0; + }); this.messages_unordered = ko.observableArray(); this.messages = ko.pureComputed(() => @@ -507,12 +515,13 @@ export class Conversation { const isMissedCall = messageEntity.isCall() && !messageEntity.wasCompleted(); const isPing = messageEntity.isPing(); const isMessage = messageEntity.isContent(); + const selfUser = this.selfUser(); const isSelfMentioned = isMessage && - this.selfUser() && - (messageEntity as ContentMessage).isUserMentioned(this.selfUser().qualifiedId); + selfUser !== undefined && + (messageEntity as ContentMessage).isUserMentioned(selfUser.qualifiedId); const isSelfQuoted = - isMessage && this.selfUser() && (messageEntity as ContentMessage).isUserQuoted(this.selfUser().id); + isMessage && selfUser !== undefined && (messageEntity as ContentMessage).isUserQuoted(selfUser.id); const isE2EIVerification = messageEntity.isE2EIVerification(); @@ -747,14 +756,18 @@ export class Conversation { * @returns If a message was replaced in the conversation */ addMessage(messageEntity: Message): boolean | void { - if (!messageEntity) { + if (messageEntity === undefined) { return; } - const messageWithLinkPreview = () => this._findDuplicate(messageEntity.id, messageEntity.from); - const editedMessage = () => - this._findDuplicate((messageEntity as ContentMessage).replacing_message_id, messageEntity.from); - const alreadyAdded = messageWithLinkPreview() || editedMessage(); + const messageWithLinkPreview = () => { + return this._findDuplicate(messageEntity.id, messageEntity.from); + }; + const editedMessage = () => { + const replacingMessageId = (messageEntity as ContentMessage).replacing_message_id; + return replacingMessageId !== undefined ? this._findDuplicate(replacingMessageId, messageEntity.from) : undefined; + }; + const alreadyAdded = messageWithLinkPreview() ?? editedMessage(); if (messageEntity.isContent()) { this.hasContentMessages(true); @@ -795,7 +808,7 @@ export class Conversation { // we found a message from self user for (let counter = message_ets.length - 1; counter >= 0; counter--) { const message_et = message_ets[counter]; - if (message_et.user()?.isMe) { + if (message_et?.user()?.isMe) { this.updateTimestamps(message_et); break; } @@ -805,11 +818,11 @@ export class Conversation { this.messages_unordered.push(...message_ets); } - getFirstUnreadSelfMention(): ContentMessage { + getFirstUnreadSelfMention(): ContentMessage | undefined { return this.unreadState().selfMentions.slice().pop(); } - getLastKnownTimestamp(currentTimestamp?: number): number { + getLastKnownTimestamp(currentTimestamp: number = 0): number { const last_known_timestamp = Math.max(this.last_server_timestamp(), this.last_event_timestamp()); return last_known_timestamp ?? currentTimestamp; } @@ -854,10 +867,10 @@ export class Conversation { const participantsMapped = this.participating_user_ids().length === this.participating_user_ets().length; if (participantsMapped) { return this.participating_user_ets().reduce((accumulator, userEntity) => { - return userEntity.devices().length + return userEntity.devices().length > 0 ? accumulator + userEntity.devices().length : accumulator + ClientRepository.CONFIG.AVERAGE_NUMBER_OF_CLIENTS; - }, this.selfUser().devices().length); + }, this.selfUser()?.devices().length ?? 0); } return this.getNumberOfParticipants() * ClientRepository.CONFIG.AVERAGE_NUMBER_OF_CLIENTS; @@ -870,7 +883,9 @@ export class Conversation { prependMessages(message_ets: ContentMessage[]): void { message_ets = message_ets .map(message_et => this._checkForDuplicate(message_et)) - .filter(message_et => !!message_et) as ContentMessage[]; + .filter((messageEntity): messageEntity is ContentMessage => { + return messageEntity !== undefined; + }); this.messages_unordered.unshift(...message_ets); } @@ -880,7 +895,9 @@ export class Conversation { * @param message_id ID of the message entity to be removed from the conversation */ removeMessageById(message_id: string): void { - this.messages_unordered.remove(message_et => message_id && message_id === message_et.id); + this.messages_unordered.remove(messageEntity => { + return message_id !== '' && message_id === messageEntity.id; + }); } /** @@ -1040,17 +1057,19 @@ export class Conversation { } getTemporaryGuests(): User[] { - const userEntities = this.selfUser() - ? this.participating_user_ets().concat(this.selfUser()) - : this.participating_user_ets(); + const selfUser = this.selfUser(); + const userEntities = + selfUser !== undefined ? this.participating_user_ets().concat(selfUser) : this.participating_user_ets(); return userEntities.filter(userEntity => userEntity.isTemporaryGuest()); } getUsersWithUnverifiedClients(): User[] { - const userEntities = this.selfUser() - ? this.participating_user_ets().concat(this.selfUser()) - : this.participating_user_ets(); - return userEntities.filter(userEntity => !userEntity.is_verified()); + const selfUser = this.selfUser(); + const userEntities = + selfUser !== undefined ? this.participating_user_ets().concat(selfUser) : this.participating_user_ets(); + return userEntities.filter(userEntity => { + return userEntity.is_verified() === false; + }); } supportsVideoCall(sftEnabled: boolean): boolean { @@ -1069,8 +1088,8 @@ export class Conversation { serialize(): ConversationRecord { return { - access: this.accessModes, - access_role: this.accessRole, + access: this.accessModes ?? [], + access_role: this.accessRole ?? [], archived_state: this.archivedState(), readonly_state: this.readOnlyState(), archived_timestamp: this.archivedTimestamp(), @@ -1080,8 +1099,8 @@ export class Conversation { domain: this.domain, ephemeral_timer: this.localMessageTimer(), epoch: this.epoch, - global_message_timer: this.globalMessageTimer(), - group_id: this.groupId, + global_message_timer: this.globalMessageTimer() ?? 0, + group_id: this.groupId ?? '', initial_protocol: this.initialProtocol, id: this.id, is_guest: this.isGuest(), diff --git a/apps/webapp/src/script/repositories/entity/User/User.ts b/apps/webapp/src/script/repositories/entity/User/User.ts index b79ea01336d..4e478e7740d 100644 --- a/apps/webapp/src/script/repositories/entity/User/User.ts +++ b/apps/webapp/src/script/repositories/entity/User/User.ts @@ -55,7 +55,7 @@ export class User { /** does not include current client/device */ public readonly devices: ko.ObservableArray; public localClient: ClientEntity | undefined; - public readonly email: ko.Observable; + public readonly email: ko.Observable; public locale?: string; public readonly expirationRemaining: ko.Observable; public readonly expirationRemainingText: ko.Observable; @@ -145,6 +145,7 @@ export class User { this.serviceId = undefined; this.category = undefined; this.description = undefined; + this.type = UserType.REGULAR; this.isAvailable = ko.pureComputed(() => this.id !== '' && this.name() !== ''); @@ -172,8 +173,12 @@ export class User { this.username = ko.observable(''); - this.previewPictureResource = ko.observable().extend({rateLimit: {method: 'notifyWhenChangesStop', timeout: 100}}); - this.mediumPictureResource = ko.observable().extend({rateLimit: {method: 'notifyWhenChangesStop', timeout: 100}}); + this.previewPictureResource = (ko.observable() as ko.Observable).extend({ + rateLimit: {method: 'notifyWhenChangesStop', timeout: 100}, + }); + this.mediumPictureResource = (ko.observable() as ko.Observable).extend({ + rateLimit: {method: 'notifyWhenChangesStop', timeout: 100}, + }); this.connection = ko.observable(null); @@ -268,7 +273,9 @@ export class User { this.devices.push(new_client_et); if (this.isMe) { - this.devices.sort((client_a, client_b) => new Date(client_b.time).getTime() - new Date(client_a.time).getTime()); + this.devices.sort((client_a, client_b) => { + return new Date(client_b.time ?? '').getTime() - new Date(client_a.time ?? '').getTime(); + }); } return true; diff --git a/apps/webapp/src/script/repositories/entity/message/Asset.ts b/apps/webapp/src/script/repositories/entity/message/Asset.ts index 8a214d814e7..b54fb5dee8a 100644 --- a/apps/webapp/src/script/repositories/entity/message/Asset.ts +++ b/apps/webapp/src/script/repositories/entity/message/Asset.ts @@ -37,6 +37,7 @@ export class Asset { constructor(id?: string) { this.id = id; this.key = ''; + this.size = ''; this.type = ''; this.text = ''; } diff --git a/apps/webapp/src/script/repositories/entity/message/CallMessage.ts b/apps/webapp/src/script/repositories/entity/message/CallMessage.ts index 2f5cb263607..f77752ec3c6 100644 --- a/apps/webapp/src/script/repositories/entity/message/CallMessage.ts +++ b/apps/webapp/src/script/repositories/entity/message/CallMessage.ts @@ -31,7 +31,7 @@ export class CallMessage extends Message { private readonly call_message_type: CALL_MESSAGE_TYPE; private readonly duration: number; public readonly caption?: ko.PureComputed; - public readonly finished_reason: TERMINATION_REASON; + public readonly finished_reason?: TERMINATION_REASON; constructor(type: CALL_MESSAGE_TYPE, reason?: TERMINATION_REASON, duration: number = 0) { super(); diff --git a/apps/webapp/src/script/repositories/entity/message/ContentMessage.ts b/apps/webapp/src/script/repositories/entity/message/ContentMessage.ts index cb2205ec67c..11a2e0b2e41 100644 --- a/apps/webapp/src/script/repositories/entity/message/ContentMessage.ts +++ b/apps/webapp/src/script/repositories/entity/message/ContentMessage.ts @@ -53,11 +53,13 @@ export class ContentMessage extends Message { constructor(id?: string) { super(id); - this.was_edited = ko.pureComputed(() => !!this.edited_timestamp()); + this.was_edited = ko.pureComputed(() => { + return this.edited_timestamp() !== null; + }); } readonly displayEditedTimestamp = () => { - return t('conversationEditTimestamp', {date: formatTimeShort(this.edited_timestamp())}); + return t('conversationEditTimestamp', {date: formatTimeShort(this.edited_timestamp() ?? 0)}); }; /** @@ -76,7 +78,7 @@ export class ContentMessage extends Message { * Get the first asset attached to the message. * @returns The first asset attached to the message */ - getFirstAsset(): Asset | FileAsset | TextAsset | MediumImage { + getFirstAsset(): Asset | FileAsset | TextAsset | MediumImage | undefined { return this.assets()[0]; } @@ -95,7 +97,8 @@ export class ContentMessage extends Message { * @returns `true` if the message quotes the user, `false` otherwise. */ isUserQuoted(userId: string): boolean { - return this.quote() ? this.quote().isQuoteFromUser(userId) : false; + const quoteEntity = this.quote(); + return quoteEntity !== undefined ? quoteEntity.isQuoteFromUser(userId) : false; } /** diff --git a/apps/webapp/src/script/repositories/entity/message/FileAsset.ts b/apps/webapp/src/script/repositories/entity/message/FileAsset.ts index 51c8ae226a1..eafdb7fb9ea 100644 --- a/apps/webapp/src/script/repositories/entity/message/FileAsset.ts +++ b/apps/webapp/src/script/repositories/entity/message/FileAsset.ts @@ -52,7 +52,7 @@ export class FileAsset extends Asset { this.logger = getLogger('FileAsset'); // AssetTransferState - this.status = ko.observable(); + this.status = ko.observable() as ko.Observable; this.file_name = ''; this.file_size = 0; @@ -62,8 +62,8 @@ export class FileAsset extends Asset { this.meta = {}; // asset URL, instance of an OTR asset this has to be decrypted - this.original_resource = ko.observable(); - this.preview_resource = ko.observable(); + this.original_resource = ko.observable() as ko.Observable; + this.preview_resource = ko.observable() as ko.Observable; this.downloadProgress = ko.pureComputed(() => { if (this.original_resource()) { @@ -79,7 +79,7 @@ export class FileAsset extends Asset { } }; - this.upload_failed_reason = ko.observable(); + this.upload_failed_reason = ko.observable() as ko.Observable; } reload(): void { diff --git a/apps/webapp/src/script/repositories/entity/message/MediumImage.ts b/apps/webapp/src/script/repositories/entity/message/MediumImage.ts index f8e7bfe3ac2..0aab0ec642f 100644 --- a/apps/webapp/src/script/repositories/entity/message/MediumImage.ts +++ b/apps/webapp/src/script/repositories/entity/message/MediumImage.ts @@ -39,7 +39,7 @@ export class MediumImage extends FileAsset { this.width = '0px'; this.height = '0px'; - this.resource = ko.observable(); + this.resource = ko.observable() as ko.Observable; this.logger = getLogger('MediumImage'); } diff --git a/apps/webapp/src/script/repositories/entity/message/MemberMessage.ts b/apps/webapp/src/script/repositories/entity/message/MemberMessage.ts index 51a1e48245e..2492c5916e7 100644 --- a/apps/webapp/src/script/repositories/entity/message/MemberMessage.ts +++ b/apps/webapp/src/script/repositories/entity/message/MemberMessage.ts @@ -34,7 +34,7 @@ import {SystemMessageType} from '../../../message/SystemMessageType'; import {User} from '../User'; export class MemberMessage extends SystemMessage { - public allTeamMembers: User[]; + public allTeamMembers: User[] | undefined; public readonly hasUsers: ko.PureComputed; public readonly userIds: ko.ObservableArray; public readonly userEntities: ko.ObservableArray; @@ -57,11 +57,13 @@ export class MemberMessage extends SystemMessage { this.super_type = SuperType.MEMBER; this.memberMessageType = SystemMessageType.NORMAL; - this.userEntities = ko.observableArray(); + this.userEntities = ko.observableArray([]); this.userIds = ko.observableArray(); this.name = ko.observable(''); - this.hasUsers = ko.pureComputed(() => !!this.userEntities().length); + this.hasUsers = ko.pureComputed(() => { + return this.userEntities().length > 0; + }); this.allTeamMembers = undefined; this.showServicesWarning = false; @@ -71,12 +73,16 @@ export class MemberMessage extends SystemMessage { }); this.targetedUsers = ko.pureComputed(() => { - return this.userEntities().filter(userEntity => !matchQualifiedIds(this.user(), userEntity)); + return this.userEntities().filter(userEntity => { + return matchQualifiedIds(this.user(), userEntity) === false; + }); }); // Users joined the conversation without self this.remoteUserEntities = ko.pureComputed(() => { - return this.userEntities().filter(userEntity => !userEntity.isMe); + return this.userEntities().filter(userEntity => { + return userEntity.isMe === false; + }); }); this.senderName = ko.pureComputed(() => { @@ -84,9 +90,13 @@ export class MemberMessage extends SystemMessage { return isTeamMemberLeave ? this.name() : getUserName(this.user(), Declension.NOMINATIVE, true); }); - this.showNamedCreation = ko.pureComputed(() => this.isConversationCreate() && this.name().length > 0); + this.showNamedCreation = ko.pureComputed(() => { + return this.isConversationCreate() && this.name().length > 0; + }); - this.otherUser = ko.pureComputed(() => (this.hasUsers() ? this.userEntities()[0] : new User('', null))); + this.otherUser = ko.pureComputed(() => { + return this.hasUsers() ? this.userEntities()[0] : new User('', ''); + }); this.htmlGroupCreationHeader = ko.pureComputed(() => { if (this.showNamedCreation()) { diff --git a/apps/webapp/src/script/repositories/entity/message/Message.ts b/apps/webapp/src/script/repositories/entity/message/Message.ts index a0bb53da0f4..a00712d0690 100644 --- a/apps/webapp/src/script/repositories/entity/message/Message.ts +++ b/apps/webapp/src/script/repositories/entity/message/Message.ts @@ -87,10 +87,10 @@ export class Message { constructor(id: string = '0', super_type?: SuperType) { this.id = id; - this.super_type = super_type; + this.super_type = super_type ?? SuperType.SYSTEM; this.ephemeralCaption = ko.pureComputed(() => { const remainingTime = this.ephemeral_remaining(); - return remainingTime ? `${formatDurationCaption(remainingTime)} ${t('ephemeralRemaining')}` : ''; + return remainingTime > 0 ? `${formatDurationCaption(remainingTime)} ${t('ephemeralRemaining')}` : ''; }); this.ephemeral_remaining = ko.observable(0); this.ephemeral_expires = ko.observable(false); @@ -130,7 +130,7 @@ export class Message { this.primary_key = undefined; this.status = ko.observable(StatusType.UNSPECIFIED); this.type = ''; - this.user = ko.observable(new User('', null)); + this.user = ko.observable(new User('', '')); this.version = 1; this.visible = ko.observable(true); @@ -198,7 +198,12 @@ export class Message { } hasMultipartAsset(): this is ContentMessage { - return this.isContent() ? this.assets().some(assetEntity => assetEntity.type === AssetType.MULTIPART) : false; + const contentMessageCandidate = this as unknown as {assets?: () => Array<{type: AssetType}>}; + const hasAssetsFunction = + Object.prototype.hasOwnProperty.call(this, 'assets') && typeof contentMessageCandidate.assets === 'function'; + return this.isContent() && hasAssetsFunction + ? this.assets().some(assetEntity => assetEntity.type === AssetType.MULTIPART) + : false; } getMultipartAssets() { diff --git a/apps/webapp/src/script/repositories/entity/message/MessageTimerUpdateMessage.ts b/apps/webapp/src/script/repositories/entity/message/MessageTimerUpdateMessage.ts index ed1da8b0b2c..eb06fc9babd 100644 --- a/apps/webapp/src/script/repositories/entity/message/MessageTimerUpdateMessage.ts +++ b/apps/webapp/src/script/repositories/entity/message/MessageTimerUpdateMessage.ts @@ -28,7 +28,7 @@ import {SystemMessage} from './SystemMessage'; import {SystemMessageType} from '../../../message/SystemMessageType'; export class MessageTimerUpdateMessage extends SystemMessage { - public readonly message_timer: number; + public readonly message_timer: number | null; constructor(messageTimer: number | null) { super(); @@ -42,7 +42,7 @@ export class MessageTimerUpdateMessage extends SystemMessage { } } -const getCaption = (messageTimer: number, isSelfUser: boolean) => { +const getCaption = (messageTimer: number | null, isSelfUser: boolean) => { if (messageTimer) { const timeString = formatDuration(messageTimer).text; return isSelfUser diff --git a/apps/webapp/src/script/repositories/entity/message/VerificationMessage.ts b/apps/webapp/src/script/repositories/entity/message/VerificationMessage.ts index 8dc2c53fbfc..b9449ef5a09 100644 --- a/apps/webapp/src/script/repositories/entity/message/VerificationMessage.ts +++ b/apps/webapp/src/script/repositories/entity/message/VerificationMessage.ts @@ -32,7 +32,7 @@ import type {User} from '../User'; export class VerificationMessage extends Message { public readonly userEntities: ko.ObservableArray; public userIds: ko.ObservableArray; - public verificationMessageType: ko.Observable; + public verificationMessageType: ko.Observable; public readonly isSelfClient: ko.PureComputed; constructor() { @@ -47,8 +47,8 @@ export class VerificationMessage extends Message { this.userEntities = ko.observableArray(); this.isSelfClient = ko.pureComputed(() => { - const messageUserId = this.userIds()?.length === 1 && this.userIds()[0]; - return matchQualifiedIds(messageUserId, this.user().qualifiedId); + const messageUserId = this.userIds().length === 1 ? this.userIds()[0] : undefined; + return messageUserId !== undefined && matchQualifiedIds(messageUserId, this.user().qualifiedId); }); } } diff --git a/apps/webapp/src/script/repositories/event/EventRepository.ts b/apps/webapp/src/script/repositories/event/EventRepository.ts index 1256736ff6b..38c2b25bc9a 100644 --- a/apps/webapp/src/script/repositories/event/EventRepository.ts +++ b/apps/webapp/src/script/repositories/event/EventRepository.ts @@ -173,7 +173,8 @@ export class EventRepository { await this.handleEvent(payload, source); } catch (error: unknown) { if (source === EventSource.NOTIFICATION_STREAM) { - this.logger.warn(`Failed to handle event of type "${event.type}": ${toError(error).message}`, error); + const eventType = (payload as {event?: {type?: string}}).event?.type ?? 'unknown'; + this.logger.warn(`Failed to handle event of type "${eventType}": ${toError(error).message}`, error); } else { throw error; } @@ -394,7 +395,7 @@ export class EventRepository { * @returns Resolves when the last event date was stored */ private updateLastEventDate(eventDate: string): Promise | void { - const didDateIncrease = eventDate > this.lastEventDate(); + const didDateIncrease = eventDate > (this.lastEventDate() ?? ''); if (didDateIncrease) { this.lastEventDate(eventDate); return this.notificationService.saveLastEventDateToDb(eventDate); diff --git a/apps/webapp/src/script/repositories/event/EventService.ts b/apps/webapp/src/script/repositories/event/EventService.ts index 4222dce29d3..54044aae007 100644 --- a/apps/webapp/src/script/repositories/event/EventService.ts +++ b/apps/webapp/src/script/repositories/event/EventService.ts @@ -49,14 +49,20 @@ type DexieCollection = Dexie.Collection; type DBEvents = DexieCollection | EventRecord[]; export type IdentifiedUpdatePayload = Partial & Pick; -const eventTimeToDate = (time: string) => new Date(time) || new Date(parseInt(time, 10)); - -const compareEventsByConversation = (eventA: EventRecord, eventB: EventRecord) => - eventA.conversation.localeCompare(eventB.conversation); - -const compareEventsById = (eventA: EventRecord, eventB: EventRecord) => eventA.id.localeCompare(eventB.id); -const compareEventsByTime = (eventA: EventRecord, eventB: EventRecord) => - eventTimeToDate(eventA.time).getTime() - eventTimeToDate(eventB.time).getTime(); +const eventTimeToDate = (time: string) => { + return new Date(time); +}; + +const compareEventsByConversation = (eventA: EventRecord, eventB: EventRecord) => { + return (eventA.conversation ?? '').localeCompare(eventB.conversation ?? ''); +}; + +const compareEventsById = (eventA: EventRecord, eventB: EventRecord) => { + return (eventA.id ?? '').localeCompare(eventB.id ?? ''); +}; +const compareEventsByTime = (eventA: EventRecord, eventB: EventRecord) => { + return eventTimeToDate(eventA.time).getTime() - eventTimeToDate(eventB.time).getTime(); +}; const MAX_INDEXED_DB_LIMIT = 0xffffffff; // 4_294_967_295 /** Handles all databases interactions related to events */ @@ -86,7 +92,7 @@ export class EventService { const records = await this.storageService.getAll(StorageSchemata.OBJECT_STORE.EVENTS); return records - .filter(record => record.conversation === conversationId && eventIds.includes(record.id)) + .filter(record => record.conversation === conversationId && eventIds.includes(record.id ?? '')) .sort(compareEventsById); } catch (error: unknown) { const logMessage = `Failed to get events '${eventIds.join(',')}' for conversation '${conversationId}': ${ @@ -357,7 +363,7 @@ export class EventService { }; const savedEvent: EventRecord = { ...categorizedEvent, - primary_key: await this.storageService.save(StorageSchemata.OBJECT_STORE.EVENTS, undefined, categorizedEvent), + primary_key: await this.storageService.save(StorageSchemata.OBJECT_STORE.EVENTS, '', categorizedEvent), } as EventRecord; return savedEvent; } diff --git a/apps/webapp/src/script/repositories/extension/GiphyRepository.ts b/apps/webapp/src/script/repositories/extension/GiphyRepository.ts index c714d145fef..6987b546609 100644 --- a/apps/webapp/src/script/repositories/extension/GiphyRepository.ts +++ b/apps/webapp/src/script/repositories/extension/GiphyRepository.ts @@ -75,9 +75,11 @@ export class GiphyRepository { ...options, }; - const hasReachedRetryLimit = retry >= options.maxRetries; + const maximumRetries = options.maxRetries ?? GiphyRepository.CONFIG.MAX_RETRIES; + const maximumSize = options.maxSize ?? GiphyRepository.CONFIG.MAX_SIZE; + const hasReachedRetryLimit = retry >= maximumRetries; if (hasReachedRetryLimit) { - throw new Error(`Unable to fetch a proper GIF within ${options.maxRetries} retries`); + throw new Error(`Unable to fetch a proper GIF within ${maximumRetries} retries`); } const {data: randomGif} = await this.giphyService.getRandom(options.tag); @@ -89,7 +91,7 @@ export class GiphyRepository { } = await this.giphyService.getById(randomGif.id); const staticGif = images.fixed_width_still; const animatedGif = images.downsized; - const exceedsMaxSize = window.parseInt(animatedGif.size, 10) > options.maxSize; + const exceedsMaxSize = window.parseInt(animatedGif.size, 10) > maximumSize; if (exceedsMaxSize) { this.logger.info(`Gif size (${animatedGif.size}) is over maximum size (${animatedGif.size})`); diff --git a/apps/webapp/src/script/repositories/media/MediaConstraintsHandler.ts b/apps/webapp/src/script/repositories/media/MediaConstraintsHandler.ts index f0c122130dc..860ada37a01 100644 --- a/apps/webapp/src/script/repositories/media/MediaConstraintsHandler.ts +++ b/apps/webapp/src/script/repositories/media/MediaConstraintsHandler.ts @@ -113,7 +113,7 @@ export class MediaConstraintsHandler { getAgcPreference(): boolean { const storedValue = window.localStorage.getItem(this.agcStorageKey); - return JSON.parse(storedValue) ?? false; + return storedValue !== null ? (JSON.parse(storedValue) ?? false) : false; } getMediaStreamConstraints( diff --git a/apps/webapp/src/script/repositories/media/MediaEmbeds.ts b/apps/webapp/src/script/repositories/media/MediaEmbeds.ts index fe63309438c..ce720da9521 100644 --- a/apps/webapp/src/script/repositories/media/MediaEmbeds.ts +++ b/apps/webapp/src/script/repositories/media/MediaEmbeds.ts @@ -124,7 +124,7 @@ const generateYouTubeEmbedUrl = (url: string): string | void => { // Convert the timestamp into an embed friendly format (start=seconds) if (searchParams.has('t')) { - searchParams.set('start', convertYouTubeTimestampToSeconds(searchParams.get('t')).toString()); + searchParams.set('start', convertYouTubeTimestampToSeconds(searchParams.get('t') ?? '').toString()); searchParams.delete('t'); } diff --git a/apps/webapp/src/script/repositories/media/MediaParser.ts b/apps/webapp/src/script/repositories/media/MediaParser.ts index 1fa511fbef1..74ed7448501 100644 --- a/apps/webapp/src/script/repositories/media/MediaParser.ts +++ b/apps/webapp/src/script/repositories/media/MediaParser.ts @@ -56,7 +56,9 @@ export class MediaParser { readonly renderMediaEmbeds = (message: string, themeColor?: string): string => { if (this.showEmbed) { getLinksFromHtml(message).forEach(link => { - this.embeds.forEach(embed => (message = embed(link, message, themeColor))); + this.embeds.forEach(embed => { + message = embed(link, message, themeColor ?? ''); + }); }); } return message; diff --git a/apps/webapp/src/script/repositories/media/useBackgroundEffectsStore.ts b/apps/webapp/src/script/repositories/media/useBackgroundEffectsStore.ts index fcd5c27c0db..ab766a9ad31 100644 --- a/apps/webapp/src/script/repositories/media/useBackgroundEffectsStore.ts +++ b/apps/webapp/src/script/repositories/media/useBackgroundEffectsStore.ts @@ -80,7 +80,7 @@ export const backgroundEffectsStore = createStore()( setModel: model => set(state => { - state.model = model; + state.model = model ?? 'unknown'; }), })), ); diff --git a/apps/webapp/src/script/repositories/notification/NotificationRepository.ts b/apps/webapp/src/script/repositories/notification/NotificationRepository.ts index fdb8a655fc8..876b50b2068 100644 --- a/apps/webapp/src/script/repositories/notification/NotificationRepository.ts +++ b/apps/webapp/src/script/repositories/notification/NotificationRepository.ts @@ -328,6 +328,9 @@ export class NotificationRepository { if (messageEntity.hasAsset()) { const assetEntity = messageEntity.getFirstAsset(); + if (assetEntity === undefined) { + return undefined; + } if (assetEntity.isAudio()) { return t('notificationSharedAudio'); diff --git a/apps/webapp/src/script/repositories/notification/PreferenceNotificationRepository.ts b/apps/webapp/src/script/repositories/notification/PreferenceNotificationRepository.ts index a01f7dcd963..9d2a35d8ab8 100644 --- a/apps/webapp/src/script/repositories/notification/PreferenceNotificationRepository.ts +++ b/apps/webapp/src/script/repositories/notification/PreferenceNotificationRepository.ts @@ -68,7 +68,7 @@ export class PreferenceNotificationRepository { /** * @param selfUser an observable that contains the self user */ - constructor(selfUser: ko.Subscribable) { + constructor(selfUser: ko.Subscribable) { const notificationsStorageKey = PreferenceNotificationRepository.CONFIG.STORAGE_KEY; const storedNotifications = loadValue(notificationsStorageKey); this.notifications = ko.observableArray(storedNotifications ? JSON.parse(storedNotifications) : []); @@ -79,17 +79,30 @@ export class PreferenceNotificationRepository { }); amplify.subscribe(WebAppEvents.USER.CLIENT_ADDED, (user: QualifiedId, clientEntity?: ClientEntity) => { - if (clientEntity && !clientEntity.isLegalHold() && matchQualifiedIds(user, selfUser())) { + const currentSelfUser = selfUser(); + const shouldNotifyClientAdded = + clientEntity !== undefined && + clientEntity.isLegalHold() === false && + currentSelfUser !== undefined && + matchQualifiedIds(user, currentSelfUser); + if (shouldNotifyClientAdded) { const {id, domain, type, time, model} = clientEntity; this.notifications.push({ - data: {domain, id, model, time, type}, + data: { + domain: domain ?? undefined, + id: id ?? '', + model: model ?? '', + time: time ?? '', + type: type ?? ClientType.PERMANENT, + }, type: PreferenceNotificationRepository.CONFIG.NOTIFICATION_TYPES.NEW_CLIENT, }); } }); amplify.subscribe(WebAppEvents.USER.CLIENT_REMOVED, (user: QualifiedId, clientId: string) => { - if (matchQualifiedIds(user, selfUser())) { - this.onClientRemove(user.id, clientId, user.domain); + const currentSelfUser = selfUser(); + if (currentSelfUser !== undefined && matchQualifiedIds(user, currentSelfUser)) { + this.onClientRemove(user.id, clientId, user.domain ?? null); } }); amplify.subscribe(WebAppEvents.USER.EVENT_FROM_BACKEND, this.onUserEvent); @@ -114,7 +127,7 @@ export class PreferenceNotificationRepository { return false; } const isExpectedId = matchQualifiedIds( - {domain: clientEntity.domain, id: clientEntity.id}, + {domain: clientEntity.domain ?? null, id: clientEntity.id}, {domain, id: clientId}, ); return isExpectedId && clientEntity.type === ClientType.PERMANENT; diff --git a/apps/webapp/src/script/repositories/storage/StorageService.ts b/apps/webapp/src/script/repositories/storage/StorageService.ts index 06e583f925a..6420365cf63 100644 --- a/apps/webapp/src/script/repositories/storage/StorageService.ts +++ b/apps/webapp/src/script/repositories/storage/StorageService.ts @@ -77,7 +77,9 @@ export class StorageService { try { if (this.hasHookSupport) { this.db = this.engine['db']; - this._initCrudHooks(this.db); + if (this.db !== undefined) { + this._initCrudHooks(this.db); + } } return this.dbName; } catch (error: unknown) { @@ -115,7 +117,7 @@ export class StorageService { db.table(table).hook( DEXIE_CRUD_EVENT.DELETING, function (primaryKey: string, obj: Object, transaction: Transaction): void { - this.onsuccess = (): void => callListener(table, DEXIE_CRUD_EVENT.DELETING, obj, undefined, transaction); + this.onsuccess = (): void => callListener(table, DEXIE_CRUD_EVENT.DELETING, obj, {}, transaction); }, ); }); @@ -244,7 +246,8 @@ export class StorageService { if (!this.db) { return []; } - return tableNames.map(tableName => this.db.table(tableName)); + const database = this.db; + return tableNames.map(tableName => database.table(tableName)); } /** @@ -330,6 +333,9 @@ export class StorageService { async update>(storeName: string, primaryKey: string, changes: T): Promise { try { if (this.hasHookSupport) { + if (this.db === undefined) { + return 0; + } const numberOfUpdates = await this.db.table(storeName).update(primaryKey, changes); const logMessage = `Updated ${numberOfUpdates} record(s) with key '${primaryKey}' in store '${storeName}'`; this.logger.log(logMessage); diff --git a/apps/webapp/src/script/repositories/team/TeamRepository.ts b/apps/webapp/src/script/repositories/team/TeamRepository.ts index ab333a11b74..8bf6e0dd0d3 100644 --- a/apps/webapp/src/script/repositories/team/TeamRepository.ts +++ b/apps/webapp/src/script/repositories/team/TeamRepository.ts @@ -301,7 +301,9 @@ export class TeamRepository extends TypedEventEmitter { async getSelfMember(teamId: string): Promise { const memberEntity = await this.getTeamMember(teamId, this.userState.self().id); - this.updateUserRole(this.userState.self(), memberEntity.permissions); + if (memberEntity.permissions !== undefined) { + this.updateUserRole(this.userState.self(), memberEntity.permissions); + } return memberEntity; } @@ -311,7 +313,7 @@ export class TeamRepository extends TypedEventEmitter { private getTeamMembersFromUsers = async (users: User[]): Promise => { const selfTeamId = this.userState.self().teamId; - if (!selfTeamId) { + if (selfTeamId === undefined || selfTeamId === '') { return; } const knownMemberIds = this.teamState.teamMembers().map(member => member.id); @@ -354,18 +356,25 @@ export class TeamRepository extends TypedEventEmitter { async filterExternals(users: User[]): Promise { const teamId = this.teamState.team()?.id; - if (!teamId) { + if (teamId === undefined || teamId === '') { return users; } const userIds = users.map(({id}) => id); const members = await this.teamService.getTeamMembersByIds(teamId, userIds); return members .filter(member => roleFromTeamPermissions(member.permissions) !== ROLE.PARTNER) - .map(({user}) => users.find(({id}) => id === user)); + .map(({user}) => users.find(({id}) => id === user)) + .filter((user): user is User => { + return user !== undefined; + }); } getTeamConversationRoles(): Promise { - return this.teamService.getTeamConversationRoles(this.teamState.team().id); + const teamId = this.teamState.team()?.id; + if (teamId === undefined) { + return Promise.reject(new Error('Team id is not available')); + } + return this.teamService.getTeamConversationRoles(teamId); } async getWhitelistedServices(teamId: string, domain: string): Promise { diff --git a/apps/webapp/src/script/repositories/tracking/EventTrackingRepository.ts b/apps/webapp/src/script/repositories/tracking/EventTrackingRepository.ts index 04fe2c7a3ce..0313789d631 100644 --- a/apps/webapp/src/script/repositories/tracking/EventTrackingRepository.ts +++ b/apps/webapp/src/script/repositories/tracking/EventTrackingRepository.ts @@ -159,9 +159,13 @@ export class EventTrackingRepository { EventTrackingRepository.CONFIG.USER_ANALYTICS.COUNTLY_SYNCED_AT_LEAST_ONCE_LOCAL_STORAGE_KEY, ); - if (unsyncedTelemetryDeviceId) { + if ( + unsyncedTelemetryDeviceId !== undefined && + unsyncedTelemetryDeviceId !== null && + unsyncedTelemetryDeviceId !== '' + ) { try { - await this.messageRepository.sendCountlySync(this.telemetryDeviceId); + await this.messageRepository.sendCountlySync(this.telemetryDeviceId ?? ''); resetStoreValue(EventTrackingRepository.CONFIG.USER_ANALYTICS.COUNTLY_UNSYNCED_DEVICE_ID_LOCAL_STORAGE_KEY); } catch (error: unknown) { this.telemetryLogger.warn(`Failed to send new telemetry tracking id to other devices ${error}`); diff --git a/apps/webapp/src/script/repositories/tracking/Helpers.ts b/apps/webapp/src/script/repositories/tracking/Helpers.ts index 1acd107bd5c..c553b781de5 100644 --- a/apps/webapp/src/script/repositories/tracking/Helpers.ts +++ b/apps/webapp/src/script/repositories/tracking/Helpers.ts @@ -54,7 +54,8 @@ export function getGuestAttributes(conversationEntity: Conversation): GuestAttri if (isTeamConversation) { const isAllowGuests = !conversationEntity.isTeamOnly(); const _getUserType = (_conversationEntity: Conversation) => { - if (_conversationEntity.selfUser().isTemporaryGuest()) { + const selfUser = _conversationEntity.selfUser(); + if (selfUser !== undefined && selfUser.isTemporaryGuest()) { return UserType.TEMPORARY_GUEST; } diff --git a/apps/webapp/src/script/repositories/user/AppLockRepository.ts b/apps/webapp/src/script/repositories/user/AppLockRepository.ts index ff3a26d10a4..32ac9b3d1a5 100644 --- a/apps/webapp/src/script/repositories/user/AppLockRepository.ts +++ b/apps/webapp/src/script/repositories/user/AppLockRepository.ts @@ -89,7 +89,7 @@ export class AppLockRepository { handlePassphraseStorageEvent = ({key, oldValue}: StorageEvent): void => { const storageKey = this.getPassphraseStorageKey(); - if (key === storageKey) { + if (key === storageKey && oldValue !== null) { window.localStorage.setItem(storageKey, oldValue); } }; diff --git a/apps/webapp/src/script/repositories/user/AppLockState.ts b/apps/webapp/src/script/repositories/user/AppLockState.ts index c207ce62ddb..3ec18cdc8af 100644 --- a/apps/webapp/src/script/repositories/user/AppLockState.ts +++ b/apps/webapp/src/script/repositories/user/AppLockState.ts @@ -43,19 +43,22 @@ export class AppLockState { () => teamState.isTeam() && teamState.teamFeatures()?.appLock?.status !== FEATURE_STATUS.ENABLED, ); - this.isAppLockAvailable = ko.pureComputed(() => - teamState.isTeam() ? teamState.teamFeatures()?.appLock?.status === FEATURE_STATUS.ENABLED : defaultEnabled, - ); + this.isAppLockAvailable = ko.pureComputed(() => { + return teamState.isTeam() ? teamState.teamFeatures()?.appLock?.status === FEATURE_STATUS.ENABLED : defaultEnabled; + }); - this.isAppLockEnforced = ko.pureComputed( - () => - this.isAppLockAvailable() && - (teamState.isTeam() ? teamState.teamFeatures()?.appLock?.config?.enforceAppLock : defaultEnforced), - ); + this.isAppLockEnforced = ko.pureComputed(() => { + const appLockEnforced = teamState.isTeam() + ? (teamState.teamFeatures()?.appLock?.config?.enforceAppLock ?? false) + : defaultEnforced; + return this.isAppLockAvailable() && appLockEnforced; + }); - this.appLockInactivityTimeoutSecs = ko.pureComputed(() => - teamState.isTeam() ? teamState.teamFeatures()?.appLock?.config?.inactivityTimeoutSecs : defaultTimeoutSecs, - ); + this.appLockInactivityTimeoutSecs = ko.pureComputed(() => { + return teamState.isTeam() + ? (teamState.teamFeatures()?.appLock?.config?.inactivityTimeoutSecs ?? defaultTimeoutSecs) + : defaultTimeoutSecs; + }); this.isAppLockActivated = ko.pureComputed(() => this.isAppLockEnabled() && this.hasPassphrase()); this.hasPassphrase = ko.observable(false); diff --git a/apps/webapp/src/script/repositories/user/UserHandleGenerator.ts b/apps/webapp/src/script/repositories/user/UserHandleGenerator.ts index 33717898365..4a947f330f6 100644 --- a/apps/webapp/src/script/repositories/user/UserHandleGenerator.ts +++ b/apps/webapp/src/script/repositories/user/UserHandleGenerator.ts @@ -65,7 +65,9 @@ export const validateCharacter = (character: string): boolean => { * Appends random digits from 1 to 9 to the end of the string. */ export const appendRandomDigits = (handle: string, additionalNumbers?: number): string => { - const randomDigits = Array.from({length: additionalNumbers}, () => getRandomNumber(1, 8)); + const randomDigits = Array.from({length: additionalNumbers ?? 0}, () => { + return getRandomNumber(1, 8); + }); return `${handle}${randomDigits.join('')}`; }; diff --git a/apps/webapp/src/script/repositories/user/UserRepository.ts b/apps/webapp/src/script/repositories/user/UserRepository.ts index b454e2fea16..12a07d143a6 100644 --- a/apps/webapp/src/script/repositories/user/UserRepository.ts +++ b/apps/webapp/src/script/repositories/user/UserRepository.ts @@ -384,7 +384,7 @@ export class UserRepository extends TypedEventEmitter { const recipients = await this.clientRepository.getAllClientsFromDb(); const userIds: QualifiedId[] = Object.entries(recipients).map(([userId, clientEntities]) => { return { - domain: clientEntities[0].domain, + domain: clientEntities[0]?.domain ?? '', id: userId, }; }); diff --git a/apps/webapp/src/script/repositories/user/UserState.ts b/apps/webapp/src/script/repositories/user/UserState.ts index 8428513ebef..4396bd580ba 100644 --- a/apps/webapp/src/script/repositories/user/UserState.ts +++ b/apps/webapp/src/script/repositories/user/UserState.ts @@ -26,7 +26,7 @@ import {TIME_IN_MILLIS} from 'Util/timeUtil'; @singleton() export class UserState { - public readonly self = ko.observable(); + public readonly self = ko.observable(new User('', '')); /** All the users we know of (connected users, conversation users, team members, users we have searched for...) */ public readonly users = ko.observableArray([]); /** All the users that are directly connect to the self user (do not include users that are connected through conversations) */ diff --git a/apps/webapp/src/script/telemetry/app_init/AppInitTimings.ts b/apps/webapp/src/script/telemetry/app_init/AppInitTimings.ts index 172f24090a4..2e56e596bb5 100644 --- a/apps/webapp/src/script/telemetry/app_init/AppInitTimings.ts +++ b/apps/webapp/src/script/telemetry/app_init/AppInitTimings.ts @@ -48,7 +48,7 @@ export class AppInitTimings { } getAppLoad(): number { - const appLoaded = this.timings[AppInitTimingsStep.APP_LOADED]; + const appLoaded = this.timings[AppInitTimingsStep.APP_LOADED] ?? 0; const appLoadedInSeconds = appLoaded / TIME_IN_MILLIS.SECOND; return Math.floor(appLoadedInSeconds); diff --git a/apps/webapp/src/script/time/serverTimeHandler.ts b/apps/webapp/src/script/time/serverTimeHandler.ts index 3bdc30de059..82755a73251 100644 --- a/apps/webapp/src/script/time/serverTimeHandler.ts +++ b/apps/webapp/src/script/time/serverTimeHandler.ts @@ -37,7 +37,7 @@ export const serverTimeHandler = { }, logger: getLogger('serverTimeHandler'), - timeOffset: ko.observable(undefined) as ko.Observable, + timeOffset: ko.observable(undefined) as unknown as ko.Observable, /** * Converts a server timestamp to a local timestamp. diff --git a/apps/webapp/src/script/ui/Shortcut.ts b/apps/webapp/src/script/ui/Shortcut.ts index 6034f96c4d2..c55a0f00eca 100644 --- a/apps/webapp/src/script/ui/Shortcut.ts +++ b/apps/webapp/src/script/ui/Shortcut.ts @@ -165,11 +165,14 @@ const _registerEvent = (platformSpecificShortcut: string, event: string): void = } return keyboardJS.bind(platformSpecificShortcut, inputEvent => { + if (inputEvent === undefined) { + return; + } keyboardJS.releaseKey(inputEvent.keyCode); // Hotfix WEBAPP-1916 - const ignoreEvent = includesString(platformSpecificShortcut, 'command') && !inputEvent.metaKey; - if (!ignoreEvent) { + const ignoreEvent = includesString(platformSpecificShortcut, 'command') && inputEvent.metaKey === false; + if (ignoreEvent === false) { inputEvent.preventDefault(); amplify.publish(event); } diff --git a/apps/webapp/src/script/util/arrayUtil.ts b/apps/webapp/src/script/util/arrayUtil.ts index a117905fca4..6e4b11481f4 100644 --- a/apps/webapp/src/script/util/arrayUtil.ts +++ b/apps/webapp/src/script/util/arrayUtil.ts @@ -105,7 +105,8 @@ export const iterateItem = (array: T[], currentItem: T, reverse = false): T | // If item could not be found const isNegativeIndex = currentIndex === -1; - return isNegativeIndex ? undefined : array[iterateIndex(array, currentIndex, reverse)]; + const nextIndex = iterateIndex(array, currentIndex, reverse); + return isNegativeIndex || nextIndex === undefined ? undefined : array[nextIndex]; } return undefined; }; diff --git a/apps/webapp/src/script/util/clipboardUtil.ts b/apps/webapp/src/script/util/clipboardUtil.ts index e496e256739..b8c7379710a 100644 --- a/apps/webapp/src/script/util/clipboardUtil.ts +++ b/apps/webapp/src/script/util/clipboardUtil.ts @@ -30,8 +30,10 @@ export function copyText(text: string): Promise { let selectedRange; - if (window.getSelection) { - selectedRange = window.getSelection().rangeCount ? window.getSelection().getRangeAt(0) : undefined; + if (window.getSelection !== undefined) { + const currentSelection = window.getSelection(); + selectedRange = + currentSelection !== null && currentSelection.rangeCount > 0 ? currentSelection.getRangeAt(0) : undefined; } document.body.appendChild(fallbackSource); @@ -39,10 +41,12 @@ export function copyText(text: string): Promise { document.execCommand('copy'); document.body.removeChild(fallbackSource); - if (window.getSelection && selectedRange) { + if (window.getSelection !== undefined && selectedRange !== undefined) { const currentSelection = window.getSelection(); - currentSelection.removeAllRanges(); - currentSelection.addRange(selectedRange); + if (currentSelection !== null) { + currentSelection.removeAllRanges(); + currentSelection.addRange(selectedRange); + } } return Promise.resolve(); diff --git a/apps/webapp/src/script/util/debugUtil.ts b/apps/webapp/src/script/util/debugUtil.ts index fb786765f5e..16aec86599a 100644 --- a/apps/webapp/src/script/util/debugUtil.ts +++ b/apps/webapp/src/script/util/debugUtil.ts @@ -473,12 +473,13 @@ export class DebugUtil { /** Used by QA test automation. */ isSendingMessage(): boolean { - return this.core.service!.conversation.isSendingMessage(); + const coreService = this.core.service; + return coreService !== undefined ? coreService.conversation.isSendingMessage() : false; } async getLastMessagesFromDatabase( amount = 10, - conversationId = this.conversationState.activeConversation().id, + conversationId = this.conversationState.activeConversation()?.id ?? '', ): Promise { if (this.storageRepository.storageService.db) { const records = await this.storageRepository.storageService.db.events.toArray(); @@ -490,7 +491,7 @@ export class DebugUtil { async haveISentThisMessageToMyOtherClients( messageId: string, - conversationId: string = this.conversationState.activeConversation().id, + conversationId: string = this.conversationState.activeConversation()?.id ?? '', ): Promise { const clientId = this.clientState.currentClient?.id; const userId = this.userState.self().id; @@ -532,8 +533,9 @@ export class DebugUtil { async getEventInfo( event: ConversationEvent, ): Promise<{conversation: Conversation; event: ConversationEvent; user: User}> { - const conversation = await this.conversationRepository.getConversationById(event.qualified_conversation); - const user = await this.userRepository.getUserById(event.qualified_from || {domain: '', id: event.from}); + const conversationId = event.qualified_conversation ?? {domain: '', id: event.conversation}; + const conversation = await this.conversationRepository.getConversationById(conversationId); + const user = await this.userRepository.getUserById(event.qualified_from ?? {domain: '', id: event.from}); const debugInformation = { conversation, @@ -610,6 +612,9 @@ export class DebugUtil { canvas.height = height; canvas.width = width; const ctx = canvas.getContext('2d'); + if (ctx === null) { + return []; + } setInterval(() => { ctx.fillStyle = `#${color}`; ctx.fillRect(0, 0, width, height); @@ -621,13 +626,19 @@ export class DebugUtil { return stream.getVideoTracks(); } - navigator.mediaDevices.ondevicechange(null); + const deviceChangeHandler = navigator.mediaDevices.ondevicechange; + if (deviceChangeHandler !== null) { + deviceChangeHandler.call(navigator.mediaDevices, new Event('devicechange')); + } } injectLegalHoldLeaveEvent(includeSelf = false, maxUsers = Infinity) { const conversation = this.conversationState.activeConversation(); + if (conversation === undefined) { + return Promise.resolve(); + } let users = []; - if (includeSelf) { + if (includeSelf === true) { users.push(this.userState.self().qualifiedId); } users.push(...conversation.participating_user_ids()); @@ -652,6 +663,9 @@ export class DebugUtil { blockUserForLegalHold(userId: string) { const conversation = this.conversationState.activeConversation(); + if (conversation === undefined) { + return Promise.resolve(); + } return this.eventRepository['handleEvent']( { event: { diff --git a/apps/webapp/src/script/util/sanitizationUtil.ts b/apps/webapp/src/script/util/sanitizationUtil.ts index 31b13f8203f..83938c4ce5c 100644 --- a/apps/webapp/src/script/util/sanitizationUtil.ts +++ b/apps/webapp/src/script/util/sanitizationUtil.ts @@ -20,7 +20,9 @@ import {prependProtocol} from './urlUtil'; import {isValidEmail} from './validationUtil'; -export const escapeRegex = (string: string): string => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +export const escapeRegex = (string: string): string => { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +}; /** * Opens a new browser tab (target="_blank") with a given URL in a safe environment. @@ -29,12 +31,12 @@ export const escapeRegex = (string: string): string => string.replace(/[.*+?^${} * @param focus `true`, if the new windows should get browser focus * @returns New window handle */ -export const safeWindowOpen = (url: string, focus: boolean = true): Window => { +export const safeWindowOpen = (url: string, focus: boolean = true): Window | null => { const newWindow = window.open(prependProtocol(url)); - if (newWindow) { + if (newWindow !== null) { newWindow.opener = null; - if (focus) { + if (focus === true) { newWindow.focus(); } } diff --git a/apps/webapp/src/script/util/test/mock/localStorageMock.ts b/apps/webapp/src/script/util/test/mock/localStorageMock.ts index a93b93c9e05..3333d55cf1e 100644 --- a/apps/webapp/src/script/util/test/mock/localStorageMock.ts +++ b/apps/webapp/src/script/util/test/mock/localStorageMock.ts @@ -28,7 +28,7 @@ class LocalStorageMock { this.store = {}; }; removeItem = (key: string) => { - this.store[key] = null; + delete this.store[key]; }; } Object.defineProperty(window, 'localStorage', {value: new LocalStorageMock()}); diff --git a/apps/webapp/src/script/util/util.ts b/apps/webapp/src/script/util/util.ts index 5bea3dc45d2..ff65f75957f 100644 --- a/apps/webapp/src/script/util/util.ts +++ b/apps/webapp/src/script/util/util.ts @@ -80,7 +80,13 @@ export const checkIndexedDb = (): Promise => { export const loadDataUrl = (file: Blob): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = () => resolve(reader.result); + reader.onload = () => { + if (reader.result !== null) { + resolve(reader.result); + } else { + reject(new Error('File reader result is empty')); + } + }; reader.onerror = reject; reader.readAsDataURL(file); }); @@ -98,7 +104,7 @@ const loadUrlBuffer = ( xhr.onload = () => { const isStatusOK = xhr.status === HTTP_STATUS.OK; return isStatusOK - ? resolve({buffer: xhr.response, mimeType: xhr.getResponseHeader('content-type')}) + ? resolve({buffer: xhr.response, mimeType: xhr.getResponseHeader('content-type') ?? ''}) : reject(new Error(xhr.status.toString(10))); }; @@ -217,7 +223,7 @@ export const formatBytes = (bytes: number, decimals: number = 1): string => { }; export const getContentTypeFromDataUrl = (dataUrl: string): string => { - return dataUrl.match(/^.*:(.*);.*,/)[1]; + return dataUrl.match(/^.*:(.*);.*,/)?.[1] ?? ''; }; export const stripDataUri = (string: string): string => string.replace(/^data:.*,/, ''); @@ -354,8 +360,9 @@ export const preventFocusOutside = ( } event.preventDefault(); const parent = targetDocument.getElementById(parentId); - const focusableContent = parent ? [...parent.querySelectorAll(focusableElementsSelector)] : []; - const focusedItemIndex = focusableContent.indexOf(targetDocument.activeElement); + const focusableContent = parent !== null ? [...parent.querySelectorAll(focusableElementsSelector)] : []; + const activeElement = targetDocument.activeElement; + const focusedItemIndex = activeElement !== null ? focusableContent.indexOf(activeElement) : -1; if (event.shiftKey && focusedItemIndex != 0) { (focusableContent[focusedItemIndex - 1] as HTMLElement)?.focus(); return; diff --git a/apps/webapp/src/script/view_model/CallingViewModel.ts b/apps/webapp/src/script/view_model/CallingViewModel.ts index 16a63128f69..c6602c4a0fa 100644 --- a/apps/webapp/src/script/view_model/CallingViewModel.ts +++ b/apps/webapp/src/script/view_model/CallingViewModel.ts @@ -287,7 +287,12 @@ export class CallingViewModel { this.callingRepository.rejectCall(conversation.qualifiedId); }, startAudio: async (conversationEntity: Conversation) => { - if (conversationEntity.isGroupOrChannel() && !this.teamState.isConferenceCallingEnabled()) { + const conferenceCallingEnabledState = this.teamState.isConferenceCallingEnabled; + const isConferenceCallingEnabled = + typeof conferenceCallingEnabledState === 'function' + ? conferenceCallingEnabledState() + : conferenceCallingEnabledState; + if (conversationEntity.isGroupOrChannel() && isConferenceCallingEnabled === false) { this.showRestrictedConferenceCallingModal(); } else { await handleCallAction(conversationEntity); @@ -307,7 +312,9 @@ export class CallingViewModel { this.callingRepository.muteCall(call, muteState); }, toggleScreenshare: async (call, desktopScreenShareMenu): Promise => { - if (call.getSelfParticipant().sharesScreen()) { + const selfParticipant = call.getSelfParticipant(); + const hasSelfParticipant = selfParticipant !== undefined; + if (hasSelfParticipant && selfParticipant.sharesScreen()) { return this.callingRepository.toggleScreenshare(call); } const showScreenSelection = (): Promise => { diff --git a/apps/webapp/src/script/view_model/ListViewModel.ts b/apps/webapp/src/script/view_model/ListViewModel.ts index 33ada5fa8eb..25aaabde0d2 100644 --- a/apps/webapp/src/script/view_model/ListViewModel.ts +++ b/apps/webapp/src/script/view_model/ListViewModel.ts @@ -99,10 +99,12 @@ export class ListViewModel { this.isProAccount = this.teamState.isTeam; this.selfUser = this.userState.self; - this.isActivatedAccount = ko.pureComputed(() => this.selfUser()?.isActivatedAccount()); + this.isActivatedAccount = ko.pureComputed(() => { + return this.selfUser().isActivatedAccount(); + }); // State - this.lastUpdate = ko.observable(); + this.lastUpdate = ko.observable(0); this.visibleListItems = ko.pureComputed(() => { const {listState} = useAppState.getState(); @@ -170,7 +172,7 @@ export class ListViewModel { readonly changeNotificationSetting = () => { if (this.isProAccount()) { const {rightSidebar} = useAppMainState.getState(); - rightSidebar.goTo(PanelState.NOTIFICATIONS, {entity: this.conversationState.activeConversation()}); + rightSidebar.goTo(PanelState.NOTIFICATIONS, {entity: this.conversationState.activeConversation() ?? null}); } else { this.clickToToggleMute(); } @@ -470,7 +472,7 @@ export class ListViewModel { }; readonly clickToArchive = (conversationEntity = this.conversationState.activeConversation()): void => { - if (this.isActivatedAccount()) { + if (this.isActivatedAccount() && conversationEntity !== undefined) { this.actionsViewModel.archiveConversation(conversationEntity); } }; @@ -478,7 +480,7 @@ export class ListViewModel { clickToBlock = async (conversationEntity: Conversation): Promise => { const userEntity = conversationEntity.firstUserEntity(); - if (!userEntity) { + if (userEntity === undefined) { return; } @@ -487,7 +489,7 @@ export class ListViewModel { clickToUnblock = async (conversationEntity: Conversation): Promise => { const userEntity = conversationEntity.firstUserEntity(); - if (!userEntity) { + if (userEntity === undefined) { return; } await this.actionsViewModel.unblockUser(userEntity); @@ -495,6 +497,9 @@ export class ListViewModel { readonly clickToCancelRequest = (conversationEntity: Conversation): void => { const userEntity = conversationEntity.firstUserEntity(); + if (userEntity === undefined) { + return; + } const hideConversation = this.shouldHideConversation(conversationEntity); const nextConversationEntity = this.conversationRepository.getNextConversation(conversationEntity); @@ -502,7 +507,9 @@ export class ListViewModel { }; readonly clickToClear = (conversationEntity = this.conversationState.activeConversation()): void => { - this.actionsViewModel.clearConversation(conversationEntity); + if (conversationEntity !== undefined) { + this.actionsViewModel.clearConversation(conversationEntity); + } }; readonly clickToLeave = (conversationEntity: Conversation): void => { @@ -510,7 +517,9 @@ export class ListViewModel { }; readonly clickToToggleMute = (conversationEntity = this.conversationState.activeConversation()): void => { - this.actionsViewModel.toggleMuteConversation(conversationEntity); + if (conversationEntity !== undefined) { + this.actionsViewModel.toggleMuteConversation(conversationEntity); + } }; readonly clickToOpenNotificationSettings = ( diff --git a/apps/webapp/tsconfig.json b/apps/webapp/tsconfig.json index 80621983d5f..62c9e59e0d2 100644 --- a/apps/webapp/tsconfig.json +++ b/apps/webapp/tsconfig.json @@ -27,6 +27,7 @@ "rootDirs": ["src"], "target": "ESNext", "strict": true, + "strictPropertyInitialization": false, "strictFunctionTypes": false }, "types": ["@types/wicg-file-system-access", "jest", "@testing-library/jest-dom"], diff --git a/libraries/core/src/account.ts b/libraries/core/src/account.ts index d6af590b138..c862bdf2a8e 100644 --- a/libraries/core/src/account.ts +++ b/libraries/core/src/account.ts @@ -27,18 +27,18 @@ import { LoginData, PreKey, } from '@wireapp/api-client/lib/auth'; -import {ClientCapability, ClientClassification, ClientType, RegisteredClient} from '@wireapp/api-client/lib/client/'; +import {ClientCapability, ClientClassification, ClientType, RegisteredClient} from '@wireapp/api-client/lib/client'; import {SUBCONVERSATION_ID} from '@wireapp/api-client/lib/conversation'; import * as Events from '@wireapp/api-client/lib/event'; import {CONVERSATION_EVENT} from '@wireapp/api-client/lib/event'; -import {Notification} from '@wireapp/api-client/lib/notification/'; +import {Notification} from '@wireapp/api-client/lib/notification'; import { ConsumableEvent, ConsumableNotification, ConsumableNotificationEvent, ConsumableNotificationSynchronization, } from '@wireapp/api-client/lib/notification/consumableNotification'; -import {WebSocketClient} from '@wireapp/api-client/lib/tcp/'; +import {WebSocketClient} from '@wireapp/api-client/lib/tcp'; import {WEBSOCKET_STATE} from '@wireapp/api-client/lib/tcp/reconnectingWebsocket'; import {FEATURE_KEY, FEATURE_STATUS} from '@wireapp/api-client/lib/team'; import {QualifiedId} from '@wireapp/api-client/lib/user'; diff --git a/libraries/core/src/auth/loginSanitizer.test.ts b/libraries/core/src/auth/loginSanitizer.test.ts index d06c2d3acc2..aa9ed3c44bd 100644 --- a/libraries/core/src/auth/loginSanitizer.test.ts +++ b/libraries/core/src/auth/loginSanitizer.test.ts @@ -17,7 +17,7 @@ * */ -import {LoginData} from '@wireapp/api-client/lib/auth/'; +import {LoginData} from '@wireapp/api-client/lib/auth'; import {ClientType} from '@wireapp/api-client/lib/client'; import {LoginSanitizer} from './loginSanitizer'; diff --git a/libraries/core/src/auth/loginSanitizer.ts b/libraries/core/src/auth/loginSanitizer.ts index 14d85dcabe5..3c898d8aced 100644 --- a/libraries/core/src/auth/loginSanitizer.ts +++ b/libraries/core/src/auth/loginSanitizer.ts @@ -17,7 +17,7 @@ * */ -import {LoginData} from '@wireapp/api-client/lib/auth/'; +import {LoginData} from '@wireapp/api-client/lib/auth'; export class LoginSanitizer { constructor() {} diff --git a/libraries/core/src/client/clientBackendRepository.ts b/libraries/core/src/client/clientBackendRepository.ts index 6e2c0532dec..3de320a547c 100644 --- a/libraries/core/src/client/clientBackendRepository.ts +++ b/libraries/core/src/client/clientBackendRepository.ts @@ -17,7 +17,7 @@ * */ -import {CreateClientPayload, RegisteredClient, UpdateClientPayload} from '@wireapp/api-client/lib/client/'; +import {CreateClientPayload, RegisteredClient, UpdateClientPayload} from '@wireapp/api-client/lib/client'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/client/clientDatabaseRepository.ts b/libraries/core/src/client/clientDatabaseRepository.ts index 0ce22183c34..2e0cd37d200 100644 --- a/libraries/core/src/client/clientDatabaseRepository.ts +++ b/libraries/core/src/client/clientDatabaseRepository.ts @@ -17,7 +17,7 @@ * */ -import {RegisteredClient} from '@wireapp/api-client/lib/client/'; +import {RegisteredClient} from '@wireapp/api-client/lib/client'; import {QualifiedId} from '@wireapp/api-client/lib/user'; import {CRUDEngine} from '@wireapp/store-engine'; diff --git a/libraries/core/src/client/clientInfo.ts b/libraries/core/src/client/clientInfo.ts index ff8007408a7..7e6be0416ca 100644 --- a/libraries/core/src/client/clientInfo.ts +++ b/libraries/core/src/client/clientInfo.ts @@ -17,7 +17,7 @@ * */ -import {ClientClassification, Location} from '@wireapp/api-client/lib/client/'; +import {ClientClassification, Location} from '@wireapp/api-client/lib/client'; export interface ClientInfo { classification: ClientClassification.DESKTOP | ClientClassification.PHONE | ClientClassification.TABLET; diff --git a/libraries/core/src/client/clientService.ts b/libraries/core/src/client/clientService.ts index 578d3ea51a7..e9ded3d28e7 100644 --- a/libraries/core/src/client/clientService.ts +++ b/libraries/core/src/client/clientService.ts @@ -17,14 +17,14 @@ * */ -import {LoginData} from '@wireapp/api-client/lib/auth/'; +import {LoginData} from '@wireapp/api-client/lib/auth'; import { ClientCapability, ClientCapabilityData, ClientType, CreateClientPayload, RegisteredClient, -} from '@wireapp/api-client/lib/client/'; +} from '@wireapp/api-client/lib/client'; import {QualifiedId} from '@wireapp/api-client/lib/user'; import axios from 'axios'; import {StatusCodes} from 'http-status-codes'; diff --git a/libraries/core/src/connection/connectionService.ts b/libraries/core/src/connection/connectionService.ts index 81aaac5bc34..34fb7616062 100644 --- a/libraries/core/src/connection/connectionService.ts +++ b/libraries/core/src/connection/connectionService.ts @@ -17,7 +17,7 @@ * */ -import {Connection, ConnectionStatus} from '@wireapp/api-client/lib/connection/'; +import {Connection, ConnectionStatus} from '@wireapp/api-client/lib/connection'; import {QualifiedId} from '@wireapp/api-client/lib/user'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/conversation/content/clientAddContent.ts b/libraries/core/src/conversation/content/clientAddContent.ts index b857b5fc3f2..3d20cb9e73d 100644 --- a/libraries/core/src/conversation/content/clientAddContent.ts +++ b/libraries/core/src/conversation/content/clientAddContent.ts @@ -17,7 +17,7 @@ * */ -import {RegisteredClient} from '@wireapp/api-client/lib/client/'; +import {RegisteredClient} from '@wireapp/api-client/lib/client'; export interface ClientAddContent { client: RegisteredClient; diff --git a/libraries/core/src/conversation/content/contentType.guards.ts b/libraries/core/src/conversation/content/contentType.guards.ts index d2a035d857a..38aa33055fe 100644 --- a/libraries/core/src/conversation/content/contentType.guards.ts +++ b/libraries/core/src/conversation/content/contentType.guards.ts @@ -17,7 +17,7 @@ * */ -import {Connection} from '@wireapp/api-client/lib/connection/'; +import {Connection} from '@wireapp/api-client/lib/connection'; import {ClientActionType} from '..'; diff --git a/libraries/core/src/conversation/content/index.ts b/libraries/core/src/conversation/content/index.ts index 6af99ff0250..4c0a238391c 100644 --- a/libraries/core/src/conversation/content/index.ts +++ b/libraries/core/src/conversation/content/index.ts @@ -18,7 +18,7 @@ */ export {LegalHoldStatus} from '@wireapp/protocol-messaging'; -export {Connection as ConnectionContent} from '@wireapp/api-client/lib/connection/'; +export {Connection as ConnectionContent} from '@wireapp/api-client/lib/connection'; import * as ContentType from './contentType.guards'; export {ContentType}; diff --git a/libraries/core/src/conversation/message/payloadBundle.ts b/libraries/core/src/conversation/message/payloadBundle.ts index b172113bee8..8379185629e 100644 --- a/libraries/core/src/conversation/message/payloadBundle.ts +++ b/libraries/core/src/conversation/message/payloadBundle.ts @@ -17,7 +17,7 @@ * */ -import {ConversationEventData, TeamEventData, UserEventData} from '@wireapp/api-client/lib/event/'; +import {ConversationEventData, TeamEventData, UserEventData} from '@wireapp/api-client/lib/event'; import {QualifiedId} from '@wireapp/api-client/lib/user'; import {Message} from './message'; diff --git a/libraries/core/src/conversation/message/recipientsHelper.ts b/libraries/core/src/conversation/message/recipientsHelper.ts index cab7fd3da43..124bd0a319f 100644 --- a/libraries/core/src/conversation/message/recipientsHelper.ts +++ b/libraries/core/src/conversation/message/recipientsHelper.ts @@ -17,7 +17,7 @@ * */ -import {OTRClientMap, OTRRecipients} from '@wireapp/api-client/lib/conversation/'; +import {OTRClientMap, OTRRecipients} from '@wireapp/api-client/lib/conversation'; import {Encoder} from 'bazinga64'; export function recipientsToBase64(recipients: OTRRecipients): OTRRecipients { diff --git a/libraries/core/src/cryptography/genericMessageMapper.ts b/libraries/core/src/cryptography/genericMessageMapper.ts index 444bf921b35..cafcf594cce 100644 --- a/libraries/core/src/cryptography/genericMessageMapper.ts +++ b/libraries/core/src/cryptography/genericMessageMapper.ts @@ -17,7 +17,7 @@ * */ -import {ConversationOtrMessageAddEvent} from '@wireapp/api-client/lib/event/'; +import {ConversationOtrMessageAddEvent} from '@wireapp/api-client/lib/event'; import {LogFactory} from '@wireapp/commons'; diff --git a/libraries/core/src/giphy/giphyService.ts b/libraries/core/src/giphy/giphyService.ts index d9f81ad5dbd..293d2244920 100644 --- a/libraries/core/src/giphy/giphyService.ts +++ b/libraries/core/src/giphy/giphyService.ts @@ -17,7 +17,7 @@ * */ -import {GiphySearchOptions, GiphyMultipleResult, GiphyResult, GIPHY_RATING} from '@wireapp/api-client/lib/giphy/'; +import {GiphySearchOptions, GiphyMultipleResult, GiphyResult, GIPHY_RATING} from '@wireapp/api-client/lib/giphy'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/notification/notificationBackendRepository.ts b/libraries/core/src/notification/notificationBackendRepository.ts index 129d8087331..f5801772650 100644 --- a/libraries/core/src/notification/notificationBackendRepository.ts +++ b/libraries/core/src/notification/notificationBackendRepository.ts @@ -17,7 +17,7 @@ * */ -import {Notification} from '@wireapp/api-client/lib/notification/'; +import {Notification} from '@wireapp/api-client/lib/notification'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/notification/notificationDatabaseRepository.ts b/libraries/core/src/notification/notificationDatabaseRepository.ts index 56ddeb2e1c8..3cf98cf5417 100644 --- a/libraries/core/src/notification/notificationDatabaseRepository.ts +++ b/libraries/core/src/notification/notificationDatabaseRepository.ts @@ -18,7 +18,7 @@ */ import {BackendEvent} from '@wireapp/api-client/lib/event'; -import {Notification} from '@wireapp/api-client/lib/notification/'; +import {Notification} from '@wireapp/api-client/lib/notification'; import {CRUDEngine} from '@wireapp/store-engine'; diff --git a/libraries/core/src/notification/notificationService.ts b/libraries/core/src/notification/notificationService.ts index dbb0a4f1f40..29fc7fdc9e6 100644 --- a/libraries/core/src/notification/notificationService.ts +++ b/libraries/core/src/notification/notificationService.ts @@ -18,7 +18,7 @@ */ import {BackendEvent} from '@wireapp/api-client/lib/event'; -import {Notification} from '@wireapp/api-client/lib/notification/'; +import {Notification} from '@wireapp/api-client/lib/notification'; import {APIClient} from '@wireapp/api-client'; import {LogFactory, TypedEventEmitter} from '@wireapp/commons'; diff --git a/libraries/core/src/self/selfService.ts b/libraries/core/src/self/selfService.ts index cc08931fe56..e80822546ab 100644 --- a/libraries/core/src/self/selfService.ts +++ b/libraries/core/src/self/selfService.ts @@ -17,7 +17,7 @@ * */ -import {Self} from '@wireapp/api-client/lib/self/'; +import {Self} from '@wireapp/api-client/lib/self'; import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/team/teamService.ts b/libraries/core/src/team/teamService.ts index 6768a3bfa52..574e47ac3a3 100644 --- a/libraries/core/src/team/teamService.ts +++ b/libraries/core/src/team/teamService.ts @@ -25,7 +25,7 @@ import { TeamChunkData, TeamData, UpdateTeamData, -} from '@wireapp/api-client/lib/team/'; +} from '@wireapp/api-client/lib/team'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/test/accountHelper.ts b/libraries/core/src/test/accountHelper.ts index 9407c024c81..5817e5463bb 100644 --- a/libraries/core/src/test/accountHelper.ts +++ b/libraries/core/src/test/accountHelper.ts @@ -17,7 +17,7 @@ * */ -const {ClientType} = require('@wireapp/api-client/lib/client/'); +const {ClientType} = require('@wireapp/api-client/lib/client'); const {APIClient} = require('@wireapp/api-client'); const {Account} = require('@wireapp/core'); diff --git a/libraries/core/src/user/userService.ts b/libraries/core/src/user/userService.ts index 37e1009aa54..34236f93271 100644 --- a/libraries/core/src/user/userService.ts +++ b/libraries/core/src/user/userService.ts @@ -18,7 +18,7 @@ */ import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; -import {QualifiedId, User} from '@wireapp/api-client/lib/user/'; +import {QualifiedId, User} from '@wireapp/api-client/lib/user'; import {APIClient} from '@wireapp/api-client'; diff --git a/libraries/core/src/util/typePredicateUtil.ts b/libraries/core/src/util/typePredicateUtil.ts index 11b47ef4499..b7748d15dda 100644 --- a/libraries/core/src/util/typePredicateUtil.ts +++ b/libraries/core/src/util/typePredicateUtil.ts @@ -17,9 +17,9 @@ * */ -import {Conversation, MLSConversation, QualifiedUserClients, UserClients} from '@wireapp/api-client/lib/conversation/'; +import {Conversation, MLSConversation, QualifiedUserClients, UserClients} from '@wireapp/api-client/lib/conversation'; import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; -import {QualifiedId} from '@wireapp/api-client/lib/user/'; +import {QualifiedId} from '@wireapp/api-client/lib/user'; export function isStringArray(obj: any): obj is string[] { return Array.isArray(obj) && (obj.length === 0 || typeof obj[0] === 'string'); From 52bf6b6906142c2f820225a6d53e52085424431f Mon Sep 17 00:00:00 2001 From: Christian Rackerseder Date: Mon, 4 May 2026 11:10:54 +0200 Subject: [PATCH 40/49] ci(actions): remove node20 variable-mapper and inline branch/tag mappings Replaces kanga333/variable-mapper (Node.js 20 action) with native bash mapping steps in publish-and-deploy-webapp.yml to eliminate upcoming GitHub Actions runtime deprecation risk. --- .../workflows/publish-and-deploy-webapp.yml | 102 ++++++++---------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/.github/workflows/publish-and-deploy-webapp.yml b/.github/workflows/publish-and-deploy-webapp.yml index 34b45dc77e3..0c97fbd05b4 100644 --- a/.github/workflows/publish-and-deploy-webapp.yml +++ b/.github/workflows/publish-and-deploy-webapp.yml @@ -77,20 +77,14 @@ jobs: name: 'build-artifact' path: '${{env.BUILD_DIR}}${{env.BUILD_ARTIFACT}}' - - uses: kanga333/variable-mapper@3681b75f5c6c00162721168fb91ab74925eaebcb - id: changelog - with: - key: '${{github.ref}}' - map: | - { - "production": { - "changelog_type": "production" - }, - "staging": { - "changelog_type": "staging" - } - } - export_to: env + - name: Define changelog type + shell: bash + run: | + if [[ "${GITHUB_REF}" == *production* ]]; then + echo "changelog_type=production" >> "$GITHUB_ENV" + elif [[ "${GITHUB_REF}" == *staging* ]]; then + echo "changelog_type=staging" >> "$GITHUB_ENV" + fi - name: Generate changelog id: generate_changelog @@ -157,30 +151,26 @@ jobs: # generates a mapping between branches/tag to wire-build branches - name: Define target branches in wireapp/wire-builds to bump - uses: kanga333/variable-mapper@3681b75f5c6c00162721168fb91ab74925eaebcb id: output_target_branches - with: - key: '${{github.ref}}' - # TODO Add staging if we ever use a wire-builds as a source for staging (we use k8s) - # "staging": { - # "targets": "[\"TBD\"]" - # }, - map: | - { - "production": { - "targets": "[\"main\"]" - }, - "dev": { - "targets": "[\"dev\"]" - }, - "q1-2024": { - "targets": "[\"q1-2024\"]" - }, - "q2-2025": { - "targets": "[\"q2-2025\"]" - } - } - export_to: log,output + shell: bash + run: | + set -eo pipefail + + wire_build_target_branches_json="" + + if [[ "${GITHUB_REF}" == *production* ]]; then + wire_build_target_branches_json='["main"]' + elif [[ "${GITHUB_REF}" == *dev* ]]; then + wire_build_target_branches_json='["dev"]' + elif [[ "${GITHUB_REF}" == *q1-2024* ]]; then + wire_build_target_branches_json='["q1-2024"]' + elif [[ "${GITHUB_REF}" == *q2-2025* ]]; then + wire_build_target_branches_json='["q2-2025"]' + fi + + if [[ -n "${wire_build_target_branches_json}" ]]; then + echo "targets=${wire_build_target_branches_json}" >> "$GITHUB_OUTPUT" + fi publish_wire_builds: name: Bump webapp chart in wire-builds @@ -262,26 +252,24 @@ jobs: steps: # generates a mapping between branches/tag to aws envs to deploy to - - uses: kanga333/variable-mapper@3681b75f5c6c00162721168fb91ab74925eaebcb - id: targets - with: - key: '${{github.ref}}' - map: | - { - "dev": { - "targets": "[\"wire-webapp-dev\", \"wire-webapp-edge\"]" - }, - "master": { - "targets": "[\"wire-webapp-main\"]" - }, - "production": { - "targets": "[\"wire-webapp-prod\"]" - }, - "staging": { - "targets": "[\"wire-webapp-staging\"]" - } - } - export_to: env + - name: Define deployment targets + shell: bash + run: | + deployment_targets_json="" + + if [[ "${GITHUB_REF}" == *dev* ]]; then + deployment_targets_json='["wire-webapp-dev", "wire-webapp-edge"]' + elif [[ "${GITHUB_REF}" == *master* ]]; then + deployment_targets_json='["wire-webapp-main"]' + elif [[ "${GITHUB_REF}" == *production* ]]; then + deployment_targets_json='["wire-webapp-prod"]' + elif [[ "${GITHUB_REF}" == *staging* ]]; then + deployment_targets_json='["wire-webapp-staging"]' + fi + + if [[ -n "${deployment_targets_json}" ]]; then + echo "targets=${deployment_targets_json}" >> "$GITHUB_ENV" + fi deploy_to_aws: name: 'Deploy to live environments' From 467d7fba63f5ce3c74f8006fe5a976a75bae87b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 11:19:44 +0200 Subject: [PATCH 41/49] chore(deps): update webapp build tooling (#21204) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/webapp/package.json | 4 +- yarn.lock | 327 ++------------------------------------- 2 files changed, 14 insertions(+), 317 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index bfa576453f0..9b708b74e4a 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -106,7 +106,7 @@ "@babel/preset-typescript": "7.28.5", "@emotion/eslint-plugin": "11.12.0", "@formatjs/cli": "6.14.4", - "@nx/webpack": "22.7.0", + "@nx/webpack": "22.7.1", "@playwright/test": "1.59.1", "@roamhq/wrtc": "0.10.0", "@testing-library/dom": "10.4.1", @@ -153,7 +153,7 @@ "less-loader": "12.3.2", "os-browserify": "0.3.0", "path-browserify": "1.0.1", - "postcss": "8.5.12", + "postcss": "8.5.13", "postcss-import": "16.1.1", "postcss-less": "6.0.0", "postcss-loader": "8.2.1", diff --git a/yarn.lock b/yarn.lock index 9ea3b827741..7bef4a2994e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5345,23 +5345,6 @@ __metadata: languageName: node linkType: hard -"@nx/devkit@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/devkit@npm:22.7.0" - dependencies: - "@zkochan/js-yaml": "npm:0.0.7" - ejs: "npm:5.0.1" - enquirer: "npm:~2.3.6" - minimatch: "npm:10.2.4" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - peerDependencies: - nx: ">= 21 <= 23 || ^22.0.0-0" - checksum: 10/7dbcfe535d71082e00582d581bc94c6e262fc8e9754874c40dfa3bd7ff758d6873037d868a4501b315d2f93e515ff1e6ea1baecc82e134155a91ad66c73c9d6f - languageName: node - linkType: hard - "@nx/devkit@npm:22.7.1": version: 22.7.1 resolution: "@nx/devkit@npm:22.7.1" @@ -5435,45 +5418,6 @@ __metadata: languageName: node linkType: hard -"@nx/js@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/js@npm:22.7.0" - dependencies: - "@babel/core": "npm:^7.23.2" - "@babel/plugin-proposal-decorators": "npm:^7.22.7" - "@babel/plugin-transform-class-properties": "npm:^7.22.5" - "@babel/plugin-transform-runtime": "npm:^7.23.2" - "@babel/preset-env": "npm:^7.23.2" - "@babel/preset-typescript": "npm:^7.22.5" - "@babel/runtime": "npm:^7.22.6" - "@nx/devkit": "npm:22.7.0" - "@nx/workspace": "npm:22.7.0" - "@zkochan/js-yaml": "npm:0.0.7" - babel-plugin-const-enum: "npm:^1.0.1" - babel-plugin-macros: "npm:^3.1.0" - babel-plugin-transform-typescript-metadata: "npm:^0.3.1" - chalk: "npm:^4.1.0" - columnify: "npm:^1.6.0" - detect-port: "npm:^1.5.1" - ignore: "npm:^5.0.4" - js-tokens: "npm:^4.0.0" - jsonc-parser: "npm:3.2.0" - npm-run-path: "npm:^4.0.1" - picocolors: "npm:^1.1.0" - picomatch: "npm:4.0.4" - semver: "npm:^7.6.3" - source-map-support: "npm:0.5.19" - tinyglobby: "npm:^0.2.12" - tslib: "npm:^2.3.0" - peerDependencies: - verdaccio: ^6.0.5 - peerDependenciesMeta: - verdaccio: - optional: true - checksum: 10/251a671260a322e6bebccb1a45333cb237dc20c99197063e4da66695eb9a021dc1a5033806c25b84b3cfefb0d66b3666acd76179e9469bfbf37bc6397c09d071 - languageName: node - linkType: hard - "@nx/js@npm:22.7.1": version: 22.7.1 resolution: "@nx/js@npm:22.7.1" @@ -5529,13 +5473,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-darwin-arm64@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-darwin-arm64@npm:22.7.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@nx/nx-darwin-arm64@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-darwin-arm64@npm:22.7.1" @@ -5543,13 +5480,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-darwin-x64@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-darwin-x64@npm:22.7.0" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-darwin-x64@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-darwin-x64@npm:22.7.1" @@ -5557,13 +5487,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-freebsd-x64@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-freebsd-x64@npm:22.7.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-freebsd-x64@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-freebsd-x64@npm:22.7.1" @@ -5571,13 +5494,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm-gnueabihf@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.0" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@nx/nx-linux-arm-gnueabihf@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-linux-arm-gnueabihf@npm:22.7.1" @@ -5585,13 +5501,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm64-gnu@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.0" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@nx/nx-linux-arm64-gnu@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-linux-arm64-gnu@npm:22.7.1" @@ -5599,13 +5508,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-arm64-musl@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-linux-arm64-musl@npm:22.7.0" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@nx/nx-linux-arm64-musl@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-linux-arm64-musl@npm:22.7.1" @@ -5613,13 +5515,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-x64-gnu@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-linux-x64-gnu@npm:22.7.0" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@nx/nx-linux-x64-gnu@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-linux-x64-gnu@npm:22.7.1" @@ -5627,13 +5522,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-linux-x64-musl@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-linux-x64-musl@npm:22.7.0" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@nx/nx-linux-x64-musl@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-linux-x64-musl@npm:22.7.1" @@ -5641,13 +5529,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-win32-arm64-msvc@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.0" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@nx/nx-win32-arm64-msvc@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-win32-arm64-msvc@npm:22.7.1" @@ -5655,13 +5536,6 @@ __metadata: languageName: node linkType: hard -"@nx/nx-win32-x64-msvc@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/nx-win32-x64-msvc@npm:22.7.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@nx/nx-win32-x64-msvc@npm:22.7.1": version: 22.7.1 resolution: "@nx/nx-win32-x64-msvc@npm:22.7.1" @@ -5669,13 +5543,13 @@ __metadata: languageName: node linkType: hard -"@nx/webpack@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/webpack@npm:22.7.0" +"@nx/webpack@npm:22.7.1": + version: 22.7.1 + resolution: "@nx/webpack@npm:22.7.1" dependencies: "@babel/core": "npm:^7.23.2" - "@nx/devkit": "npm:22.7.0" - "@nx/js": "npm:22.7.0" + "@nx/devkit": "npm:22.7.1" + "@nx/js": "npm:22.7.1" "@phenomnomnominal/tsquery": "npm:~6.1.4" ajv: "npm:^8.12.0" autoprefixer: "npm:^10.4.9" @@ -5709,24 +5583,7 @@ __metadata: webpack-dev-server: "npm:^5.2.1" webpack-node-externals: "npm:^3.0.0" webpack-subresource-integrity: "npm:^5.1.0" - checksum: 10/fc6f7dfb70a4808080e8f79f7845b1af338df02d26750b072aa084344323206d8cea936f33a61386af1d8349926233566dd9534cf8d5ffa5bac91edff06f4dd3 - languageName: node - linkType: hard - -"@nx/workspace@npm:22.7.0": - version: 22.7.0 - resolution: "@nx/workspace@npm:22.7.0" - dependencies: - "@nx/devkit": "npm:22.7.0" - "@zkochan/js-yaml": "npm:0.0.7" - chalk: "npm:^4.1.0" - enquirer: "npm:~2.3.6" - nx: "npm:22.7.0" - picomatch: "npm:4.0.4" - semver: "npm:^7.6.3" - tslib: "npm:^2.3.0" - yargs-parser: "npm:21.1.1" - checksum: 10/5ca9e145a115242abbffe8a7abc34120125b394337865c2c9890f5b19f9037f26cb9ec89238ecab589c73e42250d1345188b61203f09b05ef6658e3a40cfe666 + checksum: 10/51cb5a388daafc487a79c4dd7ecc0a18dc083c3ce05d634c97b26d35d6d23999d981a3ef8d07c08f3c081b0618564e089e4302e0a05d0d1e7748c4a740f1558d languageName: node linkType: hard @@ -9880,7 +9737,7 @@ __metadata: "@lexical/react": "npm:0.27.2" "@lexical/rich-text": "npm:0.27.2" "@mediapipe/tasks-vision": "npm:0.10.35" - "@nx/webpack": "npm:22.7.0" + "@nx/webpack": "npm:22.7.1" "@playwright/test": "npm:1.59.1" "@roamhq/wrtc": "npm:0.10.0" "@sindresorhus/is": "npm:4.6.0" @@ -9966,7 +9823,7 @@ __metadata: path-browserify: "npm:1.0.1" path-to-regexp: "npm:8.4.2" platform: "npm:1.3.6" - postcss: "npm:8.5.12" + postcss: "npm:8.5.13" postcss-import: "npm:16.1.1" postcss-less: "npm:6.0.0" postcss-loader: "npm:8.2.1" @@ -20418,166 +20275,6 @@ __metadata: languageName: node linkType: hard -"nx@npm:22.7.0": - version: 22.7.0 - resolution: "nx@npm:22.7.0" - dependencies: - "@emnapi/core": "npm:1.4.5" - "@emnapi/runtime": "npm:1.4.5" - "@emnapi/wasi-threads": "npm:1.0.4" - "@jest/diff-sequences": "npm:30.0.1" - "@napi-rs/wasm-runtime": "npm:0.2.4" - "@nx/nx-darwin-arm64": "npm:22.7.0" - "@nx/nx-darwin-x64": "npm:22.7.0" - "@nx/nx-freebsd-x64": "npm:22.7.0" - "@nx/nx-linux-arm-gnueabihf": "npm:22.7.0" - "@nx/nx-linux-arm64-gnu": "npm:22.7.0" - "@nx/nx-linux-arm64-musl": "npm:22.7.0" - "@nx/nx-linux-x64-gnu": "npm:22.7.0" - "@nx/nx-linux-x64-musl": "npm:22.7.0" - "@nx/nx-win32-arm64-msvc": "npm:22.7.0" - "@nx/nx-win32-x64-msvc": "npm:22.7.0" - "@tybys/wasm-util": "npm:0.9.0" - "@yarnpkg/lockfile": "npm:1.1.0" - "@zkochan/js-yaml": "npm:0.0.7" - ansi-colors: "npm:4.1.3" - ansi-regex: "npm:5.0.1" - ansi-styles: "npm:4.3.0" - argparse: "npm:2.0.1" - asynckit: "npm:0.4.0" - axios: "npm:1.15.0" - balanced-match: "npm:4.0.3" - base64-js: "npm:1.5.1" - bl: "npm:4.1.0" - brace-expansion: "npm:5.0.2" - buffer: "npm:5.7.1" - call-bind-apply-helpers: "npm:1.0.2" - chalk: "npm:4.1.2" - cli-cursor: "npm:3.1.0" - cli-spinners: "npm:2.6.1" - cliui: "npm:8.0.1" - clone: "npm:1.0.4" - color-convert: "npm:2.0.1" - color-name: "npm:1.1.4" - combined-stream: "npm:1.0.8" - defaults: "npm:1.0.4" - define-lazy-prop: "npm:2.0.0" - delayed-stream: "npm:1.0.0" - dotenv: "npm:16.4.7" - dotenv-expand: "npm:12.0.3" - dunder-proto: "npm:1.0.1" - ejs: "npm:5.0.1" - emoji-regex: "npm:8.0.0" - end-of-stream: "npm:1.4.5" - enquirer: "npm:2.3.6" - es-define-property: "npm:1.0.1" - es-errors: "npm:1.3.0" - es-object-atoms: "npm:1.1.1" - es-set-tostringtag: "npm:2.1.0" - escalade: "npm:3.2.0" - escape-string-regexp: "npm:1.0.5" - figures: "npm:3.2.0" - flat: "npm:5.0.2" - follow-redirects: "npm:1.15.11" - form-data: "npm:4.0.5" - fs-constants: "npm:1.0.0" - function-bind: "npm:1.1.2" - get-caller-file: "npm:2.0.5" - get-intrinsic: "npm:1.3.0" - get-proto: "npm:1.0.1" - gopd: "npm:1.2.0" - has-flag: "npm:4.0.0" - has-symbols: "npm:1.1.0" - has-tostringtag: "npm:1.0.2" - hasown: "npm:2.0.2" - ieee754: "npm:1.2.1" - ignore: "npm:7.0.5" - inherits: "npm:2.0.4" - is-docker: "npm:2.2.1" - is-fullwidth-code-point: "npm:3.0.0" - is-interactive: "npm:1.0.0" - is-unicode-supported: "npm:0.1.0" - is-wsl: "npm:2.2.0" - json5: "npm:2.2.3" - jsonc-parser: "npm:3.2.0" - lines-and-columns: "npm:2.0.3" - log-symbols: "npm:4.1.0" - math-intrinsics: "npm:1.1.0" - mime-db: "npm:1.52.0" - mime-types: "npm:2.1.35" - mimic-fn: "npm:2.1.0" - minimatch: "npm:10.2.4" - minimist: "npm:1.2.8" - npm-run-path: "npm:4.0.1" - once: "npm:1.4.0" - onetime: "npm:5.1.2" - open: "npm:8.4.2" - ora: "npm:5.3.0" - path-key: "npm:3.1.1" - picocolors: "npm:1.1.1" - proxy-from-env: "npm:2.1.0" - readable-stream: "npm:3.6.2" - require-directory: "npm:2.1.1" - resolve.exports: "npm:2.0.3" - restore-cursor: "npm:3.1.0" - safe-buffer: "npm:5.2.1" - semver: "npm:7.7.4" - signal-exit: "npm:3.0.7" - smol-toml: "npm:1.6.1" - string-width: "npm:4.2.3" - string_decoder: "npm:1.3.0" - strip-ansi: "npm:6.0.1" - strip-bom: "npm:3.0.0" - supports-color: "npm:7.2.0" - tar-stream: "npm:2.2.0" - tmp: "npm:0.2.4" - tree-kill: "npm:1.2.2" - tsconfig-paths: "npm:4.2.0" - tslib: "npm:2.8.1" - util-deprecate: "npm:1.0.2" - wcwidth: "npm:1.0.1" - wrap-ansi: "npm:7.0.0" - wrappy: "npm:1.0.2" - y18n: "npm:5.0.8" - yaml: "npm:2.8.0" - yargs: "npm:17.7.2" - yargs-parser: "npm:21.1.1" - peerDependencies: - "@swc-node/register": ^1.11.1 - "@swc/core": ^1.15.8 - dependenciesMeta: - "@nx/nx-darwin-arm64": - optional: true - "@nx/nx-darwin-x64": - optional: true - "@nx/nx-freebsd-x64": - optional: true - "@nx/nx-linux-arm-gnueabihf": - optional: true - "@nx/nx-linux-arm64-gnu": - optional: true - "@nx/nx-linux-arm64-musl": - optional: true - "@nx/nx-linux-x64-gnu": - optional: true - "@nx/nx-linux-x64-musl": - optional: true - "@nx/nx-win32-arm64-msvc": - optional: true - "@nx/nx-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc-node/register": - optional: true - "@swc/core": - optional: true - bin: - nx: dist/bin/nx.js - nx-cloud: dist/bin/nx-cloud.js - checksum: 10/d00559454655fec9e162c4038440e43e68d57511c00ced818469eae8df8e8335756142ce25d919aefb0bd66b9d33e6ea4323458ffab21a82f1fb32e851a224da - languageName: node - linkType: hard - "nx@npm:22.7.1": version: 22.7.1 resolution: "nx@npm:22.7.1" @@ -22758,14 +22455,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.5.12": - version: 8.5.12 - resolution: "postcss@npm:8.5.12" +"postcss@npm:8.5.13": + version: 8.5.13 + resolution: "postcss@npm:8.5.13" dependencies: nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/ec6b79b68c363eca3c8ffceb134a4ab637274aee6ac0857614bf7c18d40ce4ce5f9036edec57b7e0be99895724d2599d0ec7328dbd7f407204e7548697b322f1 + checksum: 10/4763873c0a39a6f71fc552d0046fedb80dca889ed0b85e36217285f754a3bd003a906f6088feb266b1f53b0ad20483a475dce4cc1c756f05e27940c51e40ebd8 languageName: node linkType: hard From ddc903ba8eed2219de5ba426c8905ff06bf63751 Mon Sep 17 00:00:00 2001 From: Christian Rackerseder Date: Mon, 4 May 2026 11:37:04 +0200 Subject: [PATCH 42/49] fix: preserve runtime body classes to keep conversation selection styling and text selection behavior stable Restore user interface state integrity by ensuring theme and accent updates only modify their own class namespaces instead of replacing the full body class list. --- .../AppContainer/hooks/useAccentColor.ts | 30 ++++++++++++------- .../components/AppContainer/hooks/useTheme.ts | 15 ++++++---- .../content/conversation/message-list.less | 3 ++ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/apps/webapp/src/script/components/AppContainer/hooks/useAccentColor.ts b/apps/webapp/src/script/components/AppContainer/hooks/useAccentColor.ts index cbe6eeb3b20..91939a0fcd1 100644 --- a/apps/webapp/src/script/components/AppContainer/hooks/useAccentColor.ts +++ b/apps/webapp/src/script/components/AppContainer/hooks/useAccentColor.ts @@ -23,16 +23,21 @@ import ko from 'knockout'; import {container} from 'tsyringe'; import {UserState} from 'Repositories/user/UserState'; +import {ACCENT_ID} from 'src/script/Config'; function setAccentColor(accentColor?: number) { - if (!accentColor) { - return; - } - const classes = document.body.className - .split(' ') - .filter(elementClass => !elementClass.startsWith('main-accent-color-')) - .concat(`main-accent-color-${accentColor}`); - document.body.className = classes.join(' '); + const accentColorClassId = accentColor ?? ACCENT_ID.BLUE; + const accentClassPrefix = 'main-accent-color-'; + const bodyClassList = document.body.classList; + const accentClasses = Array.from(bodyClassList).filter(existingClassName => { + return existingClassName.startsWith(accentClassPrefix); + }); + + accentClasses.forEach(existingAccentClass => { + bodyClassList.remove(existingAccentClass); + }); + + bodyClassList.add(`${accentClassPrefix}${accentColorClassId}`); } export function useAccentColor() { @@ -42,9 +47,12 @@ export function useAccentColor() { const selfUser = userState.self(); return selfUser?.accent_id(); }); - const subscription = accentColor.subscribe(accent_color => { - setAccentColor(accent_color); + setAccentColor(accentColor()); + const subscription = accentColor.subscribe(accentColorValue => { + setAccentColor(accentColorValue); }); - return () => subscription.dispose(); + return () => { + return subscription.dispose(); + }; }, []); } diff --git a/apps/webapp/src/script/components/AppContainer/hooks/useTheme.ts b/apps/webapp/src/script/components/AppContainer/hooks/useTheme.ts index cded8c6737a..a4eb12805e2 100644 --- a/apps/webapp/src/script/components/AppContainer/hooks/useTheme.ts +++ b/apps/webapp/src/script/components/AppContainer/hooks/useTheme.ts @@ -28,11 +28,16 @@ const THEMES_CLASS_PREFIX = 'theme-'; export type Theme = WebappProperties['settings']['interface']['theme']; function setTheme(theme: Theme) { - const classes = document.body.className - .split(' ') - .filter(elementClass => !elementClass.startsWith(THEMES_CLASS_PREFIX)) - .concat(`${THEMES_CLASS_PREFIX}${theme}`); - document.body.className = classes.join(' '); + const bodyClassList = document.body.classList; + const themeClasses = Array.from(bodyClassList).filter(existingClassName => { + return existingClassName.startsWith(THEMES_CLASS_PREFIX); + }); + + themeClasses.forEach(existingThemeClass => { + bodyClassList.remove(existingThemeClass); + }); + + bodyClassList.add(`${THEMES_CLASS_PREFIX}${theme}`); } export function useTheme(getTheme: () => Theme) { diff --git a/apps/webapp/src/style/content/conversation/message-list.less b/apps/webapp/src/style/content/conversation/message-list.less index ebc2126284b..9969058fb3a 100644 --- a/apps/webapp/src/style/content/conversation/message-list.less +++ b/apps/webapp/src/style/content/conversation/message-list.less @@ -154,6 +154,7 @@ display: flex; width: 100%; line-height: var(--avatar-diameter-xs); + user-select: text; & > .message-body-actions { top: 6px; @@ -209,6 +210,7 @@ align-items: center; font-size: var(--font-size-small); font-weight: var(--font-weight-regular); + user-select: text; white-space: normal; &:has(.delivered-message-icon) { @@ -284,6 +286,7 @@ .message-body { position: relative; width: 100%; + user-select: text; .text:has(> .iframe-container) { margin-right: 0; From b315667986a47b3e8f573635c09a2b673802a1bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 10:06:39 +0000 Subject: [PATCH 43/49] fix(deps): update aws-sdk-js-v3 monorepo to v3.1040.0 --- libraries/api-client/package.json | 4 +- yarn.lock | 311 +++++++++++++++--------------- 2 files changed, 158 insertions(+), 157 deletions(-) diff --git a/libraries/api-client/package.json b/libraries/api-client/package.json index 8acef77489d..afc418c4507 100644 --- a/libraries/api-client/package.json +++ b/libraries/api-client/package.json @@ -18,8 +18,8 @@ "./lib/shims/node/websocket": "./lib/shims/browser/websocket.js" }, "dependencies": { - "@aws-sdk/client-s3": "3.1037.0", - "@aws-sdk/lib-storage": "3.1037.0", + "@aws-sdk/client-s3": "3.1040.0", + "@aws-sdk/lib-storage": "3.1040.0", "@wireapp/priority-queue": "2.1.19", "@wireapp/protocol-messaging": "1.56.0", "axios-retry": "4.5.0", diff --git a/yarn.lock b/yarn.lock index 7bef4a2994e..834d5124e76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,31 +167,31 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-s3@npm:3.1037.0": - version: 3.1037.0 - resolution: "@aws-sdk/client-s3@npm:3.1037.0" +"@aws-sdk/client-s3@npm:3.1040.0": + version: 3.1040.0 + resolution: "@aws-sdk/client-s3@npm:3.1040.0" dependencies: "@aws-crypto/sha1-browser": "npm:5.2.0" "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/credential-provider-node": "npm:^3.972.36" + "@aws-sdk/core": "npm:^3.974.7" + "@aws-sdk/credential-provider-node": "npm:^3.972.38" "@aws-sdk/middleware-bucket-endpoint": "npm:^3.972.10" "@aws-sdk/middleware-expect-continue": "npm:^3.972.10" - "@aws-sdk/middleware-flexible-checksums": "npm:^3.974.13" + "@aws-sdk/middleware-flexible-checksums": "npm:^3.974.15" "@aws-sdk/middleware-host-header": "npm:^3.972.10" "@aws-sdk/middleware-location-constraint": "npm:^3.972.10" "@aws-sdk/middleware-logger": "npm:^3.972.10" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.11" - "@aws-sdk/middleware-sdk-s3": "npm:^3.972.34" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.36" "@aws-sdk/middleware-ssec": "npm:^3.972.10" - "@aws-sdk/middleware-user-agent": "npm:^3.972.35" + "@aws-sdk/middleware-user-agent": "npm:^3.972.37" "@aws-sdk/region-config-resolver": "npm:^3.972.13" - "@aws-sdk/signature-v4-multi-region": "npm:^3.996.22" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.24" "@aws-sdk/types": "npm:^3.973.8" "@aws-sdk/util-endpoints": "npm:^3.996.8" "@aws-sdk/util-user-agent-browser": "npm:^3.972.10" - "@aws-sdk/util-user-agent-node": "npm:^3.973.21" + "@aws-sdk/util-user-agent-node": "npm:^3.973.23" "@smithy/config-resolver": "npm:^4.4.17" "@smithy/core": "npm:^3.23.17" "@smithy/eventstream-serde-browser": "npm:^4.2.14" @@ -205,7 +205,7 @@ __metadata: "@smithy/md5-js": "npm:^4.2.14" "@smithy/middleware-content-length": "npm:^4.2.14" "@smithy/middleware-endpoint": "npm:^4.4.32" - "@smithy/middleware-retry": "npm:^4.5.5" + "@smithy/middleware-retry": "npm:^4.5.7" "@smithy/middleware-serde": "npm:^4.2.20" "@smithy/middleware-stack": "npm:^4.2.14" "@smithy/node-config-provider": "npm:^4.3.14" @@ -221,21 +221,21 @@ __metadata: "@smithy/util-defaults-mode-node": "npm:^4.2.54" "@smithy/util-endpoints": "npm:^3.4.2" "@smithy/util-middleware": "npm:^4.2.14" - "@smithy/util-retry": "npm:^4.3.4" + "@smithy/util-retry": "npm:^4.3.6" "@smithy/util-stream": "npm:^4.5.25" "@smithy/util-utf8": "npm:^4.2.2" - "@smithy/util-waiter": "npm:^4.2.16" + "@smithy/util-waiter": "npm:^4.3.0" tslib: "npm:^2.6.2" - checksum: 10/957bd108a9a938bb6e8a3451b47d16bb1a4c4a148094450a5af0a2a18391bb038ed53e840d77f6e88b2d274191ca1b0feb317ee54971aa6dff6d4321a1003f34 + checksum: 10/dc61b819554d44d56c23878b0bedd20bd7db8103a7281acd28ab9e1f7773f47e0eb8a9ead5084862c3890e9c51d8d6fcfeb4f4b2af2f99dd3b8c6f337519787d languageName: node linkType: hard -"@aws-sdk/core@npm:^3.974.5": - version: 3.974.5 - resolution: "@aws-sdk/core@npm:3.974.5" +"@aws-sdk/core@npm:^3.974.7, @aws-sdk/core@npm:^3.974.8": + version: 3.974.8 + resolution: "@aws-sdk/core@npm:3.974.8" dependencies: "@aws-sdk/types": "npm:^3.973.8" - "@aws-sdk/xml-builder": "npm:^3.972.19" + "@aws-sdk/xml-builder": "npm:^3.972.22" "@smithy/core": "npm:^3.23.17" "@smithy/node-config-provider": "npm:^4.3.14" "@smithy/property-provider": "npm:^4.2.14" @@ -245,10 +245,10 @@ __metadata: "@smithy/types": "npm:^4.14.1" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-middleware": "npm:^4.2.14" - "@smithy/util-retry": "npm:^4.3.4" + "@smithy/util-retry": "npm:^4.3.6" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/7ae977eeda4a63240d5c230cc8f6028d2070399e3b966c6352550a88c5c37f4c13db04bde39f6b3407849825a97f4cf5146f4a912c20b3b12e1e13ce0f69941b + checksum: 10/7371738ba92353ebb9cdc753bf53240d7fec2486b8a87c191d11ff15386a19a4335ff7c28444f87825ad2643b1c41a491dfcb8dd9cd6e556eb352da5b7e05a9b languageName: node linkType: hard @@ -262,24 +262,24 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:^3.972.31": - version: 3.972.31 - resolution: "@aws-sdk/credential-provider-env@npm:3.972.31" +"@aws-sdk/credential-provider-env@npm:^3.972.34": + version: 3.972.34 + resolution: "@aws-sdk/credential-provider-env@npm:3.972.34" dependencies: - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/6794e7f9121d0b48a73812d6ccae186af07194afeb3e3f1475c961a0d5fac80e4a41450a2446d7c3703613c4e3a7625ef9e1eb336dbd9cf5565729e22a67a8bf + checksum: 10/764a8accd62fa11dffc02354c98c7b3f4ce91b31b1c0baaecde5e10101fb60fb0ab25125e9ef1dd52cf681928e57f7da6afd4e9c63c8ec13632d38406e8ae2cb languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:^3.972.33": - version: 3.972.33 - resolution: "@aws-sdk/credential-provider-http@npm:3.972.33" +"@aws-sdk/credential-provider-http@npm:^3.972.36": + version: 3.972.36 + resolution: "@aws-sdk/credential-provider-http@npm:3.972.36" dependencies: - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/types": "npm:^3.973.8" "@smithy/fetch-http-handler": "npm:^5.3.17" "@smithy/node-http-handler": "npm:^4.6.1" @@ -289,116 +289,116 @@ __metadata: "@smithy/types": "npm:^4.14.1" "@smithy/util-stream": "npm:^4.5.25" tslib: "npm:^2.6.2" - checksum: 10/fd4363319850da0a44cf9126fb8555ced77bb237881a2b696c49af7970a4adbdd3ccf8f5bca726abb32750fc34e93be9785d32ffec654d0c50a1b80dc6e10f06 + checksum: 10/832699bb7075baca36530abe4fbcbde84e340ce3e24cfb4615be2bead459824944181644f625da576e92f25dc341cfabd713d8f3455cab55bc47be16eee2c1fe languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:^3.972.35": - version: 3.972.35 - resolution: "@aws-sdk/credential-provider-ini@npm:3.972.35" +"@aws-sdk/credential-provider-ini@npm:^3.972.38": + version: 3.972.38 + resolution: "@aws-sdk/credential-provider-ini@npm:3.972.38" dependencies: - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/credential-provider-env": "npm:^3.972.31" - "@aws-sdk/credential-provider-http": "npm:^3.972.33" - "@aws-sdk/credential-provider-login": "npm:^3.972.35" - "@aws-sdk/credential-provider-process": "npm:^3.972.31" - "@aws-sdk/credential-provider-sso": "npm:^3.972.35" - "@aws-sdk/credential-provider-web-identity": "npm:^3.972.35" - "@aws-sdk/nested-clients": "npm:^3.997.3" + "@aws-sdk/core": "npm:^3.974.8" + "@aws-sdk/credential-provider-env": "npm:^3.972.34" + "@aws-sdk/credential-provider-http": "npm:^3.972.36" + "@aws-sdk/credential-provider-login": "npm:^3.972.38" + "@aws-sdk/credential-provider-process": "npm:^3.972.34" + "@aws-sdk/credential-provider-sso": "npm:^3.972.38" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.38" + "@aws-sdk/nested-clients": "npm:^3.997.6" "@aws-sdk/types": "npm:^3.973.8" "@smithy/credential-provider-imds": "npm:^4.2.14" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/6f5171a8f91c024dd047e51787aa2f9ab49bbba1cb9469f8621dd639a3bcf30c81c4a952a4752113bf9ff1ebf9567fb94e9288406d670e95ba1c94bb28516438 + checksum: 10/49c7faa65d961450bc62e883553b278d3b525fd9a31c1e60e9ed927034c78603d1f2456b1bccce10ac478672eea8a0fb31b76ab0adf7b68dd0cb362e841b48aa languageName: node linkType: hard -"@aws-sdk/credential-provider-login@npm:^3.972.35": - version: 3.972.35 - resolution: "@aws-sdk/credential-provider-login@npm:3.972.35" +"@aws-sdk/credential-provider-login@npm:^3.972.38": + version: 3.972.38 + resolution: "@aws-sdk/credential-provider-login@npm:3.972.38" dependencies: - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/nested-clients": "npm:^3.997.3" + "@aws-sdk/core": "npm:^3.974.8" + "@aws-sdk/nested-clients": "npm:^3.997.6" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/protocol-http": "npm:^5.3.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/b0e4cb928f642c8d1c9d62bd9d5f68740532e8b83a471a526ad8c93d78b7446bce1c5732286f0ef2fab854b731119a4359039bf92a38f684b5652691297ef9b7 + checksum: 10/b03c35546b1ebffb2ba01d54131fb8bc5438a59f8a484ad3af3ebb42761cb9912d2771764dd60ab1a1a65fc64d61446fd3a519766f21354d2b0fda3d874a492e languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:^3.972.36": - version: 3.972.36 - resolution: "@aws-sdk/credential-provider-node@npm:3.972.36" - dependencies: - "@aws-sdk/credential-provider-env": "npm:^3.972.31" - "@aws-sdk/credential-provider-http": "npm:^3.972.33" - "@aws-sdk/credential-provider-ini": "npm:^3.972.35" - "@aws-sdk/credential-provider-process": "npm:^3.972.31" - "@aws-sdk/credential-provider-sso": "npm:^3.972.35" - "@aws-sdk/credential-provider-web-identity": "npm:^3.972.35" +"@aws-sdk/credential-provider-node@npm:^3.972.38": + version: 3.972.39 + resolution: "@aws-sdk/credential-provider-node@npm:3.972.39" + dependencies: + "@aws-sdk/credential-provider-env": "npm:^3.972.34" + "@aws-sdk/credential-provider-http": "npm:^3.972.36" + "@aws-sdk/credential-provider-ini": "npm:^3.972.38" + "@aws-sdk/credential-provider-process": "npm:^3.972.34" + "@aws-sdk/credential-provider-sso": "npm:^3.972.38" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.38" "@aws-sdk/types": "npm:^3.973.8" "@smithy/credential-provider-imds": "npm:^4.2.14" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/c33dcaa78faf3fa35c7a123cffecf9bcb33c33e7b4eef13576ec146c10d424ef1acc870d5af78224596bb5a1e10d966d6b351564f156331b70d14c793605a7a8 + checksum: 10/b577b77075d4957d46f5bbc190af6f70dfc0a4acff8c8db73425b94aa2e84133393cf305febcc7cacdc7c7896e67697d2f53cbc2a454bf7ba011a830e7dd067f languageName: node linkType: hard -"@aws-sdk/credential-provider-process@npm:^3.972.31": - version: 3.972.31 - resolution: "@aws-sdk/credential-provider-process@npm:3.972.31" +"@aws-sdk/credential-provider-process@npm:^3.972.34": + version: 3.972.34 + resolution: "@aws-sdk/credential-provider-process@npm:3.972.34" dependencies: - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/46505ef1da9849f4522f0f2c6b2e21f7162768828acc58cd0de7ebf31446ead95f8574be9070c0a6d6348737aa5da1e3b3b7087a63a028a61b1338f2ab528bcc + checksum: 10/184a83004066fe745e0aa36ec48480c9eadd13300f854100b6e71d7b14e3cc7254b16e28d5d7bcbc9c1a593ef3275d10b0accfba0b4c5f333f3e7be8850a5ccb languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:^3.972.35": - version: 3.972.35 - resolution: "@aws-sdk/credential-provider-sso@npm:3.972.35" +"@aws-sdk/credential-provider-sso@npm:^3.972.38": + version: 3.972.38 + resolution: "@aws-sdk/credential-provider-sso@npm:3.972.38" dependencies: - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/nested-clients": "npm:^3.997.3" - "@aws-sdk/token-providers": "npm:3.1036.0" + "@aws-sdk/core": "npm:^3.974.8" + "@aws-sdk/nested-clients": "npm:^3.997.6" + "@aws-sdk/token-providers": "npm:3.1041.0" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/b8f544841d3075e78bce267b0ef5e8bd075a05ff54293895dc7b68a319ce0fdeaa44f601c7f0102ce38b0040d65493fadded7e05c69c21ac0bb6a56a82a32776 + checksum: 10/2f1bd86399d46487901e6eee881afaf607e0aac459c6db1fceb342c225f96f726d8d73349f9cf9d5b2c5a1be6a9a373902aaae6a3e448260d0ebc80d1fe8f9a1 languageName: node linkType: hard -"@aws-sdk/credential-provider-web-identity@npm:^3.972.35": - version: 3.972.35 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.35" +"@aws-sdk/credential-provider-web-identity@npm:^3.972.38": + version: 3.972.38 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.38" dependencies: - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/nested-clients": "npm:^3.997.3" + "@aws-sdk/core": "npm:^3.974.8" + "@aws-sdk/nested-clients": "npm:^3.997.6" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/28ebc8e551ed8dbccbcb95489ad2c7f8af4aa1b2283cfa8feceeb3bf30b13e0ed6ca97ff324d7c6780dcc4cd7ee4722e07df2401c3de80c6e6e89a7ae8253c79 + checksum: 10/9975bc20ab171365b73e2e4c9567bb07ffbace0bf5e0e2c6c052e5b99f06d5635c1ce6e0e5260bd62150eca933878ac5cbccb58b3f3058714ce93e3f93aa9649 languageName: node linkType: hard -"@aws-sdk/lib-storage@npm:3.1037.0": - version: 3.1037.0 - resolution: "@aws-sdk/lib-storage@npm:3.1037.0" +"@aws-sdk/lib-storage@npm:3.1040.0": + version: 3.1040.0 + resolution: "@aws-sdk/lib-storage@npm:3.1040.0" dependencies: "@smithy/middleware-endpoint": "npm:^4.4.32" "@smithy/protocol-http": "npm:^5.3.14" @@ -409,8 +409,8 @@ __metadata: stream-browserify: "npm:3.0.0" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-s3": ^3.1037.0 - checksum: 10/c983adfa421a8a1dc1aecd1e815590f86409665884d12e33381c934d01141ebdfc0fd86b756ff27c3615a9098cd4e0f50de500f82b3b95a896bf48d2f9e630ae + "@aws-sdk/client-s3": ^3.1040.0 + checksum: 10/c680da7cbff4de0949bf5a634c8eee0c3248dfcbc0ab06dbdae3b0bbd7442d9aa6efc331e7f62d805542b834b7bd2fb5202c05ac9306a4b9dfc2467e04128d32 languageName: node linkType: hard @@ -441,14 +441,14 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-flexible-checksums@npm:^3.974.13": - version: 3.974.13 - resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.974.13" +"@aws-sdk/middleware-flexible-checksums@npm:^3.974.15": + version: 3.974.16 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.974.16" dependencies: "@aws-crypto/crc32": "npm:5.2.0" "@aws-crypto/crc32c": "npm:5.2.0" "@aws-crypto/util": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/crc64-nvme": "npm:^3.972.7" "@aws-sdk/types": "npm:^3.973.8" "@smithy/is-array-buffer": "npm:^4.2.2" @@ -459,7 +459,7 @@ __metadata: "@smithy/util-stream": "npm:^4.5.25" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/dee29fb34da26986c7e87bdbc7d5034f7ac1cf522b48732671c59a8ac14840c5e06e375dcad2385ce6b5b38bf6dff853b6aa2858da315948b2ea64b2ae8c975f + checksum: 10/694f78b532a1e7929f9ce4c5fbae055da77a5321c51a32ca0a923d98503791e2eeb4514c19b5f32aa9b9c702de03667fa80663e99615d9b34e0d62cff799ce25 languageName: node linkType: hard @@ -510,11 +510,11 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-sdk-s3@npm:^3.972.34": - version: 3.972.34 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.34" +"@aws-sdk/middleware-sdk-s3@npm:^3.972.36, @aws-sdk/middleware-sdk-s3@npm:^3.972.37": + version: 3.972.37 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.37" dependencies: - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/types": "npm:^3.973.8" "@aws-sdk/util-arn-parser": "npm:^3.972.3" "@smithy/core": "npm:^3.23.17" @@ -528,7 +528,7 @@ __metadata: "@smithy/util-stream": "npm:^4.5.25" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/d01bc3458d747ad09868126327fcc15bc47f556640d8be7b507c87679d7cb7bfea0d2d5540ece27386a905dff72b598f126f02e23d6418a6128d4acdd7793944 + checksum: 10/8b5f71c1a62fb02f0acb70eb55592db2ffa8c5c0822fc03babe3d4321672cdcf13ad165d6b921f8312683014c3f1efcede0254790c0ec8bca3a3956fea4f67d8 languageName: node linkType: hard @@ -543,39 +543,39 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:^3.972.35": - version: 3.972.35 - resolution: "@aws-sdk/middleware-user-agent@npm:3.972.35" +"@aws-sdk/middleware-user-agent@npm:^3.972.37, @aws-sdk/middleware-user-agent@npm:^3.972.38": + version: 3.972.38 + resolution: "@aws-sdk/middleware-user-agent@npm:3.972.38" dependencies: - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/types": "npm:^3.973.8" "@aws-sdk/util-endpoints": "npm:^3.996.8" "@smithy/core": "npm:^3.23.17" "@smithy/protocol-http": "npm:^5.3.14" "@smithy/types": "npm:^4.14.1" - "@smithy/util-retry": "npm:^4.3.4" + "@smithy/util-retry": "npm:^4.3.6" tslib: "npm:^2.6.2" - checksum: 10/96a4d25052a257cd20fdae51ecf7d7d0774edf276c37bdec1f5c5d4cba77373f084fee232bfee3a1c86dc4d1776c1374a009d2b3bee49c4a3f6b7c09ca3df7e3 + checksum: 10/f1bfd65d5a49ac80d09c3309bf32e0c6c3362566232aaf5ab830044622efa89abeef0231ce6a03ab5f61c15a6ed22e62c6bf8a290dd4480e2330033eec6d9fef languageName: node linkType: hard -"@aws-sdk/nested-clients@npm:^3.997.3": - version: 3.997.3 - resolution: "@aws-sdk/nested-clients@npm:3.997.3" +"@aws-sdk/nested-clients@npm:^3.997.6": + version: 3.997.6 + resolution: "@aws-sdk/nested-clients@npm:3.997.6" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.974.5" + "@aws-sdk/core": "npm:^3.974.8" "@aws-sdk/middleware-host-header": "npm:^3.972.10" "@aws-sdk/middleware-logger": "npm:^3.972.10" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.11" - "@aws-sdk/middleware-user-agent": "npm:^3.972.35" + "@aws-sdk/middleware-user-agent": "npm:^3.972.38" "@aws-sdk/region-config-resolver": "npm:^3.972.13" - "@aws-sdk/signature-v4-multi-region": "npm:^3.996.22" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.25" "@aws-sdk/types": "npm:^3.973.8" "@aws-sdk/util-endpoints": "npm:^3.996.8" "@aws-sdk/util-user-agent-browser": "npm:^3.972.10" - "@aws-sdk/util-user-agent-node": "npm:^3.973.21" + "@aws-sdk/util-user-agent-node": "npm:^3.973.24" "@smithy/config-resolver": "npm:^4.4.17" "@smithy/core": "npm:^3.23.17" "@smithy/fetch-http-handler": "npm:^5.3.17" @@ -583,7 +583,7 @@ __metadata: "@smithy/invalid-dependency": "npm:^4.2.14" "@smithy/middleware-content-length": "npm:^4.2.14" "@smithy/middleware-endpoint": "npm:^4.4.32" - "@smithy/middleware-retry": "npm:^4.5.5" + "@smithy/middleware-retry": "npm:^4.5.7" "@smithy/middleware-serde": "npm:^4.2.20" "@smithy/middleware-stack": "npm:^4.2.14" "@smithy/node-config-provider": "npm:^4.3.14" @@ -599,10 +599,10 @@ __metadata: "@smithy/util-defaults-mode-node": "npm:^4.2.54" "@smithy/util-endpoints": "npm:^3.4.2" "@smithy/util-middleware": "npm:^4.2.14" - "@smithy/util-retry": "npm:^4.3.4" + "@smithy/util-retry": "npm:^4.3.6" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/d47dbea8813562d42f0dc781859dcf34ef366af14d808419565e25ab9377959319fd628dac0a808918939b3cbcc539fc75ebee2c41ce9255a6d6ed8ec5961d38 + checksum: 10/8467df064e288a3cd2f39d33ef3a63193bb40ac5ef54ef2265dc1b1d8801dbc29c442b75c85a29e6d02e2e43e8f84924dbb7933db8642bb37788c97634c2e814 languageName: node linkType: hard @@ -619,32 +619,32 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:^3.996.22": - version: 3.996.22 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.996.22" +"@aws-sdk/signature-v4-multi-region@npm:^3.996.24, @aws-sdk/signature-v4-multi-region@npm:^3.996.25": + version: 3.996.25 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.996.25" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:^3.972.34" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.37" "@aws-sdk/types": "npm:^3.973.8" "@smithy/protocol-http": "npm:^5.3.14" "@smithy/signature-v4": "npm:^5.3.14" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/31af9c3c43938e101811ddbffd3859cdc29da0b2773e0c1e6952791e236fcd039caca2f29b9736a739f94c4c970248440858b487975045e066aadcb2b618d1ed + checksum: 10/1139b78872c18b65c3f4a3e6d38ba78bd4196129fb969bf0246034b41ac3387b9516f45734ed62e38eb1d564dcd1861ae633a2a50945ff279d67e4aeac503fa6 languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.1036.0": - version: 3.1036.0 - resolution: "@aws-sdk/token-providers@npm:3.1036.0" +"@aws-sdk/token-providers@npm:3.1041.0": + version: 3.1041.0 + resolution: "@aws-sdk/token-providers@npm:3.1041.0" dependencies: - "@aws-sdk/core": "npm:^3.974.5" - "@aws-sdk/nested-clients": "npm:^3.997.3" + "@aws-sdk/core": "npm:^3.974.8" + "@aws-sdk/nested-clients": "npm:^3.997.6" "@aws-sdk/types": "npm:^3.973.8" "@smithy/property-provider": "npm:^4.2.14" "@smithy/shared-ini-file-loader": "npm:^4.4.9" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/0695966a8c176c0a4a695cfaa0e9bbed638e8d5b9ddf0051a5bc168d81449e0341451dc53d1f5006bb7b60641c90217cba7700ea050c5ca26ef145e6247be22e + checksum: 10/bf101933d1daf6056fe92b40f88a65306fc3be17df834acbec4181f5eb781b8968f2aaf3e6f9be31ebf1302da001ae2ba44954b9c8071318ef8bed5d12d36cc9 languageName: node linkType: hard @@ -711,11 +711,11 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-node@npm:^3.973.21": - version: 3.973.21 - resolution: "@aws-sdk/util-user-agent-node@npm:3.973.21" +"@aws-sdk/util-user-agent-node@npm:^3.973.23, @aws-sdk/util-user-agent-node@npm:^3.973.24": + version: 3.973.24 + resolution: "@aws-sdk/util-user-agent-node@npm:3.973.24" dependencies: - "@aws-sdk/middleware-user-agent": "npm:^3.972.35" + "@aws-sdk/middleware-user-agent": "npm:^3.972.38" "@aws-sdk/types": "npm:^3.973.8" "@smithy/node-config-provider": "npm:^4.3.14" "@smithy/types": "npm:^4.14.1" @@ -726,18 +726,19 @@ __metadata: peerDependenciesMeta: aws-crt: optional: true - checksum: 10/08058f9657fe3db73e691ef10ba5fb201ffe57371b91ee14f50622099dd3d88ed3c1acb9c65b8d995865cdc857daeae748804823849ac5180ea11f5c55e0d440 + checksum: 10/e94edde07b98f4ebae95a30a5f2b17514faba5643d596344d142457d7d827c34d7e9217308a479dab89cb95eed9aaadd1fa9479bb087b343d73541eb0b8abba2 languageName: node linkType: hard -"@aws-sdk/xml-builder@npm:^3.972.19": - version: 3.972.19 - resolution: "@aws-sdk/xml-builder@npm:3.972.19" +"@aws-sdk/xml-builder@npm:^3.972.22": + version: 3.972.22 + resolution: "@aws-sdk/xml-builder@npm:3.972.22" dependencies: + "@nodable/entities": "npm:2.1.0" "@smithy/types": "npm:^4.14.1" - fast-xml-parser: "npm:5.7.1" + fast-xml-parser: "npm:5.7.2" tslib: "npm:^2.6.2" - checksum: 10/41b30f0a541baee18a47a0eba1dfb679b31da5d6e7befde4c22665566a24a90f075f0af7edac62169332e0b2c38e8e3c28bbf09a14fa475c034771746faa55c4 + checksum: 10/54032fdf33434cdefcecd374747c0cb16f1e13ee0237a089fdb49c2f01758fbb55c1ba22457cae86223af9abfdf54727099cbe67416af1664bc6e290e10f0b0d languageName: node linkType: hard @@ -5302,7 +5303,7 @@ __metadata: languageName: node linkType: hard -"@nodable/entities@npm:^2.1.0": +"@nodable/entities@npm:2.1.0, @nodable/entities@npm:^2.1.0": version: 2.1.0 resolution: "@nodable/entities@npm:2.1.0" checksum: 10/355c55e82aebe45d4b962d16530951df51e19e3e63a27ea61ad3260c0807064619b270b9c83db10e8394f42760abd5b7f7c5b5117678c4246ce8364a4aafc637 @@ -6955,21 +6956,21 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-retry@npm:^4.5.5": - version: 4.5.5 - resolution: "@smithy/middleware-retry@npm:4.5.5" +"@smithy/middleware-retry@npm:^4.5.7": + version: 4.5.7 + resolution: "@smithy/middleware-retry@npm:4.5.7" dependencies: "@smithy/core": "npm:^3.23.17" "@smithy/node-config-provider": "npm:^4.3.14" "@smithy/protocol-http": "npm:^5.3.14" - "@smithy/service-error-classification": "npm:^4.3.0" + "@smithy/service-error-classification": "npm:^4.3.1" "@smithy/smithy-client": "npm:^4.12.13" "@smithy/types": "npm:^4.14.1" "@smithy/util-middleware": "npm:^4.2.14" - "@smithy/util-retry": "npm:^4.3.4" + "@smithy/util-retry": "npm:^4.3.6" "@smithy/uuid": "npm:^1.1.2" tslib: "npm:^2.6.2" - checksum: 10/40f8f2e58f54ee28dba0baba6561a347040c363e5ad6a403c42c0482be79f5923f935761a1ed775147775883e4e42b5e37a29e8feb541e0240a24dc46f02ac0a + checksum: 10/accdd12c43fbc30703656a1849c80f3ef0e28835664078700240d83ca2f8636ca708000b08f5e7828f88fcc79ed9f06cb4f6c6c3dfc38f1cb03d339fdea8b21c languageName: node linkType: hard @@ -7060,12 +7061,12 @@ __metadata: languageName: node linkType: hard -"@smithy/service-error-classification@npm:^4.3.0": - version: 4.3.0 - resolution: "@smithy/service-error-classification@npm:4.3.0" +"@smithy/service-error-classification@npm:^4.3.1": + version: 4.3.1 + resolution: "@smithy/service-error-classification@npm:4.3.1" dependencies: "@smithy/types": "npm:^4.14.1" - checksum: 10/e9704d5cf40f05dd9de9d2aadd02a7ca9f80d21cf7869468c33ce1662c04a94bde736d737f19bbd097c516e47ff61750d8b7ed0dd3c9b047d3ea6e3c6a2da573 + checksum: 10/611fa6a143e48430d1cb9b3cefa9c5683a9bbb2e6a0215e36605bd35a679ab6347c6070bfee3c2376c25df24e497b87407d284d0b212b758017a4b179deaf424 languageName: node linkType: hard @@ -7254,14 +7255,14 @@ __metadata: languageName: node linkType: hard -"@smithy/util-retry@npm:^4.3.4": - version: 4.3.4 - resolution: "@smithy/util-retry@npm:4.3.4" +"@smithy/util-retry@npm:^4.3.6": + version: 4.3.8 + resolution: "@smithy/util-retry@npm:4.3.8" dependencies: - "@smithy/service-error-classification": "npm:^4.3.0" + "@smithy/service-error-classification": "npm:^4.3.1" "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/b9703f45861fd5259d28e7f9d53c94c3c21876f143a653a22525cb6df8be2282de333532bf342834b027fe0b0dbdc51d6d1502df1e69f609c6d6db7c176339ad + checksum: 10/1e093b8d1e8b00ba5a381cfc65cbeff80d5930823d73c253ab73d06449d397a8c2e045e4a201753557551f6518a215432562e326289a8209ac8ad57bdc11a0c9 languageName: node linkType: hard @@ -7310,13 +7311,13 @@ __metadata: languageName: node linkType: hard -"@smithy/util-waiter@npm:^4.2.16": - version: 4.2.16 - resolution: "@smithy/util-waiter@npm:4.2.16" +"@smithy/util-waiter@npm:^4.3.0": + version: 4.3.0 + resolution: "@smithy/util-waiter@npm:4.3.0" dependencies: "@smithy/types": "npm:^4.14.1" tslib: "npm:^2.6.2" - checksum: 10/81087ffb43b6bbf961a4cc65c4ce7b832273eccd1b6b0b88e5d4b577e17d920f2938f4b9761b231535914f2ea86f5b25339a4e58bade4888070da23242e36779 + checksum: 10/b40cdf498c50cdee3325cff41da424d497c2eac8ce5f22309762042d40d901782a48fe290f8df6d9b130c660d2bb936871e0b7000c5704b466289e45aa6830fc languageName: node linkType: hard @@ -9359,8 +9360,8 @@ __metadata: version: 0.0.0-use.local resolution: "@wireapp/api-client@workspace:libraries/api-client" dependencies: - "@aws-sdk/client-s3": "npm:3.1037.0" - "@aws-sdk/lib-storage": "npm:3.1037.0" + "@aws-sdk/client-s3": "npm:3.1040.0" + "@aws-sdk/lib-storage": "npm:3.1040.0" "@swc/core": "npm:1.15.32" "@swc/jest": "npm:0.2.39" "@types/jest": "npm:30.0.0" @@ -14809,9 +14810,9 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:5.7.1": - version: 5.7.1 - resolution: "fast-xml-parser@npm:5.7.1" +"fast-xml-parser@npm:5.7.2": + version: 5.7.2 + resolution: "fast-xml-parser@npm:5.7.2" dependencies: "@nodable/entities": "npm:^2.1.0" fast-xml-builder: "npm:^1.1.5" @@ -14819,7 +14820,7 @@ __metadata: strnum: "npm:^2.2.3" bin: fxparser: src/cli/cli.js - checksum: 10/ce7de013cae7707d12b9da8cb294265da3780bb8bfa36b17f98053654628a0142159d78746747b1ed38bdefca8b6817f051171183e69a527ba18e1df067e9bce + checksum: 10/7f32d77127dbd5eb1b4c9f7f6ad81972527049905cebe8926c88d102b84c2a56468180dd7541384c93bfc8dad2cef0b1b82b097a931893d3cab3989bd1cf83e1 languageName: node linkType: hard From 8a40e92e952b7e23daec28400e564b7360d953e3 Mon Sep 17 00:00:00 2001 From: Arjita Date: Mon, 4 May 2026 13:27:07 +0200 Subject: [PATCH 44/49] feat: Initial UI for search view(https://wearezeta.atlassian.net/browse/WPB-20721) (#21189) * feat: Initial UI for search view * feat: home icon in breadcrumbs when user is at root level(WPB-25163) * feat: show default search view message, update clear icon * fix: review comments * fix: translate merge * fix: pipeline import order issue * feat: add startup feature toggle for shared drive search,filter --- apps/webapp/src/i18n/en-US.json | 2 + ...rumbs.styles.ts => Conversation.styles.ts} | 32 +++- .../components/Conversation/Conversation.tsx | 52 ++++++- .../CellsHeader/CellsHeader.styles.ts | 87 +++++++++-- .../CellsHeader/CellsHeader.tsx | 146 +++++++++--------- .../CellsHeader/CellsRootHomeIcon.tsx | 48 ++++++ .../CellsHeader/CellsSearchClearIcon.tsx | 33 ++++ .../ConversationCells.styles.ts | 31 ++++ .../ConversationCells/ConversationCells.tsx | 54 +++++-- .../CellsBreadcrumbs/CellsBreadcrumbs.tsx | 9 +- .../script/components/TitleBar/TitleBar.tsx | 111 +++++++------ .../startupFeatureToggleNames.ts | 2 + .../startupFeatureToggles.test.ts | 11 ++ .../style/content/conversation/title-bar.less | 20 +++ apps/webapp/src/types/i18n.d.ts | 2 + 15 files changed, 480 insertions(+), 160 deletions(-) rename apps/webapp/src/script/components/Conversation/{ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts => Conversation.styles.ts} (50%) create mode 100644 apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx create mode 100644 apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index e850c09e2b8..8cc7402b163 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -481,6 +481,8 @@ "cells.restoreRootNodeModal.folder.headline": "Restore folder", "cells.search.closeButton": "Close", "cells.search.failed": "Something went wrong, please try again later.", + "cells.search.idle.description": "Apply a search terms, or a filter to see results.", + "cells.search.idle.heading": "Searching within Shared Drive and all its folders", "cells.search.placeholder": "Search files and folders", "cells.selfDeletingMessage.info": "The feature is not available for conversations with a shared Drive.", "cells.shareModal.changePassword": "Change Password", diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts b/apps/webapp/src/script/components/Conversation/Conversation.styles.ts similarity index 50% rename from apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts rename to apps/webapp/src/script/components/Conversation/Conversation.styles.ts index 9d0147c3a4d..7e119c5a24a 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts +++ b/apps/webapp/src/script/components/Conversation/Conversation.styles.ts @@ -19,6 +19,34 @@ import {CSSObject} from '@emotion/react'; -export const wrapperStyles: CSSObject = { - padding: '0 8px', +export const tabsWrapperStyles: CSSObject = { + position: 'relative', +}; + +export const tabsHiddenStyles: CSSObject = { + visibility: 'hidden', +}; + +export const searchResultsOverlayStyles: CSSObject = { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + display: 'flex', + alignItems: 'flex-end', + paddingLeft: '16px', + paddingBottom: '8px', +}; + +export const searchResultsHeadingStyles: CSSObject = { + margin: 0, + fontSize: 'var(--font-size-medium)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-semibold)', + lineHeight: 'var(--line-height-md)', + color: 'var(--Backgrounds-On-Background-Variant, #000)', + 'body.theme-dark &': { + color: 'var(--Backgrounds-On-Background-Variant, #FFF)', + }, }; diff --git a/apps/webapp/src/script/components/Conversation/Conversation.tsx b/apps/webapp/src/script/components/Conversation/Conversation.tsx index 8b4549efd6d..00e5895818e 100644 --- a/apps/webapp/src/script/components/Conversation/Conversation.tsx +++ b/apps/webapp/src/script/components/Conversation/Conversation.tsx @@ -46,6 +46,7 @@ import {User} from 'Repositories/entity/User'; import {ServiceEntity} from 'Repositories/integration/ServiceEntity'; import {TeamState} from 'Repositories/team/TeamState'; import {Config} from 'src/script/Config'; +import {sharedDriveSearchAndFiltersFeatureToggleName} from 'src/script/featureToggles/startupFeatureToggleNames'; import {useKoSubscribableChildren} from 'Util/componentUtil'; import {isLastReceivedMessage} from 'Util/conversationMessages'; import {allowsAllFiles, getFileExtensionOrName, hasAllowedExtension} from 'Util/fileTypeUtil'; @@ -55,6 +56,12 @@ import {getLogger} from 'Util/logger'; import {safeMailOpen, safeWindowOpen} from 'Util/sanitizationUtil'; import {formatBytes} from 'Util/util'; +import { + searchResultsHeadingStyles, + searchResultsOverlayStyles, + tabsHiddenStyles, + tabsWrapperStyles, +} from './Conversation.styles'; import {ConversationCells} from './ConversationCells/ConversationCells'; import {ConversationFileDropzone} from './ConversationFileDropzone/ConversationFileDropzone'; import {ConversationMessagesWrapper} from './ConversationMessagesWrapper/ConversationMessagesWrapper'; @@ -71,7 +78,7 @@ import {isServiceEntity} from '../../guards/Service'; import {MotionDuration} from '../../motion/MotionDuration'; import {RightSidebarParams} from '../../page/AppMain'; import {PanelState} from '../../page/RightSidebar'; -import {useMainViewModel} from '../../page/RootProvider'; +import {useApplicationContext, useMainViewModel} from '../../page/RootProvider'; import {ElementType, MessageDetails} from '../MessagesList/Message/ContentMessage/asset/TextMessageRenderer'; interface ConversationProps { @@ -96,12 +103,15 @@ export const Conversation = ({ const isVirtualizedMessagesListEnabled = CONFIG.FEATURE.ENABLE_VIRTUALIZED_MESSAGES_LIST; const mainViewModel = useMainViewModel(); + const {isFeatureToggleEnabled} = useApplicationContext(); const {content: contentViewModel} = mainViewModel; const {conversationRepository, repositories} = contentViewModel; + const isSharedDriveSearchAndFiltersEnabled = isFeatureToggleEnabled(sharedDriveSearchAndFiltersFeatureToggleName); const [isConversationLoaded, setIsConversationLoaded] = useState(false); const [inputValue, setInputValue] = useState(''); const [isGiphyModalOpen, setIsGiphyModalOpen] = useState(false); + const [isSharedDriveSearchViewOpen, setIsSharedDriveSearchViewOpen] = useState(false); const conversationState = container.resolve(ConversationState); const callState = container.resolve(CallState); @@ -521,6 +531,12 @@ export const Conversation = ({ const isCellsEnabled = Config.getConfig().FEATURE.ENABLE_CELLS && activeConversation?.cellsState() !== CONVERSATION_CELLS_STATE.DISABLED; + useEffect(() => { + if ((!isFileTabActive || !isSharedDriveSearchAndFiltersEnabled) && isSharedDriveSearchViewOpen) { + setIsSharedDriveSearchViewOpen(false); + } + }, [isFileTabActive, isSharedDriveSearchAndFiltersEnabled, isSharedDriveSearchViewOpen]); + const {getRootProps, getInputProps, openAllFilesView, openImageFilesView, handlePastedFile, isDragAccept} = useFilesUploadDropzone({ isTeam: inTeam, @@ -551,16 +567,32 @@ export const Conversation = ({ openRightSidebar={openRightSidebar} isRightSidebarOpen={isRightSidebarOpen} isReadOnlyConversation={isReadOnlyConversation || isSelfUserRemoved} - withBottomDivider={!isCellsEnabled} + withBottomDivider={!isCellsEnabled || (isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen)} + isSharedDriveSearchViewOpen={isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen} + onCloseSharedDriveSearchView={() => setIsSharedDriveSearchViewOpen(false)} /> {isCellsEnabled && ( <> - +
+
+ +
+ {isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen && ( +
+

Search results

+
+ )} +
{isFileTabActive && ( { + if (isSharedDriveSearchAndFiltersEnabled) { + setIsSharedDriveSearchViewOpen(true); + } + }} /> )} diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts index c0da59feabf..ac21c68d3b0 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts @@ -24,8 +24,9 @@ export const wrapperStyles: CSSObject = { justifyContent: 'space-between', alignItems: 'flex-start', flexDirection: 'column', + gap: '16px', marginBottom: '20px', - paddingRight: '8px', + padding: '0 16px', width: '100%', }; @@ -35,6 +36,21 @@ export const contentStyles: CSSObject = { justifyContent: 'space-between', gap: '8px', width: '100%', + minHeight: '32px', +}; + +export const breadcrumbsRowStyles: CSSObject = { + display: 'flex', + width: '100%', + height: '24px', + alignItems: 'center', + gap: '10px', +}; + +export const rootHomeIconStyles: CSSObject = { + width: '14px', + height: '14px', + flexShrink: 0, }; export const actionsStyles: CSSObject = { @@ -44,21 +60,68 @@ export const actionsStyles: CSSObject = { }; export const searchInputStyles: CSSObject = { - boxShadow: '0 0 0 0.667px transparent', - '.wireinput': { - padding: 0, - paddingLeft: '8px', - boxShadow: 'none', - '&:focus, &:hover, &:active': { - boxShadow: 'none', - }, - }, + display: 'flex', + width: '394px', + height: '32px', + padding: '7px 12px', + justifyContent: 'space-between', + alignItems: 'center', + flexShrink: 0, + gap: '8px', + borderRadius: '12px', + border: '1px solid var(--Border-Base-Primary, #DCE0E3)', + background: 'var(--Background-Base-Primary, #FFF)', + boxSizing: 'border-box', '&:focus-within': { - boxShadow: '0 0 0 0.667px var(--Light-UI-Blue, #0667C8)', + border: '1px solid var(--Border-Accent-Color-Primary, #0667C8)', }, 'body.theme-dark &': { + border: '1px solid var(--Border-Base-Primary, #34373D)', + background: 'var(--Background-Base-Primary, #17181A)', '&:focus-within': { - boxShadow: '0 0 0 0.667px var(--Dark-UI-Blue, #54a6ff)', + border: '1px solid var(--Border-Accent-Color-Primary, #54A6FF)', }, }, }; + +export const searchIconStyles: CSSObject = { + flexShrink: 0, + width: '11.706px', + height: '12px', +}; + +export const searchNativeInputStyles: CSSObject = { + flex: 1, + minWidth: 0, + border: 'none', + outline: 'none', + background: 'transparent', + fontSize: '14px', + color: 'inherit', + boxShadow: 'none', + padding: 0, + '&:hover, &:focus, &:active': { + boxShadow: 'none', + outline: 'none', + }, +}; + +export const clearButtonStyles: CSSObject = { + border: 'none', + background: 'transparent', + padding: 0, + margin: 0, + lineHeight: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + color: 'var(--main-color)', + flexShrink: 0, +}; + +export const clearIconStyles: CSSObject = { + width: '16px', + height: '16px', + flexShrink: 0, +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx index 63ecee9033f..8d1b73541f6 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx @@ -17,19 +17,28 @@ * */ -import {useEffect, useRef} from 'react'; - import {QualifiedId} from '@wireapp/api-client/lib/user/'; -import {CloseIcon, Input, InputSubmitCombo, SearchIcon} from '@wireapp/react-ui-kit'; +import {SearchIcon} from '@wireapp/react-ui-kit'; import {CellsRepository} from 'Repositories/cells/cellsRepository'; import {t} from 'Util/localizerUtil'; -import {actionsStyles, contentStyles, searchInputStyles, wrapperStyles} from './CellsHeader.styles'; +import { + actionsStyles, + breadcrumbsRowStyles, + clearButtonStyles, + contentStyles, + searchIconStyles, + searchInputStyles, + searchNativeInputStyles, + wrapperStyles, +} from './CellsHeader.styles'; import {CellsMoreMenu} from './CellsMoreMenu/CellsMoreMenu'; import {CellsNewMenu} from './CellsNewMenu/CellsNewMenu'; import {CellsRefresh} from './CellsRefresh/CellsRefresh'; +import {CellsRootHomeIcon} from './CellsRootHomeIcon'; +import {CellsSearchClearIcon} from './CellsSearchClearIcon'; import {CellsBreadcrumbs} from '../common/CellsBreadcrumbs/CellsBreadcrumbs'; import {getBreadcrumbsFromPath} from '../common/getBreadcrumbsFromPath/getBreadcrumbsFromPath'; @@ -41,6 +50,8 @@ interface CellsHeaderProps { conversationName: string; conversationQualifiedId: QualifiedId; cellsRepository: CellsRepository; + isSearchViewOpen: boolean; + onOpenSearchView: () => void; searchValue: string; onSearchChange: (value: string) => void; onSearchClear: () => void; @@ -48,94 +59,79 @@ interface CellsHeaderProps { export const CellsHeader = ({ onRefresh, - conversationQualifiedId, conversationName, + conversationQualifiedId, cellsRepository, + isSearchViewOpen, + onOpenSearchView, searchValue, onSearchChange, onSearchClear, }: CellsHeaderProps) => { - const inputRef = useRef(null); - const breadcrumbs = getBreadcrumbsFromPath({ baseCrumb: t('cells.breadcrumb.files', {conversationName}), currentPath: getCellsFilesPath(), }); - - useEffect(() => { - inputRef.current?.focus(); - }, []); + const isRootLevel = breadcrumbs.length === 1; return (
- - openBreadcrumb({ - conversationQualifiedId, - path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', - }) - } - /> -
- - - -
-
- - - - div': {width: '100%'}, - input: { - fontSize: '14px', - height: '32px', - '&:hover': { - boxShadow: 'none', - }, - '&:focus': { - outline: 'none', - boxShadow: 'none', - }, - }, - }} - type="text" - value={searchValue} - ref={inputRef} - aria-label={t('cells.search.placeholder')} - placeholder={t('cells.search.placeholder')} - onChange={event => onSearchChange(event.currentTarget.value)} - data-uie-name="full-search-header-input" - /> +
+ - {searchValue && ( - onSearchChange(event.currentTarget.value)} + data-uie-name="full-search-header-input" /> + + {searchValue && ( + + )} +
+ {!isSearchViewOpen && ( +
+ + + +
)} -
+
+ {!isSearchViewOpen && ( +
+ {isRootLevel ? ( + + ) : ( + + openBreadcrumb({ + conversationQualifiedId, + path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', + }) + } + /> + )} +
+ )}
); }; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx new file mode 100644 index 00000000000..39dd194ff8a --- /dev/null +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx @@ -0,0 +1,48 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {rootHomeIconStyles} from './CellsHeader.styles'; + +export const CellsRootHomeIcon = () => { + return ( + + ); +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx new file mode 100644 index 00000000000..03ea5ed2649 --- /dev/null +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {clearIconStyles} from './CellsHeader.styles'; + +export const CellsSearchClearIcon = () => { + return ( + + ); +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts index 19bcee29d90..be90b9a1475 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts @@ -25,3 +25,34 @@ export const wrapperStyles: CSSObject = { flexDirection: 'column', height: '100%', }; + +export const searchIdleStateStyles: CSSObject = { + display: 'flex', + width: '405px', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', + margin: '64px auto 0', +}; + +export const searchIdleHeadingStyles: CSSObject = { + color: 'var(--main-color)', + textAlign: 'center', + fontSize: 'var(--font-size-base, 16px)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-bold, 700)', + lineHeight: 'var(--line-height-lg, 24px)', + letterSpacing: '0.05px', + margin: 0, +}; + +export const searchIdleDescriptionStyles: CSSObject = { + color: 'var(--main-color)', + textAlign: 'center', + fontSize: 'var(--font-size-base, 16px)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-regular, 400)', + lineHeight: 'var(--line-height-lg, 24px)', + letterSpacing: '0.05px', + margin: 0, +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx index 57a9254873e..5be790cf418 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx @@ -17,7 +17,7 @@ * */ -import {memo} from 'react'; +import {memo, useEffect, useRef} from 'react'; import {CONVERSATION_CELLS_STATE} from '@wireapp/api-client/lib/conversation'; @@ -35,7 +35,12 @@ import {CellsStateInfo} from './CellsStateInfo/CellsStateInfo'; import {CellsTable} from './CellsTable/CellsTable'; import {isInRecycleBin} from './common/recycleBin/recycleBin'; import {useCellsStore} from './common/useCellsStore/useCellsStore'; -import {wrapperStyles} from './ConversationCells.styles'; +import { + searchIdleDescriptionStyles, + searchIdleHeadingStyles, + searchIdleStateStyles, + wrapperStyles, +} from './ConversationCells.styles'; import {useCellsPagination} from './useCellsPagination/useCellsPagination'; import {useConversationSearchFiles} from './useConversationSearch/useConversationSearchFiles'; import {useGetAllCellsNodes} from './useGetAllCellsNodes/useGetAllCellsNodes'; @@ -47,10 +52,19 @@ interface ConversationCellsProps { userRepository: UserRepository; activeConversation: Conversation; conversationRepository: ConversationRepository; + isSearchViewOpen: boolean; + onOpenSearchView: () => void; } export const ConversationCells = memo( - ({cellsRepository, userRepository, activeConversation, conversationRepository}: ConversationCellsProps) => { + ({ + cellsRepository, + userRepository, + activeConversation, + conversationRepository, + isSearchViewOpen, + onOpenSearchView, + }: ConversationCellsProps) => { const {cellsState: initialCellState, name} = useKoSubscribableChildren(activeConversation, ['cellsState', 'name']); const {getNodes, status: nodesStatus, getPagination} = useCellsStore(); @@ -86,13 +100,23 @@ export const ConversationCells = memo( onClear: refresh, }); - const isSearchActive = !!searchValue; + const trimmedSearchValue = searchValue.trim(); + const isSearchActive = !!trimmedSearchValue; + const isSearchViewIdle = isSearchViewOpen && !trimmedSearchValue; + const wasSearchViewOpen = useRef(isSearchViewOpen); const handleClearSearch = () => { clearSearch(); void refresh(); }; + useEffect(() => { + if (wasSearchViewOpen.current && !isSearchViewOpen && searchValue) { + handleClearSearch(); + } + wasSearchViewOpen.current = isSearchViewOpen; + }, [isSearchViewOpen, searchValue]); + // When search is active, refresh should trigger search reload const handleRefresh = isSearchActive ? () => handleSearch(searchValue) : refresh; @@ -115,19 +139,21 @@ export const ConversationCells = memo( const hasNodes = !!nodes.length; const emptyView = !isError && !hasNodes && isCellsStateReady; - const isTableVisible = (isSuccess || isLoading) && isCellsStateReady; - const isLoadingVisible = isLoading && isCellsStateReady; - const isNoNodesVisible = !isLoading && emptyView && !isInRecycleBin(); - const isPaginationVisible = !emptyView; - const isEmptyRecycleBin = isInRecycleBin() && emptyView && !isLoading; + const isTableVisible = (isSuccess || isLoading) && isCellsStateReady && !isSearchViewIdle; + const isLoadingVisible = isLoading && isCellsStateReady && !isSearchViewIdle; + const isNoNodesVisible = !isLoading && emptyView && !isInRecycleBin() && !isSearchViewIdle; + const isPaginationVisible = !emptyView && !isSearchViewIdle; + const isEmptyRecycleBin = isInRecycleBin() && emptyView && !isLoading && !isSearchViewIdle; return (
)} + {isSearchViewIdle && ( +
+

{t('cells.search.idle.heading')}

+

{t('cells.search.idle.description')}

+
+ )} {isCellsStatePending && !isRefreshing && ( )} @@ -148,7 +180,7 @@ export const ConversationCells = memo( )} {isEmptyRecycleBin && } - {(isLoadingVisible || isRefreshing) && } + {(isLoadingVisible || (isRefreshing && !isSearchViewIdle)) && } {isError && } {isPaginationVisible && }
diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.tsx index 1a6b06a7ddd..b7627c816a7 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.tsx @@ -19,19 +19,12 @@ import {Breadcrumbs} from '@wireapp/react-ui-kit'; -import {wrapperStyles} from './CellsBreadcrumbs.styles'; - interface CellsBreadcrumbsProps { maxNotCombinedItems?: number; items: Array<{name: string; path: string}>; - onItemClick: (item: {name: string}) => void; } export const CellsBreadcrumbs = ({maxNotCombinedItems, items, onItemClick}: CellsBreadcrumbsProps) => { - return ( -
- -
- ); + return ; }; diff --git a/apps/webapp/src/script/components/TitleBar/TitleBar.tsx b/apps/webapp/src/script/components/TitleBar/TitleBar.tsx index e4a1e02b6d9..c12c5387406 100644 --- a/apps/webapp/src/script/components/TitleBar/TitleBar.tsx +++ b/apps/webapp/src/script/components/TitleBar/TitleBar.tsx @@ -61,6 +61,8 @@ interface TitleBarProps { callState?: CallState; isReadOnlyConversation?: boolean; withBottomDivider: boolean; + isSharedDriveSearchViewOpen?: boolean; + onCloseSharedDriveSearchView?: () => void; } export const TitleBar = ({ @@ -74,6 +76,8 @@ export const TitleBar = ({ teamState = container.resolve(TeamState), isReadOnlyConversation = false, withBottomDivider, + isSharedDriveSearchViewOpen = false, + onCloseSharedDriveSearchView, }: TitleBarProps) => { const { is1to1, @@ -266,7 +270,20 @@ export const TitleBar = ({ /> )} - {isActivatedAccount && !mdBreakpoint && ( + {isSharedDriveSearchViewOpen && ( + + )} + + {isActivatedAccount && !mdBreakpoint && !isSharedDriveSearchViewOpen && ( - )} - - {mdBreakpoint ? ( + {!isSharedDriveSearchViewOpen && ( <> - - {t('tooltipConversationSearch')} - - {showCallControls && ( - { + currentFocusedElementRef.current = event.currentTarget; + startCallAndShowAlert(); + }} data-uie-name="do-call" disabled={isCallButtonDisabled} > - + + )} + + {mdBreakpoint ? ( + <> + + {t('tooltipConversationSearch')} + + {showCallControls && ( + + + + )} + + ) : ( + )} - ) : ( - )} diff --git a/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts b/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts index bd0115649b5..b848d19b050 100644 --- a/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts +++ b/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts @@ -19,10 +19,12 @@ export const reliableWebsocketConnectionFeatureToggleName = 'reliable-websocket-connection'; export const applockRefactoredFeatureToggleName = 'applock-refactored'; +export const sharedDriveSearchAndFiltersFeatureToggleName = 'shared-drive-search-and-filters'; export const startupFeatureToggleNames = [ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ] as const; export type StartupFeatureToggleName = (typeof startupFeatureToggleNames)[number]; diff --git a/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts b/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts index e467bd9b4d7..d6fd3935f38 100644 --- a/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts +++ b/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts @@ -25,12 +25,14 @@ import { import { applockRefactoredFeatureToggleName, reliableWebsocketConnectionFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, startupFeatureToggleNames, } from './startupFeatureToggleNames'; const featureToggleNamesWithDedicatedExistenceTests = [ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ] as const; describe('startupFeatureToggles', function () { @@ -83,6 +85,14 @@ describe('startupFeatureToggles', function () { expect(startupFeatureToggles.isFeatureToggleEnabled(applockRefactoredFeatureToggleName)).toBe(true); }); + it('enables the shared drive search and filters feature toggle when present in the query parameter', () => { + const startupFeatureToggles = createStartupFeatureTogglesFromLocationSearch( + `?${startupFeatureToggleQueryParameterName}=${sharedDriveSearchAndFiltersFeatureToggleName}`, + ); + + expect(startupFeatureToggles.isFeatureToggleEnabled(sharedDriveSearchAndFiltersFeatureToggleName)).toBe(true); + }); + it('trims whitespace around feature toggle names', () => { const startupFeatureToggles = createStartupFeatureTogglesFromLocationSearch( `?${startupFeatureToggleQueryParameterName}= ${reliableWebsocketConnectionFeatureToggleName} `, @@ -127,6 +137,7 @@ describe('startupFeatureToggles', function () { expect(allowedStartupFeatureToggleNames).toEqual([ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ]); }); diff --git a/apps/webapp/src/style/content/conversation/title-bar.less b/apps/webapp/src/style/content/conversation/title-bar.less index 0345649ae1f..16c721514de 100644 --- a/apps/webapp/src/style/content/conversation/title-bar.less +++ b/apps/webapp/src/style/content/conversation/title-bar.less @@ -100,6 +100,26 @@ body.theme-dark { * > { display: flex; } + + &--borderless { + border: 1px solid transparent; + background-color: transparent; + + &:hover { + border-color: transparent; + background-color: transparent; + } + + body.theme-dark & { + border: 1px solid transparent; + background-color: transparent; + + &:hover { + border-color: transparent; + background-color: transparent; + } + } + } } .conversation-title-bar-name-label { diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index 65716ca80cc..bcbaad6c630 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -485,6 +485,8 @@ declare module 'I18n/en-US.json' { 'cells.restoreRootNodeModal.folder.headline': `Restore folder`; 'cells.search.closeButton': `Close`; 'cells.search.failed': `Something went wrong, please try again later.`; + 'cells.search.idle.description': `Apply a search terms, or a filter to see results.`; + 'cells.search.idle.heading': `Searching within Shared Drive and all its folders`; 'cells.search.placeholder': `Search files and folders`; 'cells.selfDeletingMessage.info': `The feature is not available for conversations with a shared Drive.`; 'cells.shareModal.changePassword': `Change Password`; From 3ac471ffd536f0571239972555fe87a6a99f4216 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Mon, 4 May 2026 13:27:36 +0200 Subject: [PATCH 45/49] ci: re-enable collection of snapshots for debugging failed tests in CI [WPB-22420] (#21215) --- apps/webapp/playwright.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/webapp/playwright.config.ts b/apps/webapp/playwright.config.ts index a3fb0f79187..d40623faece 100644 --- a/apps/webapp/playwright.config.ts +++ b/apps/webapp/playwright.config.ts @@ -53,7 +53,6 @@ module.exports = defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: { mode: 'on-first-retry', - snapshots: false, sources: false, attachments: false, }, From 58ab667b7e2539c52577d840679160c5356c4796 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff <95471369+markbrockhoff@users.noreply.github.com> Date: Mon, 4 May 2026 14:27:26 +0200 Subject: [PATCH 46/49] test: improve stability of tests sending connection requests [WPB-22420] (#21185) * test: improve stability of tests sending connection requests [WPB-22420] In some cases it could happen that the 1on1 conversation was created after the group conversation causing the test to fail e.g. because the already typed message couldn't be sent * test: allow login via guest link to be retried It looks like the login button on the page for guests to join via link sometimes ignores clicks if they happen to fast. But sadly this page is out of the control of this repo to improve so a retry will have to do for now --- .../test/e2e_tests/specs/Block/block.spec.ts | 20 +++++++++++++------ .../e2e_tests/specs/Calling/calling.spec.ts | 2 ++ .../groupVideoCall-TC-8637.spec.ts | 1 + .../specs/Guestroom/guestroom.spec.ts | 9 +++++++-- .../participantProfile.spec.ts | 1 + 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts index 33aa31b3ddd..7e2a017cc02 100644 --- a/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Block/block.spec.ts @@ -113,12 +113,14 @@ test.describe('User Blocking', () => { 'Verify you can block a user who is not in your team', {tag: ['@TC-140', '@regression']}, async ({createPage}) => { - const userAPageManager = await PageManager.from(createPage(withLogin(userA))); - await sendConnectionRequest(userAPageManager, userB); - const {pages: userAPages, modals: userAModals} = userAPageManager.webapp; + const [userAPage, userBPage] = await Promise.all([createPage(withLogin(userA)), createPage(withLogin(userB))]); + await sendConnectionRequest(userAPage, userB); + + const {pages: userAPages, modals: userAModals} = PageManager.from(userAPage).webapp; + const {pages: userBPages} = PageManager.from(userBPage).webapp; // Preconditions: User B accepts the connection request - const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; + await userBPages.conversationList().openPendingConnectionRequest(); await userBPages.connectRequest().clickConnectButton(); // Step 1: User A and B have a 1:1 conversation @@ -169,6 +171,7 @@ test.describe('User Blocking', () => { const conversationName = 'GroupConversation'; await expect(userAPages.conversationList().getConversation(userB.fullName)).toBeAttached(); + await expect(userBPages.conversationList().getConversation(userA.fullName)).toBeAttached(); await createGroup(userAPages, conversationName, [userB]); await test.step('Step 1: User B sends message to group chat with User A', async () => { @@ -199,12 +202,17 @@ test.describe('User Blocking', () => { {tag: ['@TC-142', '@regression']}, async ({createPage}) => { - const userAPageManagerInstance = await PageManager.from(createPage(withLogin(userA))); + const [userAPageManagerInstance, userBPageManagerInstance] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userB))), + ]); await sendConnectionRequest(userAPageManagerInstance, userB); + const {pages: userAPages, modals: userAModals} = userAPageManagerInstance.webapp; + const {pages: userBPages} = userBPageManagerInstance.webapp; // Preconditions: User B accepts the connection request - const userBPages = (await PageManager.from(createPage(withLogin(userB)))).webapp.pages; + await userBPages.conversationList().openPendingConnectionRequest(); await userBPages.connectRequest().clickConnectButton(); // Step 1: User A and B have a 1:1 conversation diff --git a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts index 20a675039cb..caa65323b4c 100644 --- a/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Calling/calling.spec.ts @@ -490,6 +490,8 @@ test.describe('Calling', () => { await test.step('Setup: Accept connection and start group call', async () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); + await expect(userAPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); + await expect(guestPages.conversationList().getConversation(userA.fullName)).toBeAttached(); await createGroup(userAPages, groupName, [userB, guestUser]); diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts index 63c1d605e82..49f3ba25673 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/groupVideoCall-TC-8637.spec.ts @@ -44,6 +44,7 @@ test('Group Video call', {tag: ['@TC-8637', '@crit-flow-web']}, async ({createTe await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); await expect(ownerPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); + await expect(guestPages.conversationList().getConversation(teamOwner.fullName)).toBeAttached(); }); await test.step('Owner and team member are in a group conversation together', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts index 8219941b1c7..34ba7e35805 100644 --- a/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Guestroom/guestroom.spec.ts @@ -55,8 +55,12 @@ test.describe('Guestroom', () => { await guestPages.conversationJoin().joinBrowserButton.click(); await expect(guestPages.conversationJoin().joinAsGuestButton).toBeVisible(); - await guestPages.login().login(guestUser); - await expect(guestBModals.joinGuestLinkPassword().joinForm).toBeVisible({timeout: LOGIN_TIMEOUT}); + // Allow the login to be retried if the modal doesn't show up automatically, this is a workaround since the login mask for guest links is out of our control + await expect(async () => { + await guestPages.login().login(guestUser); + await expect(guestBModals.joinGuestLinkPassword().joinForm).toBeVisible(); + }).toPass({timeout: LOGIN_TIMEOUT}); + await guestBModals.joinGuestLinkPassword().joinConversation('WrongPassword'); await expect(guestBModals.joinGuestLinkPassword().joinForm).toContainText( 'Password is incorrect, please try again.', @@ -180,6 +184,7 @@ test.describe('Guestroom', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); await expect(ownerPages.conversationList().getConversation(guestUser.fullName)).toBeAttached(); + await expect(guestPages.conversationList().getConversation(userA.fullName)).toBeAttached(); await test.step('Owner creates a group with guest', async () => { await createGroup(ownerPages, groupName, [guestUser]); diff --git a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts index 0b1d8a23aa3..635f6fdaafe 100644 --- a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts @@ -103,6 +103,7 @@ test.describe('Participant Profile', () => { await sendConnectionRequest(userAPage, userC); await acceptConnectionRequest(userCPages); await expect(userAPages.conversationList().getConversation(userC.fullName)).toBeAttached(); + await expect(userCPages.conversationList().getConversation(userA.fullName)).toBeAttached(); await test.step('User C is in group with User A and B. User C is not connected to user B', async () => { await createGroup(userAPages, groupName, [userB, userC]); From f67309e1e7cd9310a89c08a6b20ca2a37d2d9156 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 12:28:13 +0000 Subject: [PATCH 47/49] fix(deps): update dependency xstate to v5.31.0 (#21212) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/webapp/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 9b708b74e4a..11b1c67b459 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -95,7 +95,7 @@ "use-debounce": "10.1.1", "webgl-utils.js": "1.1.0", "webrtc-adapter": "9.0.5", - "xstate": "5.30.0", + "xstate": "5.31.0", "zustand": "4.5.7" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 834d5124e76..972f7918eb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9867,7 +9867,7 @@ __metadata: webpack-hot-middleware: "npm:2.26.1" webrtc-adapter: "npm:9.0.5" workbox-webpack-plugin: "npm:7.4.0" - xstate: "npm:5.30.0" + xstate: "npm:5.31.0" zustand: "npm:4.5.7" languageName: unknown linkType: soft @@ -27938,10 +27938,10 @@ __metadata: languageName: node linkType: hard -"xstate@npm:5.30.0": - version: 5.30.0 - resolution: "xstate@npm:5.30.0" - checksum: 10/6c12d9f76502538bb35cad56a52ba1192a1a53f68f28bd4e8661a4ffedc83d913c289ecce0ac06c242d355b708477f5f3227380f9e038c3ac4bf091f0edc3e4d +"xstate@npm:5.31.0": + version: 5.31.0 + resolution: "xstate@npm:5.31.0" + checksum: 10/4097e4efe1350150102dc486ba4721b46e058058640805f1948ce89797d810ffa5e69e2238dbaa0ca26eca9528e8a36bd3208749f1abc50c36e83992e85bce4c languageName: node linkType: hard From 0274a7f5fd88b7e6ff649fff9a80a47b79b0ede1 Mon Sep 17 00:00:00 2001 From: Zafar Saeed Khan Date: Mon, 4 May 2026 15:06:35 +0200 Subject: [PATCH 48/49] chore: update checkout action to fetch master branch with full history (#21218) --- .github/workflows/pull-translations-from-crowdin.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull-translations-from-crowdin.yml b/.github/workflows/pull-translations-from-crowdin.yml index 6587a414460..4e387144bd7 100644 --- a/.github/workflows/pull-translations-from-crowdin.yml +++ b/.github/workflows/pull-translations-from-crowdin.yml @@ -22,6 +22,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: master + fetch-depth: 0 - name: Download translations and create PR uses: crowdin/github-action@7ca9c452bfe9197d3bb7fa83a4d7e2b0c9ae835d From 09009c74ee9794d4b4174fff5f19805e08f036be Mon Sep 17 00:00:00 2001 From: JacquelineLehner <117973982+JacquelineLehner@users.noreply.github.com> Date: Mon, 4 May 2026 16:28:51 +0200 Subject: [PATCH 49/49] refactor: remove tearDown.util.ts [WPB-25196] (#21219) * refactor: remove unused addCreatedTeam util Refs: WPB-25196 * refactor: remove unused tearDownAll util Refs: WPB-25196 * refactor: remove unused removeCreatedTeam util Refs: WPB-25196 * refactor: remove unused removeAllUser util Refs: WPB-25196 * refactor: remove outdated tearDown utils and rely on fixtures for test user cleanup in registration.spec.ts Refs: WPB-25196 * refactor: remove tearDown.util.ts Refs: WPB-25196 --- .../specs/Registration/registration.spec.ts | 18 +--- .../test/e2e_tests/utils/tearDown.util.ts | 83 ------------------- 2 files changed, 4 insertions(+), 97 deletions(-) delete mode 100644 apps/webapp/test/e2e_tests/utils/tearDown.util.ts diff --git a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts index e03d0b6ca00..efa492a40d2 100644 --- a/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Registration/registration.spec.ts @@ -19,19 +19,11 @@ import {getUser} from 'test/e2e_tests/data/user'; import {completeRegistrationForm, goToPersonalRegistration} from 'test/e2e_tests/utils/registration.util'; -import {addCreatedUser, removeCreatedUser} from 'test/e2e_tests/utils/tearDown.util'; import {test, expect} from '../../test.fixtures'; test.describe('Registration', () => { test.describe('email registration used', () => { - const userA = getUser(); - - test.beforeAll(async ({api}) => { - await api.createPersonalUser(userA); - addCreatedUser(userA); - }); - test( 'I want to be informed about terms of personal account before registration', {tag: ['@TC-1621', '@regression']}, @@ -58,7 +50,9 @@ test.describe('Registration', () => { test( 'I want to be notified if the email address I entered during registration has already been registered', {tag: ['@TC-1623', '@TC-1640', '@regression']}, - async ({pageManager}) => { + async ({pageManager, createUser}) => { + const userA = await createUser(); + await pageManager.openMainPage(); const {pages} = pageManager.webapp; @@ -69,15 +63,11 @@ test.describe('Registration', () => { 'This email address has already been registered. Learn more', ); - expect(pageManager.webapp.pages.registration().passwordPolicy).toHaveText( + await expect(pages.registration().passwordPolicy).toHaveText( 'Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.', ); }, ); - - test.afterAll(async ({api}) => { - await removeCreatedUser(api, userA); - }); }); test( diff --git a/apps/webapp/test/e2e_tests/utils/tearDown.util.ts b/apps/webapp/test/e2e_tests/utils/tearDown.util.ts deleted file mode 100644 index 945d7482ddd..00000000000 --- a/apps/webapp/test/e2e_tests/utils/tearDown.util.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Wire - * Copyright (C) 2025 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import {ApiManagerE2E} from 'test/e2e_tests/backend/apiManager.e2e'; - -import {User} from '../data/user'; - -const createdUsers: Map = new Map(); -const createdTeams: Map = new Map(); - -// Utility functions to manage created personal users for cleanup -export const addCreatedUser = (user: User) => { - if (!user.id) { - throw new Error('User must have an ID to be added to createdUsers'); - } - createdUsers.set(user.id, user); -}; - -// Utility functions to manage created teams for cleanup (no need to pass user, as teams are associated with users) -export const addCreatedTeam = (user: User, teamId: string) => { - if (!user.id) { - throw new Error('User must have an ID to be added to createdTeams'); - } - createdTeams.set(user, teamId); -}; - -export const removeCreatedTeam = async (api: ApiManagerE2E, user: User) => { - if (!user.id) { - throw new Error('User must have an ID to be removed from createdTeams'); - } - const teamId = createdTeams.get(user); - if (!teamId) { - throw new Error(`No team found for user ${user.id}`); - } - await api.team.deleteTeam(user, teamId); - createdTeams.delete(user); -}; - -export const removeCreatedUser = async (api: ApiManagerE2E, user: User) => { - if (!user.id) { - throw new Error('User must have an ID to be removed from createdUsers'); - } - const token = user.token ?? (await api.auth.loginUser(user)).data.access_token; - if (!token) { - throw new Error(`Couldn't fetch token for ${user.username}`); - } - await api.user.deleteUser(user.password, token); - createdUsers.delete(user.id); -}; - -// Function to tear down created users and teams -// This function should be called after tests to clean up the created data -export const tearDownAll = async (api: ApiManagerE2E) => { - for (const [user] of createdTeams.entries()) { - await removeCreatedTeam(api, user); - } - createdTeams.clear(); - - await removeAllUser(api); -}; - -export const removeAllUser = async (api: ApiManagerE2E) => { - for (const [, user] of createdUsers.entries()) { - await removeCreatedUser(api, user); - } - createdUsers.clear(); -};