From 640a02009545d67cf8f3158c92d738bdb3d8b0ae Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Dec 2022 15:45:01 -0700 Subject: [PATCH 01/21] Intercept "hs too old" error and replace with a user-friendly thing --- src/i18n/strings/en_EN.json | 1 + src/utils/AutoDiscoveryUtils.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 43247b0bd1d..8f02329e724 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -680,6 +680,7 @@ "No homeserver URL provided": "No homeserver URL provided", "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", + "Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.": "Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.", "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", "This homeserver has been blocked by its administrator.": "This homeserver has been blocked by its administrator.", "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index 4a07dc0372f..59d69c0b1fa 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -238,6 +238,9 @@ export default class AutoDiscoveryUtils { if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) { throw newTranslatableError(hsResult.error); } + if (hsResult.error === AutoDiscovery.ERROR_HOMESERVER_TOO_OLD) { + throw newTranslatableError(_td("Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.")); + } throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); } // else the error is not related to syntax - continue anyways. } From 75bf6445b7941bbf73c762418cf6fcfcaa83eacf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Jul 2023 12:11:03 +0100 Subject: [PATCH 02/21] Update tests --- cypress/e2e/timeline/timeline.spec.ts | 4 ++-- test/components/views/messages/MImageBody-test.tsx | 8 ++++---- test/components/views/messages/MVideoBody-test.tsx | 2 +- .../views/messages/__snapshots__/MImageBody-test.tsx.snap | 2 +- .../views/messages/__snapshots__/MVideoBody-test.tsx.snap | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cypress/e2e/timeline/timeline.spec.ts b/cypress/e2e/timeline/timeline.spec.ts index 6acf8a19b2f..8e4517f2bce 100644 --- a/cypress/e2e/timeline/timeline.spec.ts +++ b/cypress/e2e/timeline/timeline.spec.ts @@ -705,14 +705,14 @@ describe("Timeline", () => { }); it("should render url previews", () => { - cy.intercept("**/_matrix/media/r0/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*", { + cy.intercept("**/_matrix/media/v3/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*", { statusCode: 200, fixture: "riot.png", headers: { "Content-Type": "image/png", }, }).as("mxc"); - cy.intercept("**/_matrix/media/r0/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*", { + cy.intercept("**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*", { statusCode: 200, body: { "og:title": "Element Call", diff --git a/test/components/views/messages/MImageBody-test.tsx b/test/components/views/messages/MImageBody-test.tsx index 8af6ea96d85..617ab92b3a8 100644 --- a/test/components/views/messages/MImageBody-test.tsx +++ b/test/components/views/messages/MImageBody-test.tsx @@ -56,7 +56,7 @@ describe("", () => { }, }), }); - const url = "https://server/_matrix/media/r0/download/server/encrypted-image"; + const url = "https://server/_matrix/media/v3/download/server/encrypted-image"; // eslint-disable-next-line no-restricted-properties cli.mxcUrlToHttp.mockImplementation( (mxcUrl: string, width?: number, height?: number, resizeMethod?: string, allowDirectLinks?: boolean) => { @@ -179,8 +179,8 @@ describe("", () => { }); it("should fall back to /download/ if /thumbnail/ fails", async () => { - const thumbUrl = "https://server/_matrix/media/r0/thumbnail/server/image?width=800&height=600&method=scale"; - const downloadUrl = "https://server/_matrix/media/r0/download/server/image"; + const thumbUrl = "https://server/_matrix/media/v3/thumbnail/server/image?width=800&height=600&method=scale"; + const downloadUrl = "https://server/_matrix/media/v3/download/server/image"; const event = new MatrixEvent({ room_id: "!room:server", @@ -227,7 +227,7 @@ describe("", () => { mocked(global.URL.createObjectURL).mockReturnValue("blob:generated-thumb"); fetchMock.getOnce( - "https://server/_matrix/media/r0/download/server/image", + "https://server/_matrix/media/v3/download/server/image", { body: fs.readFileSync(path.resolve(__dirname, "..", "..", "..", "images", "animated-logo.webp")), }, diff --git a/test/components/views/messages/MVideoBody-test.tsx b/test/components/views/messages/MVideoBody-test.tsx index 49263046e9f..f9bf4d81018 100644 --- a/test/components/views/messages/MVideoBody-test.tsx +++ b/test/components/views/messages/MVideoBody-test.tsx @@ -57,7 +57,7 @@ describe("MVideoBody", () => { }, }), }); - const thumbUrl = "https://server/_matrix/media/r0/download/server/encrypted-poster"; + const thumbUrl = "https://server/_matrix/media/v3/download/server/encrypted-poster"; fetchMock.getOnce(thumbUrl, { status: 200 }); // eslint-disable-next-line no-restricted-properties diff --git a/test/components/views/messages/__snapshots__/MImageBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MImageBody-test.tsx.snap index b38b53e937a..3ba381641bc 100644 --- a/test/components/views/messages/__snapshots__/MImageBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/MImageBody-test.tsx.snap @@ -6,7 +6,7 @@ exports[` should generate a thumbnail if one isn't included for ani class="mx_MImageBody" >
From 183a80a8ae397cb921680c9a040524374293ab4b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Jul 2023 12:19:16 +0100 Subject: [PATCH 03/21] Add legacy server toast --- src/Lifecycle.ts | 32 ++++++++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 34 insertions(+) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 315b3a3fc7e..3d9eca4ad08 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -25,6 +25,7 @@ import { decryptAES, encryptAES, IEncryptedPayload } from "matrix-js-sdk/src/cry import { QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; import { SSOAction } from "matrix-js-sdk/src/@types/auth"; +import { MINIMUM_MATRIX_VERSION } from "matrix-js-sdk/src/version-support"; import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg"; import SecurityCustomisations from "./customisations/Security"; @@ -67,6 +68,7 @@ import { SdkContextClass } from "./contexts/SDKContext"; import { messageForLoginError } from "./utils/ErrorUtils"; import { completeOidcLogin } from "./utils/oidc/authorize"; import { persistOidcAuthenticatedSettings } from "./utils/oidc/persistOidcSettings"; +import GenericToast from "./components/views/toasts/GenericToast"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -585,6 +587,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): }, false, ); + checkServerVersions(); return true; } else { logger.log("No previous session found."); @@ -592,6 +595,35 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): } } +async function checkServerVersions(): Promise { + MatrixClientPeg.get() + ?.getVersions() + .then((response) => { + if (!response.versions.includes(MINIMUM_MATRIX_VERSION)) { + const toastKey = "LEGACY_SERVER"; + ToastStore.sharedInstance().addOrReplaceToast({ + key: toastKey, + title: _t("Your server is unsupported"), + props: { + description: _t( + "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s without errors.", + { + version: MINIMUM_MATRIX_VERSION, + brand: SdkConfig.get().brand, + }, + ), + acceptLabel: _t("OK"), + onAccept: () => { + ToastStore.sharedInstance().dismissToast(toastKey); + }, + }, + component: GenericToast, + priority: 98, + }); + } + }); +} + async function handleLoadSessionFailure(e: unknown): Promise { logger.error("Unable to load session", e); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 64f9a7becb7..37ceba207dc 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -105,6 +105,8 @@ "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.", "We couldn't log you in": "We couldn't log you in", "Try again": "Try again", + "Your server is unsupported": "Your server is unsupported", + "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s.": "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s.", "User is not logged in": "User is not logged in", "Database unexpectedly closed": "Database unexpectedly closed", "This may be caused by having the app open in multiple tabs or due to clearing browser data.": "This may be caused by having the app open in multiple tabs or due to clearing browser data.", From ac6cec99499c44ecbbeef59913be3c21da6d247d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Jul 2023 12:25:11 +0100 Subject: [PATCH 04/21] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 37ceba207dc..f30aeca86ea 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -106,7 +106,7 @@ "We couldn't log you in": "We couldn't log you in", "Try again": "Try again", "Your server is unsupported": "Your server is unsupported", - "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s.": "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s.", + "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s without errors.": "This server is using an older version of Matrix. Upgrade to Matrix %(version)s to use %(brand)s without errors.", "User is not logged in": "User is not logged in", "Database unexpectedly closed": "Database unexpectedly closed", "This may be caused by having the app open in multiple tabs or due to clearing browser data.": "This may be caused by having the app open in multiple tabs or due to clearing browser data.", From 9276bfa30e63456a82c5c006202010a3e54a61b4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Jul 2023 12:30:29 +0100 Subject: [PATCH 05/21] Iterate --- src/AddThreepid.ts | 210 ++++++++++++++----------------- src/utils/AutoDiscoveryUtils.tsx | 4 +- 2 files changed, 96 insertions(+), 118 deletions(-) diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index a1567d2256f..840bb49dd06 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -198,70 +198,59 @@ export default class AddThreepid { */ public async checkEmailLinkClicked(): Promise<[success?: boolean, result?: IAuthData | Error | null]> { try { - if (await this.matrixClient.doesServerSupportSeparateAddAndBind()) { - if (this.bind) { - const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); - if (!identityAccessToken) { - throw new UserFriendlyError("No identity access token found"); - } - await this.matrixClient.bindThreePid({ - sid: this.sessionId!, - client_secret: this.clientSecret, - id_server: getIdServerDomain(this.matrixClient), - id_access_token: identityAccessToken, - }); - } else { - try { - await this.makeAddThreepidOnlyRequest(); - - // The spec has always required this to use UI auth but synapse briefly - // implemented it without, so this may just succeed and that's OK. - return [true]; - } catch (err) { - if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) { - // doesn't look like an interactive-auth failure - throw err; - } + if (this.bind) { + const authClient = new IdentityAuthClient(); + const identityAccessToken = await authClient.getAccessToken(); + if (!identityAccessToken) { + throw new UserFriendlyError("No identity access token found"); + } + await this.matrixClient.bindThreePid({ + sid: this.sessionId!, + client_secret: this.clientSecret, + id_server: getIdServerDomain(this.matrixClient), + id_access_token: identityAccessToken, + }); + } else { + try { + await this.makeAddThreepidOnlyRequest(); - const dialogAesthetics = { - [SSOAuthEntry.PHASE_PREAUTH]: { - title: _t("Use Single Sign On to continue"), - body: _t( - "Confirm adding this email address by using Single Sign On to prove your identity.", - ), - continueText: _t("Single Sign On"), - continueKind: "primary", - }, - [SSOAuthEntry.PHASE_POSTAUTH]: { - title: _t("Confirm adding email"), - body: _t("Click the button below to confirm adding this email address."), - continueText: _t("Confirm"), - continueKind: "primary", - }, - }; - const { finished } = Modal.createDialog(InteractiveAuthDialog<{}>, { - title: _t("Add Email Address"), - matrixClient: this.matrixClient, - authData: err.data, - makeRequest: this.makeAddThreepidOnlyRequest, - aestheticsForStagePhases: { - [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, - [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, - }, - } as InteractiveAuthDialogProps); - return finished; + // The spec has always required this to use UI auth but synapse briefly + // implemented it without, so this may just succeed and that's OK. + return [true]; + } catch (err) { + if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) { + // doesn't look like an interactive-auth failure + throw err; } + + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t( + "Confirm adding this email address by using Single Sign On to prove your identity.", + ), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm adding email"), + body: _t("Click the button below to confirm adding this email address."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; + const { finished } = Modal.createDialog(InteractiveAuthDialog<{}>, { + title: _t("Add Email Address"), + matrixClient: this.matrixClient, + authData: err.data, + makeRequest: this.makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + } as InteractiveAuthDialogProps); + return finished; } - } else { - await this.matrixClient.addThreePid( - { - sid: this.sessionId!, - client_secret: this.clientSecret, - id_server: getIdServerDomain(this.matrixClient), - }, - this.bind, - ); } } catch (err) { if (err instanceof HTTPError && err.httpStatus === 401) { @@ -324,65 +313,52 @@ export default class AddThreepid { throw result; } - if (supportsSeparateAddAndBind) { - if (this.bind) { - await this.matrixClient.bindThreePid({ - sid: this.sessionId!, - client_secret: this.clientSecret, - id_server: getIdServerDomain(this.matrixClient), - id_access_token: await authClient.getAccessToken(), - }); - } else { - try { - await this.makeAddThreepidOnlyRequest(); - - // The spec has always required this to use UI auth but synapse briefly - // implemented it without, so this may just succeed and that's OK. - return; - } catch (err) { - if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) { - // doesn't look like an interactive-auth failure - throw err; - } + if (this.bind) { + await this.matrixClient.bindThreePid({ + sid: this.sessionId!, + client_secret: this.clientSecret, + id_server: getIdServerDomain(this.matrixClient), + id_access_token: await authClient.getAccessToken(), + }); + } else { + try { + await this.makeAddThreepidOnlyRequest(); - const dialogAesthetics = { - [SSOAuthEntry.PHASE_PREAUTH]: { - title: _t("Use Single Sign On to continue"), - body: _t( - "Confirm adding this phone number by using Single Sign On to prove your identity.", - ), - continueText: _t("Single Sign On"), - continueKind: "primary", - }, - [SSOAuthEntry.PHASE_POSTAUTH]: { - title: _t("Confirm adding phone number"), - body: _t("Click the button below to confirm adding this phone number."), - continueText: _t("Confirm"), - continueKind: "primary", - }, - }; - const { finished } = Modal.createDialog(InteractiveAuthDialog<{}>, { - title: _t("Add Phone Number"), - matrixClient: this.matrixClient, - authData: err.data, - makeRequest: this.makeAddThreepidOnlyRequest, - aestheticsForStagePhases: { - [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, - [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, - }, - } as InteractiveAuthDialogProps); - return finished; + // The spec has always required this to use UI auth but synapse briefly + // implemented it without, so this may just succeed and that's OK. + return; + } catch (err) { + if (!(err instanceof MatrixError) || err.httpStatus !== 401 || !err.data || !err.data.flows) { + // doesn't look like an interactive-auth failure + throw err; } + + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t("Confirm adding this phone number by using Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm adding phone number"), + body: _t("Click the button below to confirm adding this phone number."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; + const { finished } = Modal.createDialog(InteractiveAuthDialog<{}>, { + title: _t("Add Phone Number"), + matrixClient: this.matrixClient, + authData: err.data, + makeRequest: this.makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + } as InteractiveAuthDialogProps); + return finished; } - } else { - await this.matrixClient.addThreePid( - { - sid: this.sessionId!, - client_secret: this.clientSecret, - id_server: getIdServerDomain(this.matrixClient), - }, - this.bind, - ); } } } diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index d686dd309b5..47973e7ee07 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -238,7 +238,9 @@ export default class AutoDiscoveryUtils { throw new UserFriendlyError(String(hsResult.error)); } if (hsResult.error === AutoDiscovery.ERROR_HOMESERVER_TOO_OLD) { - throw new UserFriendlyError(_td("Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.")); + throw new UserFriendlyError( + "Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.", + ); } throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } // else the error is not related to syntax - continue anyways. From 69af23c2f87017ebc578bc10e7f08116a5146875 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Aug 2023 15:09:02 +0100 Subject: [PATCH 06/21] Fix tests --- test/Lifecycle-test.ts | 1 + test/components/structures/MatrixChat-test.tsx | 3 ++- test/components/structures/auth/Login-test.tsx | 6 +++--- test/components/structures/auth/Registration-test.tsx | 5 +++-- test/components/views/dialogs/ServerPickerDialog-test.tsx | 8 ++++---- test/test-utils/test-utils.ts | 1 + 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/test/Lifecycle-test.ts b/test/Lifecycle-test.ts index c93c5060d2a..2701c20ddb4 100644 --- a/test/Lifecycle-test.ts +++ b/test/Lifecycle-test.ts @@ -50,6 +50,7 @@ describe("Lifecycle", () => { store: { destroy: jest.fn(), }, + getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), }); beforeEach(() => { diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index 91cb477c020..3dabe33c45b 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -61,6 +61,7 @@ describe("", () => { // reused in createClient mock below const getMockClientMethods = () => ({ ...mockClientMethodsUser(userId), + getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), startClient: jest.fn(), stopClient: jest.fn(), setCanResetTimelineCallback: jest.fn(), @@ -180,7 +181,7 @@ describe("", () => { mockClient = getMockClientWithEventEmitter(getMockClientMethods()); fetchMock.get("https://test.com/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); localStorageSetSpy = jest.spyOn(localStorage.__proto__, "setItem"); localStorageGetSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined); diff --git a/test/components/structures/auth/Login-test.tsx b/test/components/structures/auth/Login-test.tsx index 6973f494683..d79692f9095 100644 --- a/test/components/structures/auth/Login-test.tsx +++ b/test/components/structures/auth/Login-test.tsx @@ -71,7 +71,7 @@ describe("Login", function () { fetchMock.resetHistory(); fetchMock.get("https://matrix.org/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); platform = mockPlatformPeg({ startSingleSignOn: jest.fn(), @@ -209,7 +209,7 @@ describe("Login", function () { fetchMock.get("https://server2/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); rerender(getRawComponent("https://server2")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); @@ -359,7 +359,7 @@ describe("Login", function () { // but server2 is fetchMock.get("https://server2/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); const { rerender } = render(getRawComponent()); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); diff --git a/test/components/structures/auth/Registration-test.tsx b/test/components/structures/auth/Registration-test.tsx index e72ffc58b98..be5afff827b 100644 --- a/test/components/structures/auth/Registration-test.tsx +++ b/test/components/structures/auth/Registration-test.tsx @@ -36,6 +36,7 @@ describe("Registration", function () { const mockClient = mocked({ registerRequest, loginFlows: jest.fn(), + getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), } as unknown as MatrixClient); beforeEach(function () { @@ -59,7 +60,7 @@ describe("Registration", function () { }); fetchMock.get("https://matrix.org/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); mockPlatformPeg({ startSingleSignOn: jest.fn(), @@ -125,7 +126,7 @@ describe("Registration", function () { fetchMock.get("https://server2/_matrix/client/versions", { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); rerender(getRawComponent("https://server2")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); diff --git a/test/components/views/dialogs/ServerPickerDialog-test.tsx b/test/components/views/dialogs/ServerPickerDialog-test.tsx index 759dadf9003..0ce1fe0ec08 100644 --- a/test/components/views/dialogs/ServerPickerDialog-test.tsx +++ b/test/components/views/dialogs/ServerPickerDialog-test.tsx @@ -117,7 +117,7 @@ describe("", () => { it("should allow user to revert from a custom server to the default", async () => { fetchMock.get(`https://custom.org/_matrix/client/versions`, { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); const onFinished = jest.fn(); @@ -147,7 +147,7 @@ describe("", () => { const homeserver = "https://myhomeserver.site"; fetchMock.get(`${homeserver}/_matrix/client/versions`, { unstable_features: {}, - versions: [], + versions: ["v1.1"], }); const onFinished = jest.fn(); getComponent({ onFinished }); @@ -200,7 +200,7 @@ describe("", () => { fetchMock.getOnce(wellKnownUrl, validWellKnown); fetchMock.getOnce(versionsUrl, { - versions: [], + versions: ["v1.1"], }); fetchMock.getOnce(isWellKnownUrl, {}); const onFinished = jest.fn(); @@ -236,7 +236,7 @@ describe("", () => { const wellKnownUrl = `https://${homeserver}/.well-known/matrix/client`; fetchMock.get(wellKnownUrl, { status: 404 }); // but is otherwise live (happy versions response) - fetchMock.get(`https://${homeserver}/_matrix/client/versions`, { versions: ["1"] }); + fetchMock.get(`https://${homeserver}/_matrix/client/versions`, { versions: ["v1.1"] }); const onFinished = jest.fn(); getComponent({ onFinished }); diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index d503652d1b6..e3dbf299cbc 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -242,6 +242,7 @@ export function createTestClient(): MatrixClient { getSyncStateData: jest.fn(), getDehydratedDevice: jest.fn(), exportRoomKeys: jest.fn(), + getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), } as unknown as MatrixClient; client.reEmitter = new ReEmitter(client); From 238b30a314bc697e8b49ec7132cccdf005cea255 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Aug 2023 09:00:40 +0100 Subject: [PATCH 07/21] Remove doesServerSupportSeparateAddAndBind as we now require v1.1 --- src/AddThreepid.ts | 87 ++++++++----------- .../settings/discovery/EmailAddresses.tsx | 39 --------- .../views/settings/discovery/PhoneNumbers.tsx | 45 ---------- .../tabs/user/GeneralUserSettingsTab.tsx | 13 +-- .../discovery/EmailAddresses-test.tsx | 3 - test/test-utils/client.ts | 1 - 6 files changed, 40 insertions(+), 148 deletions(-) diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index 60ed338d26e..88996b259e4 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -100,30 +100,25 @@ export default class AddThreepid { */ public async bindEmailAddress(emailAddress: string): Promise { this.bind = true; - if (await this.matrixClient.doesServerSupportSeparateAddAndBind()) { - // For separate bind, request a token directly from the IS. - const authClient = new IdentityAuthClient(); - const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; - try { - const res = await this.matrixClient.requestEmailToken( - emailAddress, - this.clientSecret, - 1, - undefined, - identityAccessToken, - ); - this.sessionId = res.sid; - return res; - } catch (err) { - if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") { - throw new UserFriendlyError("This email address is already in use", { cause: err }); - } - // Otherwise, just blurt out the same error - throw err; + // For separate bind, request a token directly from the IS. + const authClient = new IdentityAuthClient(); + const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; + try { + const res = await this.matrixClient.requestEmailToken( + emailAddress, + this.clientSecret, + 1, + undefined, + identityAccessToken, + ); + this.sessionId = res.sid; + return res; + } catch (err) { + if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") { + throw new UserFriendlyError("This email address is already in use", { cause: err }); } - } else { - // For tangled bind, request a token via the HS. - return this.addEmailAddress(emailAddress); + // Otherwise, just blurt out the same error + throw err; } } @@ -163,31 +158,26 @@ export default class AddThreepid { */ public async bindMsisdn(phoneCountry: string, phoneNumber: string): Promise { this.bind = true; - if (await this.matrixClient.doesServerSupportSeparateAddAndBind()) { - // For separate bind, request a token directly from the IS. - const authClient = new IdentityAuthClient(); - const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; - try { - const res = await this.matrixClient.requestMsisdnToken( - phoneCountry, - phoneNumber, - this.clientSecret, - 1, - undefined, - identityAccessToken, - ); - this.sessionId = res.sid; - return res; - } catch (err) { - if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") { - throw new UserFriendlyError("This phone number is already in use", { cause: err }); - } - // Otherwise, just blurt out the same error - throw err; + // For separate bind, request a token directly from the IS. + const authClient = new IdentityAuthClient(); + const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; + try { + const res = await this.matrixClient.requestMsisdnToken( + phoneCountry, + phoneNumber, + this.clientSecret, + 1, + undefined, + identityAccessToken, + ); + this.sessionId = res.sid; + return res; + } catch (err) { + if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") { + throw new UserFriendlyError("This phone number is already in use", { cause: err }); } - } else { - // For tangled bind, request a token via the HS. - return this.addMsisdn(phoneCountry, phoneNumber); + // Otherwise, just blurt out the same error + throw err; } } @@ -290,7 +280,6 @@ export default class AddThreepid { msisdnToken: string, ): Promise<[success?: boolean, result?: IAuthData | Error | null] | undefined> { const authClient = new IdentityAuthClient(); - const supportsSeparateAddAndBind = await this.matrixClient.doesServerSupportSeparateAddAndBind(); let result: { success: boolean } | MatrixError; if (this.submitUrl) { @@ -300,7 +289,7 @@ export default class AddThreepid { this.clientSecret, msisdnToken, ); - } else if (this.bind || !supportsSeparateAddAndBind) { + } else if (this.bind) { result = await this.matrixClient.submitMsisdnToken( this.sessionId!, this.clientSecret, diff --git a/src/components/views/settings/discovery/EmailAddresses.tsx b/src/components/views/settings/discovery/EmailAddresses.tsx index 3f913c27d86..de74c59d2c5 100644 --- a/src/components/views/settings/discovery/EmailAddresses.tsx +++ b/src/components/views/settings/discovery/EmailAddresses.tsx @@ -77,10 +77,6 @@ export class EmailAddress extends React.Component { - if (!(await MatrixClientPeg.safeGet().doesServerSupportSeparateAddAndBind())) { - return this.changeBindingTangledAddBind({ bind, label, errorTitle }); - } - const { medium, address } = this.props.email; try { @@ -113,41 +109,6 @@ export class EmailAddress extends React.Component { - const { medium, address } = this.props.email; - - const task = new AddThreepid(MatrixClientPeg.safeGet()); - this.setState({ - verifying: true, - continueDisabled: true, - addTask: task, - }); - - try { - await MatrixClientPeg.safeGet().deleteThreePid(medium, address); - if (bind) { - await task.bindEmailAddress(address); - } else { - await task.addEmailAddress(address); - } - this.setState({ - continueDisabled: false, - bound: bind, - }); - } catch (err) { - logger.error(`changeBindingTangledAddBind: Unable to ${label} email address ${address}`, err); - this.setState({ - verifying: false, - continueDisabled: false, - addTask: null, - }); - Modal.createDialog(ErrorDialog, { - title: errorTitle, - description: extractErrorMessageFromError(err, _t("Operation failed")), - }); - } - } - private onRevokeClick = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); diff --git a/src/components/views/settings/discovery/PhoneNumbers.tsx b/src/components/views/settings/discovery/PhoneNumbers.tsx index 442f74b0d63..6627b119f32 100644 --- a/src/components/views/settings/discovery/PhoneNumbers.tsx +++ b/src/components/views/settings/discovery/PhoneNumbers.tsx @@ -73,10 +73,6 @@ export class PhoneNumber extends React.Component { - if (!(await MatrixClientPeg.safeGet().doesServerSupportSeparateAddAndBind())) { - return this.changeBindingTangledAddBind({ bind, label, errorTitle }); - } - const { medium, address } = this.props.msisdn; try { @@ -114,47 +110,6 @@ export class PhoneNumber extends React.Component { - const { medium, address } = this.props.msisdn; - - const task = new AddThreepid(MatrixClientPeg.safeGet()); - this.setState({ - verifying: true, - continueDisabled: true, - addTask: task, - }); - - try { - await MatrixClientPeg.safeGet().deleteThreePid(medium, address); - // XXX: Sydent will accept a number without country code if you add - // a leading plus sign to a number in E.164 format (which the 3PID - // address is), but this goes against the spec. - // See https://github.com/matrix-org/matrix-doc/issues/2222 - if (bind) { - // @ts-ignore - await task.bindMsisdn(null, `+${address}`); - } else { - // @ts-ignore - await task.addMsisdn(null, `+${address}`); - } - this.setState({ - continueDisabled: false, - bound: bind, - }); - } catch (err) { - logger.error(`changeBindingTangledAddBind: Unable to ${label} phone number ${address}`, err); - this.setState({ - verifying: false, - continueDisabled: false, - addTask: null, - }); - Modal.createDialog(ErrorDialog, { - title: errorTitle, - description: extractErrorMessageFromError(err, _t("Operation failed")), - }); - } - } - private onRevokeClick = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index be6d8dd56aa..c1b620cf656 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -38,7 +38,6 @@ import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms import IdentityAuthClient from "../../../../../IdentityAuthClient"; import { abbreviateUrl } from "../../../../../utils/UrlUtils"; import { getThreepidsWithBindStatus } from "../../../../../boundThreepids"; -import Spinner from "../../../elements/Spinner"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; import { ActionPayload } from "../../../../../dispatcher/payloads"; @@ -71,7 +70,6 @@ interface IState { spellCheckEnabled?: boolean; spellCheckLanguages: string[]; haveIdServer: boolean; - serverSupportsSeparateAddAndBind?: boolean; idServerHasUnsignedTerms: boolean; requiredPolicyInfo: | { @@ -167,8 +165,6 @@ export default class GeneralUserSettingsTab extends React.Component { const cli = this.context; - const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind(); - const capabilities = await cli.getCapabilities(); // this is cached const changePasswordCap = capabilities["m.change_password"]; @@ -180,7 +176,7 @@ export default class GeneralUserSettingsTab extends React.Component(cli.getClientWellKnown()); const externalAccountManagementUrl = delegatedAuthConfig?.account; - this.setState({ serverSupportsSeparateAddAndBind, canChangePassword, externalAccountManagementUrl }); + this.setState({ canChangePassword, externalAccountManagementUrl }); } private async getThreepidState(): Promise { @@ -333,10 +329,7 @@ export default class GeneralUserSettingsTab extends React.Component ) : ( @@ -366,8 +359,6 @@ export default class GeneralUserSettingsTab extends React.Component ); - } else if (this.state.serverSupportsSeparateAddAndBind === null) { - threepidSection = ; } let passwordChangeSection: ReactNode = null; diff --git a/test/components/views/settings/discovery/EmailAddresses-test.tsx b/test/components/views/settings/discovery/EmailAddresses-test.tsx index 8ff76cd3c04..a2494d196d8 100644 --- a/test/components/views/settings/discovery/EmailAddresses-test.tsx +++ b/test/components/views/settings/discovery/EmailAddresses-test.tsx @@ -43,7 +43,6 @@ describe("", () => { const mockClient = getMockClientWithEventEmitter({ getIdentityServerUrl: jest.fn().mockReturnValue("https://fake-identity-server"), generateClientSecret: jest.fn(), - doesServerSupportSeparateAddAndBind: jest.fn(), requestEmailToken: jest.fn(), bindThreePid: jest.fn(), }); @@ -75,7 +74,6 @@ describe("", () => { describe("Email verification share phase", () => { it("shows translated error message", async () => { render(); - mockClient.doesServerSupportSeparateAddAndBind.mockResolvedValue(true); mockClient.requestEmailToken.mockRejectedValue( new MatrixError( { errcode: "M_THREEPID_IN_USE", error: "Some fake MatrixError occured" }, @@ -95,7 +93,6 @@ describe("", () => { // Start these tests out at the "Complete" phase render(); mockClient.requestEmailToken.mockResolvedValue({ sid: "123-fake-sid" } satisfies IRequestTokenResponse); - mockClient.doesServerSupportSeparateAddAndBind.mockResolvedValue(true); fireEvent.click(screen.getByText("Share")); // Then wait for the completion screen to come up await screen.findByText("Complete"); diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts index 80bf9849ce4..f0eba703ac5 100644 --- a/test/test-utils/client.ts +++ b/test/test-utils/client.ts @@ -126,7 +126,6 @@ export const mockClientMethodsEvents = () => ({ * Returns basic mocked client methods related to server support */ export const mockClientMethodsServer = (): Partial, unknown>> => ({ - doesServerSupportSeparateAddAndBind: jest.fn(), getIdentityServerUrl: jest.fn(), getHomeserverUrl: jest.fn(), getCapabilities: jest.fn().mockReturnValue({}), From a9189a400520f976976daeebdd68bd9510413ed2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Aug 2023 09:11:53 +0100 Subject: [PATCH 08/21] Add test --- test/utils/AutoDiscoveryUtils-test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/utils/AutoDiscoveryUtils-test.tsx b/test/utils/AutoDiscoveryUtils-test.tsx index e8e5f87dc1a..74fdf8f5c36 100644 --- a/test/utils/AutoDiscoveryUtils-test.tsx +++ b/test/utils/AutoDiscoveryUtils-test.tsx @@ -232,5 +232,22 @@ describe("AutoDiscoveryUtils", () => { warning: undefined, }); }); + + it("handles homeserver too old error", () => { + const discoveryResult: ClientConfig = { + ...validIsConfig, + "m.homeserver": { + state: AutoDiscoveryAction.FAIL_ERROR, + error: AutoDiscovery.ERROR_HOMESERVER_TOO_OLD, + base_url: "https://matrix.org", + }, + }; + const syntaxOnly = true; + expect(() => + AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly), + ).toThrow( + "Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.", + ); + }); }); }); From 067c737a4e67adb367d61eb63e1f6a4ec2b6d2c6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 10:21:14 +0100 Subject: [PATCH 09/21] Fix general settings tab --- .../views/settings/tabs/user/GeneralUserSettingsTab.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index c1b620cf656..0c87f25dcff 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -324,12 +324,7 @@ export default class GeneralUserSettingsTab extends React.Component ) : ( From 5111690e0dbaeadbe3219584e6d35ff6719c6c9a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 11:24:14 +0100 Subject: [PATCH 10/21] Improve coverage --- test/Lifecycle-test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Lifecycle-test.ts b/test/Lifecycle-test.ts index 2701c20ddb4..dcf2bc6cae0 100644 --- a/test/Lifecycle-test.ts +++ b/test/Lifecycle-test.ts @@ -26,6 +26,7 @@ import { MatrixClientPeg } from "../src/MatrixClientPeg"; import Modal from "../src/Modal"; import * as StorageManager from "../src/utils/StorageManager"; import { getMockClientWithEventEmitter, mockPlatformPeg } from "./test-utils"; +import ToastStore from "../src/stores/ToastStore"; const webCrypto = new Crypto(); @@ -344,6 +345,22 @@ describe("Lifecycle", () => { }); }); }); + + it("should show a toast if the matrix server version is unsupported", async () => { + const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast"); + mockClient.getVersions.mockResolvedValue({ + versions: ["r0.6.0"], + unstable_features: {}, + }); + initLocalStorageMock({ ...localStorageSession }); + + expect(await restoreFromLocalStorage()).toEqual(true); + expect(toastSpy).toHaveBeenCalledWith( + expect.objectContaining({ + title: "Your server is unsupported", + }), + ); + }); }); }); From 8ec00596dd2c0ef99d573f642260a8183d0fd6c2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 11:38:58 +0100 Subject: [PATCH 11/21] Stop using r0 API endpoints --- cypress/e2e/read-receipts/read-receipts.spec.ts | 6 +++--- cypress/support/login.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/read-receipts/read-receipts.spec.ts b/cypress/e2e/read-receipts/read-receipts.spec.ts index 0329b0af73b..a08862d4409 100644 --- a/cypress/e2e/read-receipts/read-receipts.spec.ts +++ b/cypress/e2e/read-receipts/read-receipts.spec.ts @@ -288,7 +288,7 @@ describe("Read receipts", () => { cy.intercept({ method: "POST", url: new RegExp( - `http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/receipt/m\\.read/.+`, + `http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/receipt/m\\.read/.+`, ), }).as("receiptRequest"); @@ -321,7 +321,7 @@ describe("Read receipts", () => { cy.intercept({ method: "POST", - url: new RegExp(`http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/read_markers`), + url: new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`), }).as("readMarkersRequest"); cy.findByRole("button", { name: "Jump to first unread message." }).click(); @@ -341,7 +341,7 @@ describe("Read receipts", () => { cy.intercept({ method: "POST", - url: new RegExp(`http://localhost:\\d+/_matrix/client/r0/rooms/${uriEncodedOtherRoomId}/read_markers`), + url: new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`), }).as("readMarkersRequest"); cy.findByRole("button", { name: "Scroll to most recent messages" }).click(); diff --git a/cypress/support/login.ts b/cypress/support/login.ts index 5a65da17617..141822d2d82 100644 --- a/cypress/support/login.ts +++ b/cypress/support/login.ts @@ -62,7 +62,7 @@ declare global { Cypress.Commands.add( "loginUser", (homeserver: HomeserverInstance, username: string, password: string): Chainable => { - const url = `${homeserver.baseUrl}/_matrix/client/r0/login`; + const url = `${homeserver.baseUrl}/_matrix/client/v3/login`; return cy .request<{ access_token: string; From adbd0f65729f1384875d9360ad14e560868ece93 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 11:53:39 +0100 Subject: [PATCH 12/21] Remove more { // be seeing. serverIsAlive: true, serverDeadError: "", - serverSupportsControlOfDevicesLogout: false, logoutDevices: false, }; this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl); } - public componentDidMount(): void { - this.checkServerCapabilities(this.props.serverConfig); - } - public componentDidUpdate(prevProps: Readonly): void { if ( prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl || @@ -121,9 +114,6 @@ export default class ForgotPassword extends React.Component { ) { // Do a liveliness check on the new URLs this.checkServerLiveliness(this.props.serverConfig); - - // Do capabilities check on new URLs - this.checkServerCapabilities(this.props.serverConfig); } } @@ -146,19 +136,6 @@ export default class ForgotPassword extends React.Component { } } - private async checkServerCapabilities(serverConfig: ValidatedServerConfig): Promise { - const tempClient = createClient({ - baseUrl: serverConfig.hsUrl, - }); - - const serverSupportsControlOfDevicesLogout = await tempClient.doesServerSupportLogoutDevices(); - - this.setState({ - logoutDevices: !serverSupportsControlOfDevicesLogout, - serverSupportsControlOfDevicesLogout, - }); - } - private async onPhaseEmailInputSubmit(): Promise { this.phase = Phase.SendingEmail; @@ -376,16 +353,10 @@ export default class ForgotPassword extends React.Component { description: (

- {!this.state.serverSupportsControlOfDevicesLogout - ? _t( - "Resetting your password on this homeserver will cause all of your devices to be " + - "signed out. This will delete the message encryption keys stored on them, " + - "making encrypted chat history unreadable.", - ) - : _t( - "Signing out your devices will delete the message encryption keys stored on them, " + - "making encrypted chat history unreadable.", - )} + {_t( + "Signing out your devices will delete the message encryption keys stored on them, " + + "making encrypted chat history unreadable.", + )}

{_t( @@ -446,16 +417,14 @@ export default class ForgotPassword extends React.Component { autoComplete="new-password" />

- {this.state.serverSupportsControlOfDevicesLogout ? ( -
- this.setState({ logoutDevices: !this.state.logoutDevices })} - checked={this.state.logoutDevices} - > - {_t("Sign out of all devices")} - -
- ) : null} +
+ this.setState({ logoutDevices: !this.state.logoutDevices })} + checked={this.state.logoutDevices} + > + {_t("Sign out of all devices")} + +
{this.state.errorText && } , - ], - }); - - const [confirmed] = await finished; - if (!confirmed) return; - } - - this.changePassword(cli, oldPassword, newPassword, serverSupportsControlOfDevicesLogout, userHasOtherDevices); + this.changePassword(cli, oldPassword, newPassword, userHasOtherDevices); } private changePassword( cli: MatrixClient, oldPassword: string, newPassword: string, - serverSupportsControlOfDevicesLogout: boolean, userHasOtherDevices: boolean, ): void { const authDict = { @@ -163,23 +115,17 @@ export default class ChangePassword extends React.Component { phase: Phase.Uploading, }); - const logoutDevices = serverSupportsControlOfDevicesLogout ? false : undefined; - - // undefined or true mean all devices signed out - const didLogoutOutOtherDevices = !serverSupportsControlOfDevicesLogout && userHasOtherDevices; - - cli.setPassword(authDict, newPassword, logoutDevices) + cli.setPassword(authDict, newPassword, false) .then( () => { if (this.props.shouldAskForEmail) { return this.optionallySetEmail().then((confirmed) => { this.props.onFinished({ didSetEmail: confirmed, - didLogoutOutOtherDevices, }); }); } else { - this.props.onFinished({ didLogoutOutOtherDevices }); + this.props.onFinished({}); } }, (err) => { @@ -229,17 +175,6 @@ export default class ChangePassword extends React.Component { return modal.finished.then(([confirmed]) => !!confirmed); } - private onExportE2eKeysClicked = (): void => { - Modal.createDialogAsync( - import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog") as unknown as Promise< - typeof ExportE2eKeysDialog - >, - { - matrixClient: MatrixClientPeg.safeGet(), - }, - ); - }; - private markFieldValid(fieldID: FieldType, valid?: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 66fcee75c2d..b01912beec7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1357,11 +1357,6 @@ "Workspace: ": "Workspace: ", "Channel: ": "Channel: ", "No display name": "No display name", - "Warning!": "Warning!", - "Changing your password on this homeserver will cause all of your other devices to be signed out. This will delete the message encryption keys stored on them, and may make encrypted chat history unreadable.": "Changing your password on this homeserver will cause all of your other devices to be signed out. This will delete the message encryption keys stored on them, and may make encrypted chat history unreadable.", - "If you want to retain access to your chat history in encrypted rooms you should first export your room keys and re-import them afterwards.": "If you want to retain access to your chat history in encrypted rooms you should first export your room keys and re-import them afterwards.", - "You can also ask your homeserver admin to upgrade the server to change this behaviour.": "You can also ask your homeserver admin to upgrade the server to change this behaviour.", - "Export E2E room keys": "Export E2E room keys", "Error while changing password: %(error)s": "Error while changing password: %(error)s", "New passwords don't match": "New passwords don't match", "Passwords can't be empty": "Passwords can't be empty", @@ -1391,6 +1386,7 @@ "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", "": "", + "Export E2E room keys": "Export E2E room keys", "Import E2E room keys": "Import E2E room keys", "Cryptography": "Cryptography", "Session ID:": "Session ID:", @@ -2318,6 +2314,7 @@ "Failed to mute user": "Failed to mute user", "Unmute": "Unmute", "Mute": "Mute", + "Warning!": "Warning!", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.", "Are you sure?": "Are you sure?", "Deactivate user?": "Deactivate user?", @@ -3564,7 +3561,6 @@ "Skip verification for now": "Skip verification for now", "Too many attempts in a short time. Wait some time before trying again.": "Too many attempts in a short time. Wait some time before trying again.", "Too many attempts in a short time. Retry after %(timeout)s.": "Too many attempts in a short time. Retry after %(timeout)s.", - "Resetting your password on this homeserver will cause all of your devices to be signed out. This will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Resetting your password on this homeserver will cause all of your devices to be signed out. This will delete the message encryption keys stored on them, making encrypted chat history unreadable.", "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", "If you want to retain access to your chat history in encrypted rooms, set up Key Backup or export your message keys from one of your other devices before proceeding.": "If you want to retain access to your chat history in encrypted rooms, set up Key Backup or export your message keys from one of your other devices before proceeding.", "Reset password": "Reset password", diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 057c9fcb086..570a5c2c734 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -217,7 +217,6 @@ export function createTestClient(): MatrixClient { uploadContent: jest.fn(), getEventMapper: (_options?: MapperOpts) => (event: Partial) => new MatrixEvent(event), leaveRoomChain: jest.fn((roomId) => ({ [roomId]: null })), - doesServerSupportLogoutDevices: jest.fn().mockReturnValue(true), requestPasswordEmailToken: jest.fn().mockRejectedValue({}), setPassword: jest.fn().mockRejectedValue({}), groupCallEventHandler: { groupCalls: new Map() }, From 8c47f833e9c8263943b4c5db8e4546efce6928cb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 12:02:04 +0100 Subject: [PATCH 13/21] iterate --- .../views/settings/tabs/user/GeneralUserSettingsTab.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index bcde40726a5..ff0e237dd38 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -299,12 +299,8 @@ export default class GeneralUserSettingsTab extends React.Component { - let description = _t("Your password was successfully changed."); - if (didLogoutOutOtherDevices) { - description += - " " + _t("You will not receive push notifications on other devices until you sign back in to them."); - } + private onPasswordChanged = (): void => { + const description = _t("Your password was successfully changed."); // TODO: Figure out a design that doesn't involve replacing the current dialog Modal.createDialog(ErrorDialog, { title: _t("Success"), From 91738a442efdfd448eb3f3bed770792d512db216 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 12:58:53 +0100 Subject: [PATCH 14/21] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b01912beec7..81fb487a062 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1544,7 +1544,6 @@ "%(errorMessage)s (HTTP status %(httpStatus)s)": "%(errorMessage)s (HTTP status %(httpStatus)s)", "Error changing password": "Error changing password", "Your password was successfully changed.": "Your password was successfully changed.", - "You will not receive push notifications on other devices until you sign back in to them.": "You will not receive push notifications on other devices until you sign back in to them.", "Success": "Success", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", From 98ec8a4ce5d5be624db052fac1e2d9657b6d3364 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 14:15:36 +0100 Subject: [PATCH 15/21] Improve coverage --- .../views/settings/ChangePassword.tsx | 10 +-- .../views/settings/ChangePassword-test.tsx | 84 +++++++++++++++++++ .../ChangePassword-test.tsx.snap | 71 ++++++++++++++++ 3 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 test/components/views/settings/ChangePassword-test.tsx create mode 100644 test/components/views/settings/__snapshots__/ChangePassword-test.tsx.snap diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index b0064548007..842d6f6ea56 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -89,16 +89,10 @@ export default class ChangePassword extends React.Component { private async onChangePassword(oldPassword: string, newPassword: string): Promise { const cli = MatrixClientPeg.safeGet(); - const userHasOtherDevices = (await cli.getDevices()).devices.length > 1; - this.changePassword(cli, oldPassword, newPassword, userHasOtherDevices); + this.changePassword(cli, oldPassword, newPassword); } - private changePassword( - cli: MatrixClient, - oldPassword: string, - newPassword: string, - userHasOtherDevices: boolean, - ): void { + private changePassword(cli: MatrixClient, oldPassword: string, newPassword: string): void { const authDict = { type: "m.login.password", identifier: { diff --git a/test/components/views/settings/ChangePassword-test.tsx b/test/components/views/settings/ChangePassword-test.tsx new file mode 100644 index 00000000000..a1098081869 --- /dev/null +++ b/test/components/views/settings/ChangePassword-test.tsx @@ -0,0 +1,84 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { getByText, render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import ChangePassword from "../../../../src/components/views/settings/ChangePassword"; +import { stubClient } from "../../../test-utils"; +import { mocked } from "jest-mock"; + +describe("", () => { + it("renders expected fields", () => { + const onFinished = jest.fn(); + const onError = jest.fn(); + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); + }); + + it("should show validation tooltip if passwords do not match", async () => { + const onFinished = jest.fn(); + const onError = jest.fn(); + const { getByLabelText, getByText } = render(); + + const currentPasswordField = getByLabelText("Current password"); + await userEvent.type(currentPasswordField, "CurrentPassword1234"); + + const newPasswordField = getByLabelText("New Password"); + await userEvent.type(newPasswordField, "$%newPassword1234"); + const confirmPasswordField = getByLabelText("Confirm password"); + await userEvent.type(confirmPasswordField, "$%newPassword1235"); + + await userEvent.click(getByText("Change Password")); + + await expect(screen.findByText("Passwords don't match")).resolves.toBeInTheDocument(); + }); + + it("should call MatrixClient::setPassword with expected parameters", async () => { + const cli = stubClient(); + + const onFinished = jest.fn(); + const onError = jest.fn(); + const { getByLabelText, getByText } = render(); + + const currentPasswordField = getByLabelText("Current password"); + await userEvent.type(currentPasswordField, "CurrentPassword1234"); + + const newPasswordField = getByLabelText("New Password"); + await userEvent.type(newPasswordField, "$%newPassword1234"); + const confirmPasswordField = getByLabelText("Confirm password"); + await userEvent.type(confirmPasswordField, "$%newPassword1234"); + + await userEvent.click(getByText("Change Password")); + + await waitFor(() => { + expect(cli.setPassword).toHaveBeenCalledWith( + expect.objectContaining({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: cli.getUserId(), + }, + password: "CurrentPassword1234", + }), + "$%newPassword1234", + false, + ); + }); + }); +}); diff --git a/test/components/views/settings/__snapshots__/ChangePassword-test.tsx.snap b/test/components/views/settings/__snapshots__/ChangePassword-test.tsx.snap new file mode 100644 index 00000000000..be6c17d5af6 --- /dev/null +++ b/test/components/views/settings/__snapshots__/ChangePassword-test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders expected fields 1`] = ` + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ Change Password +
+
+
+`; From 4b5b041031b19d32981b1ca62d7a2516a9c928c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 14:19:54 +0100 Subject: [PATCH 16/21] Delint --- test/components/views/settings/ChangePassword-test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/components/views/settings/ChangePassword-test.tsx b/test/components/views/settings/ChangePassword-test.tsx index a1098081869..1fe81821cda 100644 --- a/test/components/views/settings/ChangePassword-test.tsx +++ b/test/components/views/settings/ChangePassword-test.tsx @@ -15,12 +15,11 @@ limitations under the License. */ import React from "react"; -import { getByText, render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import ChangePassword from "../../../../src/components/views/settings/ChangePassword"; import { stubClient } from "../../../test-utils"; -import { mocked } from "jest-mock"; describe("", () => { it("renders expected fields", () => { From 3afe5770df5b083f12d04bf6cf8a1942c1edc39e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 14:49:14 +0100 Subject: [PATCH 17/21] Improve test coverage --- .../views/settings/account/PhoneNumbers.tsx | 2 +- .../settings/account/PhoneNumbers-test.tsx | 62 ++++++++++ .../__snapshots__/PhoneNumbers-test.tsx.snap | 110 ++++++++++++++++++ test/test-utils/test-utils.ts | 3 + 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 test/components/views/settings/account/PhoneNumbers-test.tsx create mode 100644 test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap diff --git a/src/components/views/settings/account/PhoneNumbers.tsx b/src/components/views/settings/account/PhoneNumbers.tsx index fe89b47134b..ad1fdf7cc47 100644 --- a/src/components/views/settings/account/PhoneNumbers.tsx +++ b/src/components/views/settings/account/PhoneNumbers.tsx @@ -210,7 +210,7 @@ export default class PhoneNumbers extends React.Component { ?.haveMsisdnToken(token) .then(([finished] = []) => { let newPhoneNumber = this.state.newPhoneNumber; - if (finished) { + if (finished !== false) { const msisdns = [...this.props.msisdns, { address, medium: ThreepidMedium.Phone }]; this.props.onMsisdnsChange(msisdns); newPhoneNumber = ""; diff --git a/test/components/views/settings/account/PhoneNumbers-test.tsx b/test/components/views/settings/account/PhoneNumbers-test.tsx new file mode 100644 index 00000000000..b3f16ec93c1 --- /dev/null +++ b/test/components/views/settings/account/PhoneNumbers-test.tsx @@ -0,0 +1,62 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; + +import PhoneNumbers from "../../../../../src/components/views/settings/account/PhoneNumbers"; +import { stubClient } from "../../../../test-utils"; + +describe("", () => { + it("should allow a phone number to be added", async () => { + const cli = stubClient(); + const onMsisdnsChange = jest.fn(); + const { asFragment, getByLabelText, getByText } = render( + , + ); + + mocked(cli.requestAdd3pidMsisdnToken).mockResolvedValue({ + sid: "SID", + msisdn: "+15555551234", + submit_url: "https://server.url", + success: true, + intl_fmt: "no-clue", + }); + mocked(cli.submitMsisdnTokenOtherUrl).mockResolvedValue({ success: true }); + mocked(cli.addThreePidOnly).mockResolvedValue({}); + + const phoneNumberField = getByLabelText("Phone Number"); + await userEvent.type(phoneNumberField, "5555551234"); + await userEvent.click(getByText("Add")); + + expect(cli.requestAdd3pidMsisdnToken).toHaveBeenCalledWith("US", "5555551234", "t35tcl1Ent5ECr3T", 1); + expect(asFragment()).toMatchSnapshot(); + + const verificationCodeField = getByLabelText("Verification code"); + await userEvent.type(verificationCodeField, "123666"); + await userEvent.click(getByText("Continue")); + + expect(cli.submitMsisdnTokenOtherUrl).toHaveBeenCalledWith( + "https://server.url", + "SID", + "t35tcl1Ent5ECr3T", + "123666", + ); + expect(onMsisdnsChange).toHaveBeenCalledWith([{ address: "+15555551234", medium: "msisdn" }]); + }); +}); diff --git a/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap b/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap new file mode 100644 index 00000000000..cf26994d71e --- /dev/null +++ b/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should allow a phone number to be added 1`] = ` + +
+
+
+ +
+ +
+
+ + +
+
+
+
+
+ A text message has been sent to ++15555551234. Please enter the verification code it contains. +
+
+
+
+ + +
+
+ Continue +
+
+
+
+`; diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 570a5c2c734..c0e9d23b203 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -244,6 +244,9 @@ export function createTestClient(): MatrixClient { knockRoom: jest.fn(), leave: jest.fn(), getVersions: jest.fn().mockResolvedValue({ versions: ["v1.1"] }), + requestAdd3pidMsisdnToken: jest.fn(), + submitMsisdnTokenOtherUrl: jest.fn(), + addThreePidOnly: jest.fn(), } as unknown as MatrixClient; client.reEmitter = new ReEmitter(client); From 6ddec823c8de0f5466acda1dcd7386889e251e31 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 16:00:23 +0100 Subject: [PATCH 18/21] Improve coverage --- .../views/settings/ChangePassword-test.tsx | 3 + .../settings/discovery/PhoneNumbers-test.tsx | 45 +++++++++++- .../__snapshots__/PhoneNumbers-test.tsx.snap | 68 ++++++++++++++++++- test/test-utils/test-utils.ts | 2 + 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/test/components/views/settings/ChangePassword-test.tsx b/test/components/views/settings/ChangePassword-test.tsx index 1fe81821cda..e3af79af5f2 100644 --- a/test/components/views/settings/ChangePassword-test.tsx +++ b/test/components/views/settings/ChangePassword-test.tsx @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; import ChangePassword from "../../../../src/components/views/settings/ChangePassword"; import { stubClient } from "../../../test-utils"; @@ -50,6 +51,7 @@ describe("", () => { it("should call MatrixClient::setPassword with expected parameters", async () => { const cli = stubClient(); + mocked(cli.setPassword).mockResolvedValue({}); const onFinished = jest.fn(); const onError = jest.fn(); @@ -79,5 +81,6 @@ describe("", () => { false, ); }); + expect(onFinished).toHaveBeenCalled(); }); }); diff --git a/test/components/views/settings/discovery/PhoneNumbers-test.tsx b/test/components/views/settings/discovery/PhoneNumbers-test.tsx index e8b1e27fc05..00602f11cd1 100644 --- a/test/components/views/settings/discovery/PhoneNumbers-test.tsx +++ b/test/components/views/settings/discovery/PhoneNumbers-test.tsx @@ -15,14 +15,17 @@ limitations under the License. */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; import PhoneNumbers, { PhoneNumber } from "../../../../../src/components/views/settings/discovery/PhoneNumbers"; +import { stubClient } from "../../../../test-utils"; const msisdn: IThreepid = { medium: ThreepidMedium.Phone, - address: "+441111111111", + address: "441111111111", validated_at: 12345, added_at: 12342, bound: false, @@ -38,6 +41,13 @@ describe("", () => { }); }); +const mockGetAccessToken = jest.fn().mockResolvedValue("$$getAccessToken"); +jest.mock("../../../../../src/IdentityAuthClient", () => + jest.fn().mockImplementation(() => ({ + getAccessToken: mockGetAccessToken, + })), +); + describe("", () => { it("should render a loader while loading", async () => { const { container } = render(); @@ -56,4 +66,35 @@ describe("", () => { expect(container).toMatchSnapshot(); }); + + it("should allow binding msisdn", async () => { + const cli = stubClient(); + const { getByText, getByLabelText, asFragment } = render(); + + mocked(cli.requestMsisdnToken).mockResolvedValue({ + sid: "SID", + msisdn: "+15555551234", + submit_url: "https://server.url", + success: true, + intl_fmt: "no-clue", + }); + + fireEvent.click(getByText("Share")); + await waitFor(() => + expect(cli.requestMsisdnToken).toHaveBeenCalledWith( + null, + "+441111111111", + "t35tcl1Ent5ECr3T", + 1, + undefined, + "$$getAccessToken", + ), + ); + expect(asFragment()).toMatchSnapshot(); + + const verificationCodeField = getByLabelText("Verification code"); + await userEvent.type(verificationCodeField, "123666{Enter}"); + + expect(cli.submitMsisdnToken).toHaveBeenCalledWith("SID", "t35tcl1Ent5ECr3T", "123666", "$$getAccessToken"); + }); }); diff --git a/test/components/views/settings/discovery/__snapshots__/PhoneNumbers-test.tsx.snap b/test/components/views/settings/discovery/__snapshots__/PhoneNumbers-test.tsx.snap index 5ae11779ca6..00a136ca840 100644 --- a/test/components/views/settings/discovery/__snapshots__/PhoneNumbers-test.tsx.snap +++ b/test/components/views/settings/discovery/__snapshots__/PhoneNumbers-test.tsx.snap @@ -1,5 +1,67 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` should allow binding msisdn 1`] = ` + +
+
+

+ Phone numbers +

+
+
+
+ + +441111111111 + + + + Please enter verification code sent via text. +
+
+
+
+ + +
+
+
+
+
+
+
+`; + exports[` should handle no numbers 1`] = `
should render phone numbers 1`] = ` class="mx_GeneralUserSettingsTab_section--discovery_existing_address" > + - +441111111111 + 441111111111
- Revoke + Share
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index c0e9d23b203..4bd8917e3b6 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -247,6 +247,8 @@ export function createTestClient(): MatrixClient { requestAdd3pidMsisdnToken: jest.fn(), submitMsisdnTokenOtherUrl: jest.fn(), addThreePidOnly: jest.fn(), + requestMsisdnToken: jest.fn(), + submitMsisdnToken: jest.fn(), } as unknown as MatrixClient; client.reEmitter = new ReEmitter(client); From 353565616335789f29ed8482c67b34c7c0d83eb9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 18:23:21 +0100 Subject: [PATCH 19/21] Fix test fixture --- .../views/settings/discovery/PhoneNumbers-test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/components/views/settings/discovery/PhoneNumbers-test.tsx b/test/components/views/settings/discovery/PhoneNumbers-test.tsx index 00602f11cd1..afb04a7b149 100644 --- a/test/components/views/settings/discovery/PhoneNumbers-test.tsx +++ b/test/components/views/settings/discovery/PhoneNumbers-test.tsx @@ -56,7 +56,7 @@ describe("", () => { }); it("should render phone numbers", async () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); @@ -69,7 +69,9 @@ describe("", () => { it("should allow binding msisdn", async () => { const cli = stubClient(); - const { getByText, getByLabelText, asFragment } = render(); + const { getByText, getByLabelText, asFragment } = render( + , + ); mocked(cli.requestMsisdnToken).mockResolvedValue({ sid: "SID", From 4ed6f7e8fddbcc47ab3d8391875e187fbaf1b7d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2023 18:33:11 +0100 Subject: [PATCH 20/21] Iterate --- .../views/settings/account/PhoneNumbers-test.tsx | 13 +++++++++---- .../__snapshots__/PhoneNumbers-test.tsx.snap | 8 ++++---- .../views/settings/discovery/PhoneNumbers-test.tsx | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/test/components/views/settings/account/PhoneNumbers-test.tsx b/test/components/views/settings/account/PhoneNumbers-test.tsx index b3f16ec93c1..0846371d6c5 100644 --- a/test/components/views/settings/account/PhoneNumbers-test.tsx +++ b/test/components/views/settings/account/PhoneNumbers-test.tsx @@ -21,9 +21,14 @@ import { mocked } from "jest-mock"; import PhoneNumbers from "../../../../../src/components/views/settings/account/PhoneNumbers"; import { stubClient } from "../../../../test-utils"; +import SdkConfig from "../../../../../src/SdkConfig"; describe("", () => { it("should allow a phone number to be added", async () => { + SdkConfig.add({ + default_country_code: "GB", + }); + const cli = stubClient(); const onMsisdnsChange = jest.fn(); const { asFragment, getByLabelText, getByText } = render( @@ -32,7 +37,7 @@ describe("", () => { mocked(cli.requestAdd3pidMsisdnToken).mockResolvedValue({ sid: "SID", - msisdn: "+15555551234", + msisdn: "447900111222", submit_url: "https://server.url", success: true, intl_fmt: "no-clue", @@ -41,10 +46,10 @@ describe("", () => { mocked(cli.addThreePidOnly).mockResolvedValue({}); const phoneNumberField = getByLabelText("Phone Number"); - await userEvent.type(phoneNumberField, "5555551234"); + await userEvent.type(phoneNumberField, "7900111222"); await userEvent.click(getByText("Add")); - expect(cli.requestAdd3pidMsisdnToken).toHaveBeenCalledWith("US", "5555551234", "t35tcl1Ent5ECr3T", 1); + expect(cli.requestAdd3pidMsisdnToken).toHaveBeenCalledWith("GB", "7900111222", "t35tcl1Ent5ECr3T", 1); expect(asFragment()).toMatchSnapshot(); const verificationCodeField = getByLabelText("Verification code"); @@ -57,6 +62,6 @@ describe("", () => { "t35tcl1Ent5ECr3T", "123666", ); - expect(onMsisdnsChange).toHaveBeenCalledWith([{ address: "+15555551234", medium: "msisdn" }]); + expect(onMsisdnsChange).toHaveBeenCalledWith([{ address: "447900111222", medium: "msisdn" }]); }); }); diff --git a/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap b/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap index cf26994d71e..417101d3608 100644 --- a/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap +++ b/test/components/views/settings/account/__snapshots__/PhoneNumbers-test.tsx.snap @@ -41,9 +41,9 @@ exports[` should allow a phone number to be added 1`] = `
- 🇺🇸 + 🇬🇧
- +1 + +44
should allow a phone number to be added 1`] = ` label="Phone Number" placeholder="Phone Number" type="text" - value="5555551234" + value="7900111222" />