diff --git a/playwright/e2e/oidc/oidc-aware.spec.ts b/playwright/e2e/oidc/oidc-aware.spec.ts index 2df450243a..45e99e06bb 100644 --- a/playwright/e2e/oidc/oidc-aware.spec.ts +++ b/playwright/e2e/oidc/oidc-aware.spec.ts @@ -31,7 +31,7 @@ test.describe("OIDC Aware", () => { await expect(page.getByRole("heading", { name: "Welcome alice", exact: true })).toBeVisible(); // Open settings and navigate to account management - await app.settings.openUserSettings("General"); + await app.settings.openUserSettings("Account"); const newPagePromise = context.waitForEvent("page"); await page.getByRole("button", { name: "Manage account" }).click(); diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts index 61795a85e5..6860dc1104 100644 --- a/playwright/e2e/oidc/oidc-native.spec.ts +++ b/playwright/e2e/oidc/oidc-native.spec.ts @@ -44,7 +44,7 @@ test.describe("OIDC Native", () => { const deviceId = await page.evaluate(() => window.localStorage.mx_device_id); - await app.settings.openUserSettings("General"); + await app.settings.openUserSettings("Account"); const newPagePromise = context.waitForEvent("page"); await page.getByRole("button", { name: "Manage account" }).click(); await app.settings.closeDialog(); diff --git a/playwright/e2e/settings/general-user-settings-tab.spec.ts b/playwright/e2e/settings/account-user-settings-tab.spec.ts similarity index 89% rename from playwright/e2e/settings/general-user-settings-tab.spec.ts rename to playwright/e2e/settings/account-user-settings-tab.spec.ts index 0ba85e890b..5ec149f041 100644 --- a/playwright/e2e/settings/general-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/account-user-settings-tab.spec.ts @@ -19,23 +19,23 @@ import { test, expect } from "../../element-web-test"; const USER_NAME = "Bob"; const USER_NAME_NEW = "Alice"; -test.describe("General user settings tab", () => { +test.describe("Account user settings tab", () => { test.use({ displayName: USER_NAME, config: { default_country_code: "US", // For checking the international country calling code }, uut: async ({ app, user }, use) => { - const locator = await app.settings.openUserSettings("General"); + const locator = await app.settings.openUserSettings("Account"); await use(locator); }, }); test("should be rendered properly", async ({ uut, user }) => { - await expect(uut).toMatchScreenshot("general.png"); + await expect(uut).toMatchScreenshot("account.png"); // Assert that the top heading is rendered - await expect(uut.getByRole("heading", { name: "General" })).toBeVisible(); + await expect(uut.getByRole("heading", { name: "Account", exact: true })).toBeVisible(); const profile = uut.locator(".mx_UserProfileSettings_profile"); await profile.scrollIntoViewIfNeeded(); @@ -49,12 +49,11 @@ test.describe("General user settings tab", () => { await expect(uut.getByTestId("discoverySection").locator(".mx_Spinner")).not.toBeVisible(); const accountSection = uut.getByTestId("accountSection"); + accountSection.scrollIntoViewIfNeeded(); // Assert that input areas for changing a password exists - const changePassword = accountSection.locator("form.mx_GeneralUserSettingsTab_section--account_changePassword"); - await changePassword.scrollIntoViewIfNeeded(); - await expect(changePassword.getByLabel("Current password")).toBeVisible(); - await expect(changePassword.getByLabel("New Password")).toBeVisible(); - await expect(changePassword.getByLabel("Confirm password")).toBeVisible(); + await expect(accountSection.getByLabel("Current password")).toBeVisible(); + await expect(accountSection.getByLabel("New Password")).toBeVisible(); + await expect(accountSection.getByLabel("Confirm password")).toBeVisible(); // Check email addresses area const emailAddresses = uut.getByTestId("mx_AccountEmailAddresses"); @@ -82,13 +81,13 @@ test.describe("General user settings tab", () => { test("should respond to small screen sizes", async ({ page, uut }) => { await page.setViewportSize({ width: 700, height: 600 }); - await expect(uut).toMatchScreenshot("general-smallscreen.png"); + await expect(uut).toMatchScreenshot("account-smallscreen.png"); }); test("should show tooltips on narrow screen", async ({ page, uut }) => { await page.setViewportSize({ width: 700, height: 600 }); - await page.getByRole("tab", { name: "General" }).hover(); - await expect(page.getByRole("tooltip")).toHaveText("General"); + await page.getByRole("tab", { name: "Account" }).hover(); + await expect(page.getByRole("tooltip")).toHaveText("Account"); }); test("should support adding and removing a profile picture", async ({ uut, page }) => { diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png new file mode 100644 index 0000000000..17cf808383 Binary files /dev/null and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png differ diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png new file mode 100644 index 0000000000..bbf74913ab Binary files /dev/null and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index bcc3b6cd06..f891abaa16 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png index 8056c4b5ba..edbc19aa22 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png differ diff --git a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png deleted file mode 100644 index 6555b9c9a5..0000000000 Binary files a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png and /dev/null differ diff --git a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png deleted file mode 100644 index b1215054d9..0000000000 Binary files a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-smallscreen-linux.png and /dev/null differ diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index 9294f9012e..927af90d00 100644 Binary files a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 707d8900af..aa2a35dc85 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -32,7 +32,7 @@ import HelpIcon from "@vector-im/compound-design-tokens/assets/web/icons/help"; import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView"; import { _t, _td } from "../../../languageHandler"; -import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab"; +import AccountUserSettingsTab from "../settings/tabs/user/AccountUserSettingsTab"; import SettingsStore from "../../../settings/SettingsStore"; import LabsUserSettingsTab, { showLabsFlags } from "../settings/tabs/user/LabsUserSettingsTab"; import AppearanceUserSettingsTab from "../settings/tabs/user/AppearanceUserSettingsTab"; @@ -65,8 +65,8 @@ function titleForTabID(tabId: UserTab): React.ReactNode { strong: (sub: string) => {sub}, }; switch (tabId) { - case UserTab.General: - return _t("settings|general|dialog_title", undefined, subs); + case UserTab.Account: + return _t("settings|account|dialog_title", undefined, subs); case UserTab.SessionManager: return _t("settings|sessions|dialog_title", undefined, subs); case UserTab.Appearance: @@ -103,10 +103,10 @@ export default function UserSettingsDialog(props: IProps): JSX.Element { tabs.push( new Tab( - UserTab.General, - _td("common|general"), + UserTab.Account, + _td("settings|account|title"), , - , + , "UserSettingsGeneral", ), ); @@ -216,7 +216,7 @@ export default function UserSettingsDialog(props: IProps): JSX.Element { return tabs as NonEmptyArray>; }; - const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.General, props.initialTabId); + const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.Account, props.initialTabId); const setActiveTabId = (tabId: UserTab): void => { _setActiveTabId(tabId); // Clear this so switching away from the tab and back to it will not show the QR code again diff --git a/src/components/views/dialogs/UserTab.ts b/src/components/views/dialogs/UserTab.ts index ab6a213211..fa0e23242e 100644 --- a/src/components/views/dialogs/UserTab.ts +++ b/src/components/views/dialogs/UserTab.ts @@ -15,7 +15,7 @@ limitations under the License. */ export enum UserTab { - General = "USER_GENERAL_TAB", + Account = "USER_ACCOUNT_TAB", Appearance = "USER_APPEARANCE_TAB", Notifications = "USER_NOTIFICATIONS_TAB", Preferences = "USER_PREFERENCES_TAB", diff --git a/src/components/views/settings/notifications/NotificationPusherSettings.tsx b/src/components/views/settings/notifications/NotificationPusherSettings.tsx index 7ba8021818..9da85e0b22 100644 --- a/src/components/views/settings/notifications/NotificationPusherSettings.tsx +++ b/src/components/views/settings/notifications/NotificationPusherSettings.tsx @@ -37,7 +37,7 @@ function generalTabButton(content: string): JSX.Element { onClick={() => { dispatcher.dispatch({ action: Action.ViewUserSettings, - initialTabId: UserTab.General, + initialTabId: UserTab.Account, }); }} > diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx similarity index 97% rename from src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx rename to src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx index be096c3797..a08005c006 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx @@ -62,7 +62,6 @@ const AccountSection: React.FC = ({ > {_t("settings|general|password_change_section")} = ({ onDeactivateClick ); }; -const GeneralUserSettingsTab: React.FC = ({ closeSettingsFn }) => { +const AccountUserSettingsTab: React.FC = ({ closeSettingsFn }) => { const [externalAccountManagementUrl, setExternalAccountManagementUrl] = React.useState(); const [canMake3pidChanges, setCanMake3pidChanges] = React.useState(false); const [canSetDisplayName, setCanSetDisplayName] = React.useState(false); @@ -189,7 +188,7 @@ const GeneralUserSettingsTab: React.FC = ({ closeSettingsFn }) => { } return ( - + = ({ closeSettingsFn }) => { ); }; -export default GeneralUserSettingsTab; +export default AccountUserSettingsTab; diff --git a/src/hooks/useUserOnboardingTasks.ts b/src/hooks/useUserOnboardingTasks.ts index 176242b124..ebafd5514c 100644 --- a/src/hooks/useUserOnboardingTasks.ts +++ b/src/hooks/useUserOnboardingTasks.ts @@ -127,7 +127,7 @@ const tasks: UserOnboardingTask[] = [ PosthogTrackers.trackInteraction("WebUserOnboardingTaskSetupProfile", ev); defaultDispatcher.dispatch({ action: Action.ViewUserSettings, - initialTabId: UserTab.General, + initialTabId: UserTab.Account, }); }, }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1cc22ac382..ff216691ec 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2422,6 +2422,10 @@ } }, "settings": { + "account": { + "dialog_title": "Settings: Account", + "title": "Account" + }, "all_rooms_home": "Show all rooms in Home", "all_rooms_home_description": "All rooms you're in will appear in Home.", "always_show_message_timestamps": "Always show message timestamps", @@ -2497,7 +2501,6 @@ "deactivate_confirm_erase_label": "Hide my messages from new joiners", "deactivate_section": "Deactivate Account", "deactivate_warning": "Deactivating your account is a permanent action — be careful!", - "dialog_title": "Settings: General", "discovery_email_empty": "Discovery options will appear once you have added an email.", "discovery_email_verification_instructions": "Verify the link in your inbox", "discovery_msisdn_empty": "Discovery options will appear once you have added a phone number.", diff --git a/test/components/views/dialogs/UserSettingsDialog-test.tsx b/test/components/views/dialogs/UserSettingsDialog-test.tsx index f404b7f208..546d4770e7 100644 --- a/test/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/test/components/views/dialogs/UserSettingsDialog-test.tsx @@ -98,7 +98,7 @@ describe("", () => { it("should render general settings tab when no initialTabId", () => { const { container } = render(getComponent()); - expect(getActiveTabLabel(container)).toEqual("General"); + expect(getActiveTabLabel(container)).toEqual("Account"); }); it("should render initial tab when initialTabId is set", () => { @@ -111,7 +111,7 @@ describe("", () => { // mjolnir tab is only rendered in some configs const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir })); - expect(getActiveTabLabel(container)).toEqual("General"); + expect(getActiveTabLabel(container)).toEqual("Account"); }); it("renders tabs correctly", () => { diff --git a/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap index e4fe6be13e..a6114fdaca 100644 --- a/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap @@ -3,10 +3,10 @@ exports[` renders tabs correctly 1`] = ` NodeList [ ,
  • ", () => { +let changePasswordOnError: (e: Error) => void; +let changePasswordOnFinished: () => void; + +jest.mock( + "../../../../../../src/components/views/settings/ChangePassword", + () => + ({ onError, onFinished }: { onError: (e: Error) => void; onFinished: () => void }) => { + changePasswordOnError = onError; + changePasswordOnFinished = onFinished; + return ; + }, +); + +describe("", () => { const defaultProps = { closeSettingsFn: jest.fn(), }; const userId = "@alice:server.org"; - const mockClient = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - ...mockClientMethodsServer(), - getCapabilities: jest.fn(), - getThreePids: jest.fn(), - getIdentityServerUrl: jest.fn(), - deleteThreePid: jest.fn(), - }); + let mockClient: MockedObject; let stores: SdkContextClass; const getComponent = () => ( - + ); @@ -62,6 +71,15 @@ describe("", () => { jest.spyOn(SettingsStore, "getValue").mockRestore(); jest.spyOn(logger, "error").mockRestore(); + mockClient = getMockClientWithEventEmitter({ + ...mockClientMethodsUser(userId), + ...mockClientMethodsServer(), + getCapabilities: jest.fn(), + getThreePids: jest.fn(), + getIdentityServerUrl: jest.fn(), + deleteThreePid: jest.fn(), + }); + mockClient.getCapabilities.mockResolvedValue({}); mockClient.getThreePids.mockResolvedValue({ threepids: [], @@ -77,6 +95,10 @@ describe("", () => { jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore); }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it("does not show account management link when not available", () => { const { queryByTestId } = render(getComponent()); @@ -131,6 +153,52 @@ describe("", () => { expect(screen.getByText("Deactivate Account", { selector: "h2" }).parentElement!).toMatchSnapshot(); }); + it("should display the deactivate account dialog when clicked", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (settingName) => settingName === UIFeature.Deactivate, + ); + + const createDialogFn = jest.fn(); + jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn); + + render(getComponent()); + + await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" })); + + expect(createDialogFn).toHaveBeenCalled(); + }); + it("should close settings if account deactivated", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (settingName) => settingName === UIFeature.Deactivate, + ); + + const createDialogFn = jest.fn(); + jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn); + + render(getComponent()); + + await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" })); + + createDialogFn.mock.calls[0][1].onFinished(true); + + expect(defaultProps.closeSettingsFn).toHaveBeenCalled(); + }); + it("should not close settings if account not deactivated", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (settingName) => settingName === UIFeature.Deactivate, + ); + + const createDialogFn = jest.fn(); + jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn); + + render(getComponent()); + + await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" })); + + createDialogFn.mock.calls[0][1].onFinished(false); + + expect(defaultProps.closeSettingsFn).not.toHaveBeenCalled(); + }); }); describe("3pids", () => { @@ -302,4 +370,53 @@ describe("", () => { }); }); }); + + describe("Password change", () => { + beforeEach(() => { + mockClient.getCapabilities.mockResolvedValue({ + "m.change_password": { + enabled: true, + }, + }); + }); + + it("should display a dialog if password change succeeded", async () => { + const createDialogFn = jest.fn(); + jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn); + + render(getComponent()); + + const changeButton = await screen.findByRole("button", { name: "Mock change password" }); + userEvent.click(changeButton); + + expect(changePasswordOnFinished).toBeDefined(); + changePasswordOnFinished(); + + expect(createDialogFn).toHaveBeenCalledWith(expect.anything(), { + title: "Success", + description: "Your password was successfully changed.", + }); + }); + + it("should display an error if password change failed", async () => { + const ERROR_STRING = + "Your password must contain exactly 5 lowercase letters, a box drawing character and the badger emoji."; + + const createDialogFn = jest.fn(); + jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn); + + render(getComponent()); + + const changeButton = await screen.findByRole("button", { name: "Mock change password" }); + userEvent.click(changeButton); + + expect(changePasswordOnError).toBeDefined(); + changePasswordOnError(new Error(ERROR_STRING)); + + expect(createDialogFn).toHaveBeenCalledWith(expect.anything(), { + title: "Error changing password", + description: ERROR_STRING, + }); + }); + }); }); diff --git a/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap similarity index 94% rename from test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap rename to test/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap index 469d9ea503..2e5b6e5827 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` 3pids should display 3pid email addresses and phone numbers 1`] = ` +exports[` 3pids should display 3pid email addresses and phone numbers 1`] = `
    3pids should display 3pid email addresses an > @@ -66,7 +66,7 @@ exports[` 3pids should display 3pid email addresses an
    `; -exports[` 3pids should display 3pid email addresses and phone numbers 2`] = ` +exports[` 3pids should display 3pid email addresses and phone numbers 2`] = `
    3pids should display 3pid email addresses an @@ -175,7 +175,7 @@ exports[` 3pids should display 3pid email addresses an
    `; -exports[` deactive account should render section when account deactivation feature is enabled 1`] = ` +exports[` deactive account should render section when account deactivation feature is enabled 1`] = `