Use semantic headings in user settings - account (#10972)

* account password section

* account email and phone numbers

* update cypress selectors
pull/28788/head^2
Kerry 2023-05-26 10:42:01 +12:00 committed by GitHub
parent 8d77d6e4cc
commit d0d9a36d07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 103 additions and 105 deletions

View File

@ -83,10 +83,14 @@ describe("General user settings tab", () => {
}); });
// Wait until spinners disappear // Wait until spinners disappear
cy.get(".mx_GeneralUserSettingsTab_section--account .mx_Spinner").should("not.exist"); cy.findByTestId("accountSection").within(() => {
cy.get(".mx_GeneralUserSettingsTab_section--discovery .mx_Spinner").should("not.exist"); cy.get(".mx_Spinner").should("not.exist");
});
cy.findByTestId("discoverySection").within(() => {
cy.get(".mx_Spinner").should("not.exist");
});
cy.get(".mx_GeneralUserSettingsTab_section--account").within(() => { cy.findByTestId("accountSection").within(() => {
// Assert that input areas for changing a password exists // Assert that input areas for changing a password exists
cy.get("form.mx_GeneralUserSettingsTab_section--account_changePassword") cy.get("form.mx_GeneralUserSettingsTab_section--account_changePassword")
.scrollIntoView() .scrollIntoView()
@ -95,29 +99,28 @@ describe("General user settings tab", () => {
cy.findByLabelText("New Password").should("be.visible"); cy.findByLabelText("New Password").should("be.visible");
cy.findByLabelText("Confirm password").should("be.visible"); cy.findByLabelText("Confirm password").should("be.visible");
}); });
// Check email addresses area
cy.get(".mx_EmailAddresses")
.scrollIntoView()
.within(() => {
// Assert that an input area for a new email address is rendered
cy.findByRole("textbox", { name: "Email Address" }).should("be.visible");
// Assert the add button is visible
cy.findByRole("button", { name: "Add" }).should("be.visible");
});
// Check phone numbers area
cy.get(".mx_PhoneNumbers")
.scrollIntoView()
.within(() => {
// Assert that an input area for a new phone number is rendered
cy.findByRole("textbox", { name: "Phone Number" }).should("be.visible");
// Assert that the add button is rendered
cy.findByRole("button", { name: "Add" }).should("be.visible");
});
}); });
// Check email addresses area
cy.findByTestId("mx_AccountEmailAddresses")
.scrollIntoView()
.within(() => {
// Assert that an input area for a new email address is rendered
cy.findByRole("textbox", { name: "Email Address" }).should("be.visible");
// Assert the add button is visible
cy.findByRole("button", { name: "Add" }).should("be.visible");
});
// Check phone numbers area
cy.findByTestId("mx_AccountPhoneNumbers")
.scrollIntoView()
.within(() => {
// Assert that an input area for a new phone number is rendered
cy.findByRole("textbox", { name: "Phone Number" }).should("be.visible");
// Assert that the add button is rendered
cy.findByRole("button", { name: "Add" }).should("be.visible");
});
// Check language and region setting dropdown // Check language and region setting dropdown
cy.get(".mx_GeneralUserSettingsTab_section_languageInput") cy.get(".mx_GeneralUserSettingsTab_section_languageInput")
@ -188,7 +191,7 @@ describe("General user settings tab", () => {
it("should set a country calling code based on default_country_code", () => { it("should set a country calling code based on default_country_code", () => {
// Check phone numbers area // Check phone numbers area
cy.get(".mx_PhoneNumbers") cy.findByTestId("mx_AccountPhoneNumbers")
.scrollIntoView() .scrollIntoView()
.within(() => { .within(() => {
// Assert that an input area for a new phone number is rendered // Assert that an input area for a new phone number is rendered

View File

@ -17,16 +17,12 @@ limitations under the License.
.mx_TagComposer { .mx_TagComposer {
.mx_TagComposer_input { .mx_TagComposer_input {
display: flex; display: flex;
flex-direction: row;
.mx_Field {
flex: 1;
margin: 0; /* override from field styles */
}
.mx_AccessibleButton { .mx_AccessibleButton {
min-width: 70px; min-width: 70px;
padding: 0 8px; /* override from button styles */ padding: 0 8px; /* override from button styles */
margin-left: 16px; /* distance from <Field> */ align-self: stretch; /* override default settingstab style */
} }
.mx_Field, .mx_Field,

View File

@ -24,6 +24,24 @@ limitations under the License.
a { a {
color: $links; color: $links;
} }
form {
display: flex;
flex-direction: column;
gap: $spacing-8;
flex-grow: 1;
}
// never want full width buttons
// event when other content is 100% width
.mx_AccessibleButton {
align-self: flex-start;
justify-self: flex-start;
}
.mx_Field {
margin: 0;
flex: 1;
}
} }
.mx_SettingsTab_warningText { .mx_SettingsTab_warningText {

View File

@ -14,41 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_GeneralUserSettingsTab_section--account_changePassword {
.mx_Field {
margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end);
&:first-child {
margin-top: 0;
}
}
}
/* TODO: Make this selector less painful */
.mx_GeneralUserSettingsTab_section--account .mx_SettingsTab_subheading:nth-child(n + 1),
.mx_GeneralUserSettingsTab_section--discovery .mx_SettingsTab_subheading:nth-child(n + 2),
.mx_SetIdServer .mx_SettingsTab_subheading {
margin-top: 24px;
}
.mx_GeneralUserSettingsTab_section--account,
.mx_GeneralUserSettingsTab_section--discovery {
.mx_Spinner {
/* Move the spinner to the left side of the container (default center) */
justify-content: flex-start;
}
}
.mx_GeneralUserSettingsTab_section--account .mx_EmailAddresses,
.mx_GeneralUserSettingsTab_section--account .mx_PhoneNumbers,
.mx_GeneralUserSettingsTab_section--discovery .mx_GeneralUserSettingsTab_section--discovery_existing {
margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end);
}
.mx_GeneralUserSettingsTab_section--discovery_existing { .mx_GeneralUserSettingsTab_section--discovery_existing {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 5px;
} }
.mx_GeneralUserSettingsTab_section--discovery_existing_address, .mx_GeneralUserSettingsTab_section--discovery_existing_address,

View File

@ -276,7 +276,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
} }
return ( return (
<div className="mx_EmailAddresses"> <>
{existingEmailElements} {existingEmailElements}
<form onSubmit={this.onAddClick} autoComplete="off" noValidate={true}> <form onSubmit={this.onAddClick} autoComplete="off" noValidate={true}>
<Field <Field
@ -289,7 +289,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
/> />
{addButton} {addButton}
</form> </form>
</div> </>
); );
} }
} }

View File

@ -305,7 +305,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
); );
return ( return (
<div className="mx_PhoneNumbers"> <>
{existingPhoneElements} {existingPhoneElements}
<form onSubmit={this.onAddClick} autoComplete="off" noValidate={true} className="mx_PhoneNumbers_new"> <form onSubmit={this.onAddClick} autoComplete="off" noValidate={true} className="mx_PhoneNumbers_new">
<div className="mx_PhoneNumbers_input"> <div className="mx_PhoneNumbers_input">
@ -321,7 +321,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
</div> </div>
</form> </form>
{addVerifySection} {addVerifySection}
</div> </>
); );
} }
} }

View File

@ -294,7 +294,7 @@ export default class PhoneNumbers extends React.Component<IProps> {
return ( return (
<SettingsSubsection <SettingsSubsection
data-testid="mx_PhoneNumbers" data-testid="mx_DiscoveryPhoneNumbers"
heading={_t("Phone numbers")} heading={_t("Phone numbers")}
description={description} description={description}
stretchContent stretchContent

View File

@ -60,6 +60,7 @@ import { SettingsSection } from "../../shared/SettingsSection";
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading"; import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading";
import Heading from "../../../typography/Heading"; import Heading from "../../../typography/Heading";
import InlineSpinner from "../../../elements/InlineSpinner";
interface IProps { interface IProps {
closeSettingsFn: () => void; closeSettingsFn: () => void;
@ -196,7 +197,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
); );
logger.warn(e); logger.warn(e);
} }
this.setState({ this.setState({
emails: threepids.filter((a) => a.medium === ThreepidMedium.Email), emails: threepids.filter((a) => a.medium === ThreepidMedium.Email),
msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone), msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone),
@ -329,16 +329,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
} }
private renderAccountSection(): JSX.Element { private renderAccountSection(): JSX.Element {
let passwordChangeForm: ReactNode = (
<ChangePassword
className="mx_GeneralUserSettingsTab_section--account_changePassword"
rowClassName=""
buttonKind="primary"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged}
/>
);
let threepidSection: ReactNode = null; let threepidSection: ReactNode = null;
// For older homeservers without separate 3PID add and bind methods (MSC2290), // For older homeservers without separate 3PID add and bind methods (MSC2290),
@ -351,33 +341,52 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
(this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true) (this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true)
) { ) {
const emails = this.state.loading3pids ? ( const emails = this.state.loading3pids ? (
<Spinner /> <InlineSpinner />
) : ( ) : (
<AccountEmailAddresses emails={this.state.emails} onEmailsChange={this.onEmailsChange} /> <AccountEmailAddresses emails={this.state.emails} onEmailsChange={this.onEmailsChange} />
); );
const msisdns = this.state.loading3pids ? ( const msisdns = this.state.loading3pids ? (
<Spinner /> <InlineSpinner />
) : ( ) : (
<AccountPhoneNumbers msisdns={this.state.msisdns} onMsisdnsChange={this.onMsisdnsChange} /> <AccountPhoneNumbers msisdns={this.state.msisdns} onMsisdnsChange={this.onMsisdnsChange} />
); );
threepidSection = ( threepidSection = (
<div> <>
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span> <SettingsSubsection
{emails} heading={_t("Email addresses")}
stretchContent
data-testid="mx_AccountEmailAddresses"
>
{emails}
</SettingsSubsection>
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span> <SettingsSubsection
{msisdns} heading={_t("Phone numbers")}
</div> stretchContent
data-testid="mx_AccountPhoneNumbers"
>
{msisdns}
</SettingsSubsection>
</>
); );
} else if (this.state.serverSupportsSeparateAddAndBind === null) { } else if (this.state.serverSupportsSeparateAddAndBind === null) {
threepidSection = <Spinner />; threepidSection = <Spinner />;
} }
let passwordChangeText: ReactNode = _t("Set a new account password…"); let passwordChangeSection: ReactNode = null;
if (!this.state.canChangePassword) { if (this.state.canChangePassword) {
// Just don't show anything if you can't do anything. passwordChangeSection = (
passwordChangeText = null; <>
passwordChangeForm = null; <SettingsSubsectionText>{_t("Set a new account password…")}</SettingsSubsectionText>
<ChangePassword
className="mx_GeneralUserSettingsTab_section--account_changePassword"
rowClassName=""
buttonKind="primary"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged}
/>
</>
);
} }
let externalAccountManagement: JSX.Element | undefined; let externalAccountManagement: JSX.Element | undefined;
@ -386,13 +395,13 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
externalAccountManagement = ( externalAccountManagement = (
<> <>
<p className="mx_SettingsTab_subsectionText" data-testid="external-account-management-outer"> <SettingsSubsectionText data-testid="external-account-management-outer">
{_t( {_t(
"Your account details are managed separately at <code>%(hostname)s</code>.", "Your account details are managed separately at <code>%(hostname)s</code>.",
{ hostname }, { hostname },
{ code: (sub) => <code>{sub}</code> }, { code: (sub) => <code>{sub}</code> },
)} )}
</p> </SettingsSubsectionText>
<AccessibleButton <AccessibleButton
onClick={null} onClick={null}
element="a" element="a"
@ -408,13 +417,13 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
); );
} }
return ( return (
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_section--account"> <>
<span className="mx_SettingsTab_subheading">{_t("Account")}</span> <SettingsSubsection heading={_t("Account")} stretchContent data-testid="accountSection">
{externalAccountManagement} {externalAccountManagement}
<p className="mx_SettingsTab_subsectionText">{passwordChangeText}</p> {passwordChangeSection}
{passwordChangeForm} </SettingsSubsection>
{threepidSection} {threepidSection}
</div> </>
); );
} }
@ -540,7 +549,11 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
{_t("Discovery")} {_t("Discovery")}
</Heading> </Heading>
); );
discoverySection = <SettingsSection heading={heading}>{this.renderDiscoverySection()}</SettingsSection>; discoverySection = (
<SettingsSection heading={heading} data-testid="discoverySection">
{this.renderDiscoverySection()}
</SettingsSection>
);
} }
return ( return (

View File

@ -4,7 +4,7 @@ exports[`<PhoneNumbers /> should handle no numbers 1`] = `
<div> <div>
<div <div
class="mx_SettingsSubsection" class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers" data-testid="mx_DiscoveryPhoneNumbers"
> >
<div <div
class="mx_SettingsSubsectionHeading" class="mx_SettingsSubsectionHeading"
@ -32,7 +32,7 @@ exports[`<PhoneNumbers /> should render a loader while loading 1`] = `
<div> <div>
<div <div
class="mx_SettingsSubsection" class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers" data-testid="mx_DiscoveryPhoneNumbers"
> >
<div <div
class="mx_SettingsSubsectionHeading" class="mx_SettingsSubsectionHeading"
@ -64,7 +64,7 @@ exports[`<PhoneNumbers /> should render phone numbers 1`] = `
<div> <div>
<div <div
class="mx_SettingsSubsection" class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers" data-testid="mx_DiscoveryPhoneNumbers"
> >
<div <div
class="mx_SettingsSubsectionHeading" class="mx_SettingsSubsectionHeading"