From 5f770bfe4c61b68473c2efe5bbc3a21f4065981e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 28 Apr 2026 11:24:47 +0100 Subject: [PATCH] Go to welcome on logout (#33306) * Go to welcome on logout Instead of login, for QR login project * Update tests * Add tests * Delint * Update comments --- apps/web/playwright/e2e/crypto/logout.spec.ts | 2 +- apps/web/playwright/e2e/crypto/utils.ts | 6 ++--- .../e2e/login/login-consent.spec.ts | 4 ++-- .../playwright/e2e/oidc/oidc-native.spec.ts | 6 ++--- apps/web/src/Lifecycle.ts | 2 +- apps/web/src/Views.ts | 18 +++++++-------- .../src/components/structures/MatrixChat.tsx | 11 +++++----- .../components/structures/MatrixChat-test.tsx | 22 +++++++++++++++++-- 8 files changed, 45 insertions(+), 26 deletions(-) diff --git a/apps/web/playwright/e2e/crypto/logout.spec.ts b/apps/web/playwright/e2e/crypto/logout.spec.ts index 9b2e04bf1f..4eedca2942 100644 --- a/apps/web/playwright/e2e/crypto/logout.spec.ts +++ b/apps/web/playwright/e2e/crypto/logout.spec.ts @@ -57,6 +57,6 @@ test.describe("Logout tests", () => { await locator.getByRole("menuitem", { name: "Remove this device", exact: true }).click(); // Should have logged out directly - await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible(); }); }); diff --git a/apps/web/playwright/e2e/crypto/utils.ts b/apps/web/playwright/e2e/crypto/utils.ts index 479ae12a78..ca51b7d9d8 100644 --- a/apps/web/playwright/e2e/crypto/utils.ts +++ b/apps/web/playwright/e2e/crypto/utils.ts @@ -245,7 +245,7 @@ export async function logIntoElementAndVerify(page: Page, credentials: Credentia } /** - * Click the "sign out" option in Element, and wait for the login page to load + * Click the "sign out" option in Element, and wait for the welcome page to load * * @param page - Playwright `Page` object. * @param discardKeys - if true, expect a "You'll lose access to your encrypted messages" dialog, and dismiss it. @@ -259,8 +259,8 @@ export async function logOutOfElement(page: Page, discardKeys: boolean = false) await page.locator(".mx_Dialog .mx_QuestionDialog").getByRole("button", { name: "Remove this device" }).click(); } - // Wait for the login page to load - await page.getByRole("heading", { name: "Sign in" }).click(); + // Wait for the welcome page to load + await expect(page.getByRole("heading", { name: "Be in your element" })).toBeVisible(); } /** diff --git a/apps/web/playwright/e2e/login/login-consent.spec.ts b/apps/web/playwright/e2e/login/login-consent.spec.ts index be8b9669a2..2dc33eaf21 100644 --- a/apps/web/playwright/e2e/login/login-consent.spec.ts +++ b/apps/web/playwright/e2e/login/login-consent.spec.ts @@ -334,7 +334,7 @@ test.describe("Login", () => { }); test.describe("logout", () => { - test("should go to login page on logout", async ({ page, user }) => { + test("should go to welcome page on logout", async ({ page, user }) => { await page.getByRole("button", { name: "User menu" }).click(); await expect(page.getByText(user.displayName, { exact: true })).toBeVisible(); @@ -345,7 +345,7 @@ test.describe("Login", () => { .locator(".mx_UserMenu_contextMenu") .getByRole("menuitem", { name: "Remove this device" }) .click(); - await expect(page).toHaveURL(/\/#\/login$/); + await expect(page).toHaveURL(/\/#\/welcome$/); }); }); }); diff --git a/apps/web/playwright/e2e/oidc/oidc-native.spec.ts b/apps/web/playwright/e2e/oidc/oidc-native.spec.ts index 6d6c4612c1..f81d6b71ff 100644 --- a/apps/web/playwright/e2e/oidc/oidc-native.spec.ts +++ b/apps/web/playwright/e2e/oidc/oidc-native.spec.ts @@ -123,7 +123,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { // Allow the outstanding requests queue to settle before logging out await page.waitForTimeout(2000); await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Remove this device" }).click(); - await expect(page).toHaveURL(/\/#\/login$/); + await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again await page.goto("/#/login"); @@ -159,7 +159,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { .locator(".mx_UserMenu_contextMenu") .getByRole("menuitem", { name: "Remove this device" }) .click(); - await expect(page).toHaveURL(/\/#\/login$/); + await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again await page.goto("/#/login"); @@ -209,7 +209,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { .locator(".mx_UserMenu_contextMenu") .getByRole("menuitem", { name: "Remove this device" }) .click(); - await expect(page).toHaveURL(/\/#\/login$/); + await expect(page).toHaveURL(/\/#\/welcome$/); // Log in again await page.goto("/#/login"); diff --git a/apps/web/src/Lifecycle.ts b/apps/web/src/Lifecycle.ts index 32d113adc8..7ae860bf2a 100644 --- a/apps/web/src/Lifecycle.ts +++ b/apps/web/src/Lifecycle.ts @@ -1133,7 +1133,7 @@ export async function onLoggedOut(): Promise { // customisations got the memo. if (SdkConfig.get().logout_redirect_url) { logger.log("Redirecting to external provider to finish logout"); - // XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login + // XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/welcome window.setTimeout(() => { window.location.href = SdkConfig.get().logout_redirect_url!; }, 100); diff --git a/apps/web/src/Views.ts b/apps/web/src/Views.ts index ccb0d93e05..bbd0200665 100644 --- a/apps/web/src/Views.ts +++ b/apps/web/src/Views.ts @@ -25,15 +25,15 @@ Please see LICENSE files in the repository root for full details. * │ └───────────────────────────────────────────┐ │ * │ │ No previous session │ * │ ▼ │ - * │ (from all other states ┌─────────────────┐ │ - * │ except LOCK_STOLEN) │ WELCOME │ │ - * │ │ │ │ │ - * │ │ Client logged out └─────────────────┘ │ - * │ │ │ │ │ - * │ └──────────────────────────┐ "Sign in" │ │ "Create account" │ - * │ │ ┌────────────┘ └──────────────┐ │ - * │ │ │ │ │ - * │ "Forgot ▼ ▼ "Create an ▼ │ + * │ (from all other states Client logged out ┌─────────────────┐ │ + * │ except LOCK_STOLEN) ──────────────────────►│ WELCOME │ │ + * │ │ │ │ + * │ └─────────────────┘ │ + * │ │ │ │ + * │ "Sign in" │ │ "Create account" │ + * │ ┌────────────┘ └──────────────┐ │ + * │ │ │ │ + * │ "Forgot ▼ "Create an ▼ │ * │ ┌─────────────────┐ password" ┌─────────────────┐ account" ┌─────────────────┐ │ * │ │ FORGOT_PASSWORD │◄───────────────│ LOGIN │───────────────►│ REGISTER │ │ * │ │ │───────────────►│ │◄───────────────│ │ │ diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index 14e726e532..af4fe18a9f 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -422,7 +422,7 @@ export default class MatrixChat extends React.PureComponent { this.onShowPostLoginScreen(); } - const promisesList: Promise[] = [this.firstSyncPromise.promise]; + const promisesList: Promise[] = [this.firstSyncPromise.promise]; let crossSigningIsSetUp = false; if (cryptoEnabled) { // check if the user has previously published public cross-signing keys, @@ -1101,17 +1101,18 @@ export default class MatrixChat extends React.PureComponent { } } - private viewWelcome(): void { + private viewWelcome(otherState?: Partial): void { if (shouldUseLoginForWelcome(SdkConfig.get())) { - return this.viewLogin(); + return this.viewLogin(otherState); } this.setStateForNewView({ view: Views.WELCOME, + ...otherState, }); this.notifyNewScreen("welcome"); } - private viewLogin(otherState?: any): void { + private viewLogin(otherState?: Partial): void { this.setStateForNewView({ view: Views.LOGIN, ...otherState, @@ -1555,7 +1556,7 @@ export default class MatrixChat extends React.PureComponent { * Called when the session is logged out */ private onLoggedOut(): void { - this.viewLogin({ + this.viewWelcome({ ready: false, collapseLhs: false, currentRoomId: null, diff --git a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx index 226decf6e2..04ce9de90f 100644 --- a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -588,8 +588,10 @@ describe("", () => { }); }); - const getComponentAndWaitForReady = async (): Promise => { - const renderResult = getComponent(); + const getComponentAndWaitForReady = async ( + props: Partial> = {}, + ): Promise => { + const renderResult = getComponent(props); // we think we are logged in, but are still waiting for the /sync to complete await screen.findByText("Logout"); @@ -1023,6 +1025,22 @@ describe("", () => { expect(PlatformPeg.get()!.destroyPickleKey).toHaveBeenCalledWith(userId, deviceId); }); + it("should go to welcome", async () => { + await getComponentAndWaitForReady(); + await dispatchLogoutAndWait(); + + expect(defaultProps.onNewScreen).toHaveBeenLastCalledWith("welcome", false); + }); + + it("should go to login if welcome disabled", async () => { + await getComponentAndWaitForReady({ + config: { ...defaultProps.config, embedded_pages: { login_for_welcome: true } }, + }); + await dispatchLogoutAndWait(); + + expect(defaultProps.onNewScreen).toHaveBeenLastCalledWith("login", false); + }); + describe("without delegated auth", () => { it("should call /logout", async () => { await getComponentAndWaitForReady();