Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 8cd84b0

Browse files
Kerryrichvdh
Kerry
andauthored
Use semantic headings in user settings - integrations and account deletion (#10837)
* allow testids in settings sections * use semantic headings in LabsUserSettingsTab * put back margin var * use SettingsTab wrapper * use semantic headings for deactivate acc section * use semantic heading in manage integratios * i18n * explicit cast to boolean * Update src/components/views/settings/shared/SettingsSubsection.tsx Co-authored-by: Richard van der Hoff <[email protected]> * test manage integration settings * test deactivate account section display * remove debug * fix cypress test --------- Co-authored-by: Richard van der Hoff <[email protected]>
1 parent c368748 commit 8cd84b0

File tree

7 files changed

+232
-54
lines changed

7 files changed

+232
-54
lines changed

cypress/e2e/settings/general-user-settings-tab.spec.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ describe("General user settings tab", () => {
4343
// Exclude userId from snapshots
4444
const percyCSS = ".mx_ProfileSettings_profile_controls_userId { visibility: hidden !important; }";
4545

46-
cy.get(".mx_SettingsTab.mx_GeneralUserSettingsTab").percySnapshotElement("User settings tab - General", {
46+
cy.findByTestId("mx_GeneralUserSettingsTab").percySnapshotElement("User settings tab - General", {
4747
percyCSS,
4848
// Emulate TabbedView's actual min and max widths
4949
// 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
5050
// 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
5151
widths: [580, 796],
5252
});
5353

54-
cy.get(".mx_SettingsTab.mx_GeneralUserSettingsTab").within(() => {
54+
cy.findByTestId("mx_GeneralUserSettingsTab").within(() => {
5555
// Assert that the top heading is rendered
5656
cy.findByTestId("general").should("have.text", "General").should("be.visible");
5757

@@ -156,16 +156,10 @@ describe("General user settings tab", () => {
156156
// Make sure integration manager's toggle switch is enabled
157157
cy.get(".mx_ToggleSwitch_enabled").should("be.visible");
158158

159-
// Assert space between "Manage integrations" and the integration server address is set to 4px;
160-
cy.get(".mx_SetIntegrationManager_heading_manager").should("have.css", "column-gap", "4px");
161-
162-
cy.get(".mx_SetIntegrationManager_heading_manager").within(() => {
163-
cy.get(".mx_SettingsTab_heading").should("have.text", "Manage integrations");
164-
165-
// Assert the headings' inline end margin values are set to zero in favor of the column-gap declaration
166-
cy.get(".mx_SettingsTab_heading").should("have.css", "margin-inline-end", "0px");
167-
cy.get(".mx_SettingsTab_subheading").should("have.css", "margin-inline-end", "0px");
168-
});
159+
cy.get(".mx_SetIntegrationManager_heading_manager").should(
160+
"have.text",
161+
"Manage integrations(scalar.vector.im)",
162+
);
169163
});
170164

171165
// Assert the account deactivation button is displayed
@@ -178,7 +172,7 @@ describe("General user settings tab", () => {
178172
});
179173

180174
it("should support adding and removing a profile picture", () => {
181-
cy.get(".mx_SettingsTab.mx_GeneralUserSettingsTab .mx_ProfileSettings").within(() => {
175+
cy.get(".mx_SettingsTab .mx_ProfileSettings").within(() => {
182176
// Upload a picture
183177
cy.get(".mx_ProfileSettings_avatarUpload").selectFile("cypress/fixtures/riot.png", { force: true });
184178

@@ -225,7 +219,7 @@ describe("General user settings tab", () => {
225219
});
226220

227221
it("should support changing a display name", () => {
228-
cy.get(".mx_SettingsTab.mx_GeneralUserSettingsTab .mx_ProfileSettings").within(() => {
222+
cy.get(".mx_SettingsTab .mx_ProfileSettings").within(() => {
229223
// Change the diaplay name to USER_NAME_NEW
230224
cy.findByRole("textbox", { name: "Display Name" }).type(`{selectAll}{del}${USER_NAME_NEW}{enter}`);
231225
});

res/css/views/settings/_SetIntegrationManager.pcss

-8
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,12 @@ limitations under the License.
1717
.mx_SetIntegrationManager {
1818
.mx_SettingsFlag {
1919
align-items: center;
20-
margin-top: var(--SettingsTab_heading_nth_child-margin-top);
2120

2221
.mx_SetIntegrationManager_heading_manager {
2322
display: flex;
2423
align-items: center;
2524
flex-wrap: wrap;
2625
column-gap: $spacing-4;
27-
28-
.mx_SettingsTab_heading,
29-
.mx_SettingsTab_subheading {
30-
margin-top: 0;
31-
margin-bottom: 0;
32-
margin-inline-end: 0; /* Cancel the default right (inline-end) margin */
33-
}
3426
}
3527

3628
.mx_ToggleSwitch {

src/components/views/settings/SetIntegrationManager.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { IntegrationManagerInstance } from "../../../integrations/IntegrationMan
2323
import SettingsStore from "../../../settings/SettingsStore";
2424
import { SettingLevel } from "../../../settings/SettingLevel";
2525
import ToggleSwitch from "../elements/ToggleSwitch";
26+
import Heading from "../typography/Heading";
27+
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
2628

2729
interface IProps {}
2830

@@ -70,11 +72,15 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
7072
}
7173

7274
return (
73-
<label className="mx_SetIntegrationManager" htmlFor="toggle_integration">
75+
<label
76+
className="mx_SetIntegrationManager"
77+
data-testid="mx_SetIntegrationManager"
78+
htmlFor="toggle_integration"
79+
>
7480
<div className="mx_SettingsFlag">
7581
<div className="mx_SetIntegrationManager_heading_manager">
76-
<span className="mx_SettingsTab_heading">{_t("Manage integrations")}</span>
77-
<span className="mx_SettingsTab_subheading">{managerName}</span>
82+
<Heading size="h2">{_t("Manage integrations")}</Heading>
83+
<Heading size="h3">{managerName}</Heading>
7884
</div>
7985
<ToggleSwitch
8086
id="toggle_integration"
@@ -83,13 +89,13 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
8389
onChange={this.onProvisioningToggled}
8490
/>
8591
</div>
86-
<div className="mx_SettingsTab_subsectionText">{bodyText}</div>
87-
<div className="mx_SettingsTab_subsectionText">
92+
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
93+
<SettingsSubsectionText>
8894
{_t(
8995
"Integration managers receive configuration data, and can modify widgets, " +
9096
"send room invites, and set power levels on your behalf.",
9197
)}
92-
</div>
98+
</SettingsSubsectionText>
9399
</label>
94100
);
95101
}

src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx

+19-24
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ import SetIdServer from "../../SetIdServer";
5454
import SetIntegrationManager from "../../SetIntegrationManager";
5555
import ToggleSwitch from "../../../elements/ToggleSwitch";
5656
import { IS_MAC } from "../../../../../Keyboard";
57+
import SettingsTab from "../SettingsTab";
58+
import { SettingsSection } from "../../shared/SettingsSection";
59+
import SettingsSubsection from "../../shared/SettingsSubsection";
5760

5861
interface IProps {
5962
closeSettingsFn: () => void;
@@ -492,27 +495,24 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
492495
private renderManagementSection(): JSX.Element {
493496
// TODO: Improve warning text for account deactivation
494497
return (
495-
<div className="mx_SettingsTab_section" data-testid="account-management-section">
496-
<span className="mx_SettingsTab_subheading">{_t("Account management")}</span>
497-
<span className="mx_SettingsTab_subsectionText">
498-
{_t("Deactivating your account is a permanent action — be careful!")}
499-
</span>
500-
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
501-
{_t("Deactivate Account")}
502-
</AccessibleButton>
503-
</div>
498+
<SettingsSection heading={_t("Deactivate account")}>
499+
<SettingsSubsection
500+
heading={_t("Account management")}
501+
data-testid="account-management-section"
502+
description={_t("Deactivating your account is a permanent action — be careful!")}
503+
>
504+
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
505+
{_t("Deactivate Account")}
506+
</AccessibleButton>
507+
</SettingsSubsection>
508+
</SettingsSection>
504509
);
505510
}
506511

507512
private renderIntegrationManagerSection(): ReactNode {
508513
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
509514

510-
return (
511-
<div className="mx_SettingsTab_section">
512-
{/* has its own heading as it includes the current integration manager */}
513-
<SetIntegrationManager />
514-
</div>
515-
);
515+
return <SetIntegrationManager />;
516516
}
517517

518518
public render(): React.ReactNode {
@@ -531,12 +531,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
531531

532532
let accountManagementSection: JSX.Element | undefined;
533533
if (SettingsStore.getValue(UIFeature.Deactivate)) {
534-
accountManagementSection = (
535-
<>
536-
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div>
537-
{this.renderManagementSection()}
538-
</>
539-
);
534+
accountManagementSection = this.renderManagementSection();
540535
}
541536

542537
let discoverySection;
@@ -552,7 +547,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
552547
}
553548

554549
return (
555-
<div className="mx_SettingsTab mx_GeneralUserSettingsTab">
550+
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
556551
<div className="mx_SettingsTab_heading" data-testid="general">
557552
{_t("General")}
558553
</div>
@@ -561,9 +556,9 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
561556
{this.renderLanguageSection()}
562557
{supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null}
563558
{discoverySection}
564-
{this.renderIntegrationManagerSection() /* Has its own title */}
559+
{this.renderIntegrationManagerSection()}
565560
{accountManagementSection}
566-
</div>
561+
</SettingsTab>
567562
);
568563
}
569564
}

src/i18n/strings/en_EN.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1568,10 +1568,10 @@
15681568
"Language and region": "Language and region",
15691569
"Spell check": "Spell check",
15701570
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.",
1571+
"Deactivate account": "Deactivate account",
15711572
"Account management": "Account management",
15721573
"Deactivating your account is a permanent action — be careful!": "Deactivating your account is a permanent action — be careful!",
15731574
"Deactivate Account": "Deactivate Account",
1574-
"Deactivate account": "Deactivate account",
15751575
"Discovery": "Discovery",
15761576
"%(brand)s version:": "%(brand)s version:",
15771577
"Olm version:": "Olm version:",

test/components/views/settings/tabs/user/GeneralUserSettingsTab-test.tsx

+86-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
See the License for the specific language governing permissions and
1111
limitations under the License.
1212
*/
13-
import { render } from "@testing-library/react";
13+
14+
import { fireEvent, render, screen, within } from "@testing-library/react";
1415
import React from "react";
1516
import { M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
17+
import { logger } from "matrix-js-sdk/src/logger";
1618

1719
import GeneralUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/GeneralUserSettingsTab";
1820
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
@@ -24,6 +26,8 @@ import {
2426
mockPlatformPeg,
2527
flushPromises,
2628
} from "../../../../../test-utils";
29+
import { UIFeature } from "../../../../../../src/settings/UIFeature";
30+
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
2731

2832
describe("<GeneralUserSettingsTab />", () => {
2933
const defaultProps = {
@@ -49,6 +53,8 @@ describe("<GeneralUserSettingsTab />", () => {
4953
mockPlatformPeg();
5054
jest.clearAllMocks();
5155
clientWellKnownSpy.mockReturnValue({});
56+
jest.spyOn(SettingsStore, "getValue").mockRestore();
57+
jest.spyOn(logger, "error").mockRestore();
5258
});
5359

5460
it("does not show account management link when not available", () => {
@@ -74,4 +80,83 @@ describe("<GeneralUserSettingsTab />", () => {
7480
expect(getByTestId("external-account-management-outer").textContent).toMatch(/.*id\.server\.org/);
7581
expect(getByTestId("external-account-management-link").getAttribute("href")).toMatch(accountManagementLink);
7682
});
83+
84+
describe("Manage integrations", () => {
85+
it("should not render manage integrations section when widgets feature is disabled", () => {
86+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
87+
(settingName) => settingName !== UIFeature.Widgets,
88+
);
89+
render(getComponent());
90+
91+
expect(screen.queryByTestId("mx_SetIntegrationManager")).not.toBeInTheDocument();
92+
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Widgets);
93+
});
94+
it("should render manage integrations sections", () => {
95+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
96+
(settingName) => settingName === UIFeature.Widgets,
97+
);
98+
99+
render(getComponent());
100+
101+
expect(screen.getByTestId("mx_SetIntegrationManager")).toMatchSnapshot();
102+
});
103+
it("should update integrations provisioning on toggle", () => {
104+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
105+
(settingName) => settingName === UIFeature.Widgets,
106+
);
107+
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
108+
109+
render(getComponent());
110+
111+
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
112+
fireEvent.click(within(integrationSection).getByRole("switch"));
113+
114+
expect(SettingsStore.setValue).toHaveBeenCalledWith(
115+
"integrationProvisioning",
116+
null,
117+
SettingLevel.ACCOUNT,
118+
true,
119+
);
120+
expect(within(integrationSection).getByRole("switch")).toBeChecked();
121+
});
122+
it("handles error when updating setting fails", async () => {
123+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
124+
(settingName) => settingName === UIFeature.Widgets,
125+
);
126+
jest.spyOn(logger, "error").mockImplementation(() => {});
127+
128+
jest.spyOn(SettingsStore, "setValue").mockRejectedValue("oups");
129+
130+
render(getComponent());
131+
132+
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
133+
fireEvent.click(within(integrationSection).getByRole("switch"));
134+
135+
await flushPromises();
136+
137+
expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
138+
expect(logger.error).toHaveBeenCalledWith("oups");
139+
expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
140+
});
141+
});
142+
143+
describe("deactive account", () => {
144+
it("should not render section when account deactivation feature is disabled", () => {
145+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
146+
(settingName) => settingName !== UIFeature.Deactivate,
147+
);
148+
render(getComponent());
149+
150+
expect(screen.queryByText("Deactivate account")).not.toBeInTheDocument();
151+
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Deactivate);
152+
});
153+
it("should render section when account deactivation feature is enabled", () => {
154+
jest.spyOn(SettingsStore, "getValue").mockImplementation(
155+
(settingName) => settingName === UIFeature.Deactivate,
156+
);
157+
render(getComponent());
158+
159+
expect(screen.getByText("Deactivate account").parentElement!).toMatchSnapshot();
160+
});
161+
});
77162
});

0 commit comments

Comments
 (0)