Use semantic headings in user settings - discovery (#10838)

* 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

* use currentColor in warning-triangle svg, update use in RoomStatusBar

* use semantic headings for discovery section

* test manage integration settings

* test deactivate account section display

* remove SettingsFieldset margins

* threepids styles

* remove debug

* test discovery email and phone
pull/28788/head^2
Kerry 2023-05-24 14:37:10 +12:00 committed by GitHub
parent 9211da20f4
commit 9f011b955b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 407 additions and 123 deletions

View File

@ -160,7 +160,7 @@ limitations under the License.
} }
} }
.mx_RoomStatusBar_connectionLostBar img { .mx_RoomStatusBar_connectionLostBar svg {
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
vertical-align: middle; vertical-align: middle;

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019, 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,8 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_SetIdServer .mx_Field_input { .mx_SetIdServer {
margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); display: flex;
flex-direction: column;
align-items: flex-start;
gap: $spacing-8;
.mx_Field {
width: 100%;
margin: 0;
}
} }
.mx_SetIdServer_tooltip { .mx_SetIdServer_tooltip {

View File

@ -15,7 +15,6 @@ limitations under the License.
*/ */
.mx_SettingsFieldset { .mx_SettingsFieldset {
margin: 10px 80px 10px 0;
box-sizing: content-box; box-sizing: content-box;
} }
@ -31,8 +30,6 @@ limitations under the License.
} }
.mx_SettingsFieldset_description { .mx_SettingsFieldset_description {
color: $settings-subsection-fg-color;
font-size: $font-14px;
display: block; display: block;
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -61,6 +61,8 @@ limitations under the License.
margin-left: 5px; margin-left: 5px;
} }
.mx_GeneralUserSettingsTab_heading_warningIcon { .mx_GeneralUserSettingsTab_warningIcon {
vertical-align: middle; vertical-align: middle;
margin-right: $spacing-8;
margin-bottom: 2px;
} }

View File

@ -3,7 +3,7 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 14 12" style="enable-background:new 0 0 14 12;" xml:space="preserve"> viewBox="0 0 14 12" style="enable-background:new 0 0 14 12;" xml:space="preserve">
<style type="text/css"> <style type="text/css">
.st0{fill:none;stroke:#454545;stroke-linecap:round;stroke-linejoin:round;} .st0{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;}
</style> </style>
<g> <g>
<path class="st0" d="M6,1.5L1.2,9.3c-0.2,0.3-0.2,0.8,0,1.1c0.2,0.3,0.6,0.6,1,0.6h9.7c0.4,0,0.8-0.2,1-0.6c0.2-0.3,0.2-0.8,0-1.1 <path class="st0" d="M6,1.5L1.2,9.3c-0.2,0.3-0.2,0.8,0,1.1c0.2,0.3,0.6,0.6,1,0.6h9.7c0.4,0,0.8-0.2,1-0.6c0.2-0.3,0.2-0.8,0-1.1

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 709 B

View File

@ -20,6 +20,7 @@ import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixError } from "matrix-js-sdk/src/matrix"; import { MatrixError } from "matrix-js-sdk/src/matrix";
import { Icon as WarningIcon } from "../../../res/img/feather-customised/warning-triangle.svg";
import { _t, _td } from "../../languageHandler"; import { _t, _td } from "../../languageHandler";
import Resend from "../../Resend"; import Resend from "../../Resend";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
@ -279,12 +280,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
<div className="mx_RoomStatusBar"> <div className="mx_RoomStatusBar">
<div role="alert"> <div role="alert">
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">
<img <WarningIcon width="24" height="24" />
src={require("../../../res/img/feather-customised/warning-triangle.svg").default}
width="24"
height="24"
alt=""
/>
<div> <div>
<div className="mx_RoomStatusBar_connectionLostBar_title"> <div className="mx_RoomStatusBar_connectionLostBar_title">
{_t("Connectivity to the server has been lost.")} {_t("Connectivity to the server has been lost.")}

View File

@ -32,6 +32,8 @@ import InlineSpinner from "../elements/InlineSpinner";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import Field from "../elements/Field"; import Field from "../elements/Field";
import QuestionDialog from "../dialogs/QuestionDialog"; import QuestionDialog from "../dialogs/QuestionDialog";
import SettingsFieldset from "./SettingsFieldset";
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
// We'll wait up to this long when checking for 3PID bindings on the IS. // We'll wait up to this long when checking for 3PID bindings on the IS.
const REACHABILITY_TIMEOUT = 10000; // ms const REACHABILITY_TIMEOUT = 10000; // ms
@ -428,19 +430,18 @@ export default class SetIdServer extends React.Component<IProps, IState> {
discoButtonContent = <InlineSpinner />; discoButtonContent = <InlineSpinner />;
} }
discoSection = ( discoSection = (
<div> <>
<span className="mx_SettingsTab_subsectionText">{discoBodyText}</span> <SettingsSubsectionText>{discoBodyText}</SettingsSubsectionText>
<AccessibleButton onClick={this.onDisconnectClicked} kind="danger_sm"> <AccessibleButton onClick={this.onDisconnectClicked} kind="danger_sm">
{discoButtonContent} {discoButtonContent}
</AccessibleButton> </AccessibleButton>
</div> </>
); );
} }
return ( return (
<SettingsFieldset legend={sectionTitle} description={bodyText}>
<form className="mx_SetIdServer" onSubmit={this.checkIdServer}> <form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
<span className="mx_SettingsTab_subheading">{sectionTitle}</span>
<span className="mx_SettingsTab_subsectionText">{bodyText}</span>
<Field <Field
label={_t("Enter a new identity server")} label={_t("Enter a new identity server")}
type="text" type="text"
@ -463,6 +464,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
</AccessibleButton> </AccessibleButton>
{discoSection} {discoSection}
</form> </form>
</SettingsFieldset>
); );
} }
} }

View File

@ -15,6 +15,8 @@ limitations under the License.
import React, { ReactNode, HTMLAttributes } from "react"; import React, { ReactNode, HTMLAttributes } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
interface Props extends HTMLAttributes<HTMLFieldSetElement> { interface Props extends HTMLAttributes<HTMLFieldSetElement> {
// section title // section title
legend: string | ReactNode; legend: string | ReactNode;
@ -24,7 +26,11 @@ interface Props extends HTMLAttributes<HTMLFieldSetElement> {
const SettingsFieldset: React.FC<Props> = ({ legend, className, children, description, ...rest }) => ( const SettingsFieldset: React.FC<Props> = ({ legend, className, children, description, ...rest }) => (
<fieldset {...rest} className={classNames("mx_SettingsFieldset", className)}> <fieldset {...rest} className={classNames("mx_SettingsFieldset", className)}>
<legend className="mx_SettingsFieldset_legend">{legend}</legend> <legend className="mx_SettingsFieldset_legend">{legend}</legend>
{description && <div className="mx_SettingsFieldset_description">{description}</div>} {description && (
<div className="mx_SettingsFieldset_description">
<SettingsSubsectionText>{description}</SettingsSubsectionText>
</div>
)}
{children} {children}
</fieldset> </fieldset>
); );

View File

@ -25,6 +25,8 @@ import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import Modal from "../../../../Modal"; import Modal from "../../../../Modal";
import AddThreepid, { Binding } from "../../../../AddThreepid"; import AddThreepid, { Binding } from "../../../../AddThreepid";
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog"; import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
import SettingsSubsection from "../shared/SettingsSubsection";
import InlineSpinner from "../../elements/InlineSpinner";
import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
/* /*
@ -258,23 +260,32 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
} }
interface IProps { interface IProps {
emails: IThreepid[]; emails: IThreepid[];
isLoading?: boolean;
} }
export default class EmailAddresses extends React.Component<IProps> { export default class EmailAddresses extends React.Component<IProps> {
public render(): React.ReactNode { public render(): React.ReactNode {
let content; let content;
if (this.props.emails.length > 0) { if (this.props.isLoading) {
content = <InlineSpinner />;
} else if (this.props.emails.length > 0) {
content = this.props.emails.map((e) => { content = this.props.emails.map((e) => {
return <EmailAddress email={e} key={e.address} />; return <EmailAddress email={e} key={e.address} />;
}); });
} else {
content = (
<span className="mx_SettingsTab_subsectionText">
{_t("Discovery options will appear once you have added an email above.")}
</span>
);
} }
return <div className="mx_EmailAddresses">{content}</div>; const hasEmails = !!this.props.emails.length;
return (
<SettingsSubsection
heading={_t("Email addresses")}
description={
(!hasEmails && _t("Discovery options will appear once you have added an email above.")) || undefined
}
stretchContent
>
{content}
</SettingsSubsection>
);
} }
} }

View File

@ -26,6 +26,8 @@ import Modal from "../../../../Modal";
import AddThreepid, { Binding } from "../../../../AddThreepid"; import AddThreepid, { Binding } from "../../../../AddThreepid";
import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog"; import ErrorDialog, { extractErrorMessageFromError } from "../../dialogs/ErrorDialog";
import Field from "../../elements/Field"; import Field from "../../elements/Field";
import SettingsSubsection from "../shared/SettingsSubsection";
import InlineSpinner from "../../elements/InlineSpinner";
import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
/* /*
@ -273,23 +275,32 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
interface IProps { interface IProps {
msisdns: IThreepid[]; msisdns: IThreepid[];
isLoading?: boolean;
} }
export default class PhoneNumbers extends React.Component<IProps> { export default class PhoneNumbers extends React.Component<IProps> {
public render(): React.ReactNode { public render(): React.ReactNode {
let content; let content;
if (this.props.msisdns.length > 0) { if (this.props.isLoading) {
content = <InlineSpinner />;
} else if (this.props.msisdns.length > 0) {
content = this.props.msisdns.map((e) => { content = this.props.msisdns.map((e) => {
return <PhoneNumber msisdn={e} key={e.address} />; return <PhoneNumber msisdn={e} key={e.address} />;
}); });
} else {
content = (
<span className="mx_SettingsTab_subsectionText">
{_t("Discovery options will appear once you have added a phone number above.")}
</span>
);
} }
return <div className="mx_PhoneNumbers">{content}</div>; const description =
(!content && _t("Discovery options will appear once you have added a phone number above.")) || undefined;
return (
<SettingsSubsection
data-testid="mx_PhoneNumbers"
heading={_t("Phone numbers")}
description={description}
stretchContent
>
{content}
</SettingsSubsection>
);
} }
} }

View File

@ -47,6 +47,7 @@ export const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({
<SettingsSubsectionText>{description}</SettingsSubsectionText> <SettingsSubsectionText>{description}</SettingsSubsectionText>
</div> </div>
)} )}
{!!children && (
<div <div
className={classNames("mx_SettingsSubsection_content", { className={classNames("mx_SettingsSubsection_content", {
mx_SettingsSubsection_contentStretch: !!stretchContent, mx_SettingsSubsection_contentStretch: !!stretchContent,
@ -55,6 +56,7 @@ export const SettingsSubsection: React.FC<SettingsSubsectionProps> = ({
> >
{children} {children}
</div> </div>
)}
</div> </div>
); );

View File

@ -18,11 +18,12 @@ limitations under the License.
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import { IThreepid } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IDelegatedAuthConfig, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix"; import { IDelegatedAuthConfig, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix";
import { HTTPError } from "matrix-js-sdk/src/matrix"; import { HTTPError } from "matrix-js-sdk/src/matrix";
import { Icon as WarningIcon } from "../../../../../../res/img/feather-customised/warning-triangle.svg";
import { UserFriendlyError, _t } from "../../../../../languageHandler"; import { UserFriendlyError, _t } from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings"; import ProfileSettings from "../../ProfileSettings";
import * as languageHandler from "../../../../../languageHandler"; import * as languageHandler from "../../../../../languageHandler";
@ -56,8 +57,9 @@ import ToggleSwitch from "../../../elements/ToggleSwitch";
import { IS_MAC } from "../../../../../Keyboard"; import { IS_MAC } from "../../../../../Keyboard";
import SettingsTab from "../SettingsTab"; import SettingsTab from "../SettingsTab";
import { SettingsSection } from "../../shared/SettingsSection"; import { SettingsSection } from "../../shared/SettingsSection";
import SettingsSubsection from "../../shared/SettingsSubsection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading"; import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading";
import Heading from "../../../typography/Heading";
interface IProps { interface IProps {
closeSettingsFn: () => void; closeSettingsFn: () => void;
@ -194,9 +196,10 @@ 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 === "email"), emails: threepids.filter((a) => a.medium === ThreepidMedium.Email),
msisdns: threepids.filter((a) => a.medium === "msisdn"), msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone),
loading3pids: false, loading3pids: false,
}); });
} }
@ -449,16 +452,16 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private renderDiscoverySection(): JSX.Element { private renderDiscoverySection(): JSX.Element {
if (this.state.requiredPolicyInfo.hasTerms) { if (this.state.requiredPolicyInfo.hasTerms) {
const intro = ( const intro = (
<span className="mx_SettingsTab_subsectionText"> <SettingsSubsectionText>
{_t( {_t(
"Agree to the identity server (%(serverName)s) Terms of Service to " + "Agree to the identity server (%(serverName)s) Terms of Service to " +
"allow yourself to be discoverable by email address or phone number.", "allow yourself to be discoverable by email address or phone number.",
{ serverName: this.state.idServerName }, { serverName: this.state.idServerName },
)} )}
</span> </SettingsSubsectionText>
); );
return ( return (
<div> <>
<InlineTermsAgreement <InlineTermsAgreement
policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices} policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices}
agreedUrls={this.state.requiredPolicyInfo.agreedUrls} agreedUrls={this.state.requiredPolicyInfo.agreedUrls}
@ -467,29 +470,23 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
/> />
{/* has its own heading as it includes the current identity server */} {/* has its own heading as it includes the current identity server */}
<SetIdServer missingTerms={true} /> <SetIdServer missingTerms={true} />
</div> </>
); );
} }
const emails = this.state.loading3pids ? <Spinner /> : <DiscoveryEmailAddresses emails={this.state.emails} />;
const msisdns = this.state.loading3pids ? <Spinner /> : <DiscoveryPhoneNumbers msisdns={this.state.msisdns} />;
const threepidSection = this.state.haveIdServer ? ( const threepidSection = this.state.haveIdServer ? (
<> <>
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span> <DiscoveryEmailAddresses emails={this.state.emails} isLoading={this.state.loading3pids} />
{emails} <DiscoveryPhoneNumbers msisdns={this.state.msisdns} isLoading={this.state.loading3pids} />
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
{msisdns}
</> </>
) : null; ) : null;
return ( return (
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_section--discovery"> <>
{threepidSection} {threepidSection}
{/* has its own heading as it includes the current identity server */} {/* has its own heading as it includes the current identity server */}
<SetIdServer missingTerms={false} /> <SetIdServer missingTerms={false} />
</div> </>
); );
} }
@ -520,16 +517,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
const supportsMultiLanguageSpellCheck = plaf?.supportsSpellCheckSettings(); const supportsMultiLanguageSpellCheck = plaf?.supportsSpellCheckSettings();
const discoWarning = this.state.requiredPolicyInfo.hasTerms ? (
<img
className="mx_GeneralUserSettingsTab_heading_warningIcon"
src={require("../../../../../../res/img/feather-customised/warning-triangle.svg").default}
width="18"
height="18"
alt={_t("Warning")}
/>
) : null;
let accountManagementSection: JSX.Element | undefined; let accountManagementSection: JSX.Element | undefined;
if (SettingsStore.getValue(UIFeature.Deactivate)) { if (SettingsStore.getValue(UIFeature.Deactivate)) {
accountManagementSection = this.renderManagementSection(); accountManagementSection = this.renderManagementSection();
@ -537,14 +524,23 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
let discoverySection; let discoverySection;
if (SettingsStore.getValue(UIFeature.IdentityServer)) { if (SettingsStore.getValue(UIFeature.IdentityServer)) {
discoverySection = ( const discoWarning = this.state.requiredPolicyInfo.hasTerms ? (
<> <WarningIcon
<div className="mx_SettingsTab_heading"> className="mx_GeneralUserSettingsTab_warningIcon"
{discoWarning} {_t("Discovery")} width="18"
</div> height="18"
{this.renderDiscoverySection()} // override icon default values
</> aria-hidden={false}
aria-label={_t("Warning")}
/>
) : null;
const heading = (
<Heading size="h2">
{discoWarning}
{_t("Discovery")}
</Heading>
); );
discoverySection = <SettingsSection heading={heading}>{this.renderDiscoverySection()}</SettingsSection>;
} }
return ( return (

View File

@ -13,9 +13,13 @@ exports[`<SettingsFieldset /> renders fieldset with plain text description 1`] =
</legend> </legend>
<div <div
class="mx_SettingsFieldset_description" class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Changes to who can read history. Changes to who can read history.
</div> </div>
</div>
<div> <div>
test test
</div> </div>
@ -36,6 +40,9 @@ exports[`<SettingsFieldset /> renders fieldset with react description 1`] = `
</legend> </legend>
<div <div
class="mx_SettingsFieldset_description" class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
> >
<p> <p>
Test Test
@ -46,6 +53,7 @@ exports[`<SettingsFieldset /> renders fieldset with react description 1`] = `
a link a link
</a> </a>
</div> </div>
</div>
<div> <div>
test test
</div> </div>

View File

@ -21,7 +21,7 @@ import { IRequestTokenResponse } from "matrix-js-sdk/src/client";
import { MatrixError } from "matrix-js-sdk/src/http-api"; import { MatrixError } from "matrix-js-sdk/src/http-api";
import { UserFriendlyError } from "../../../../../src/languageHandler"; import { UserFriendlyError } from "../../../../../src/languageHandler";
import { EmailAddress } from "../../../../../src/components/views/settings/discovery/EmailAddresses"; import EmailAddresses, { EmailAddress } from "../../../../../src/components/views/settings/discovery/EmailAddresses";
import { clearAllModals, getMockClientWithEventEmitter } from "../../../../test-utils"; import { clearAllModals, getMockClientWithEventEmitter } from "../../../../test-utils";
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken"); const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
@ -147,3 +147,23 @@ describe("<EmailAddress/>", () => {
}); });
}); });
}); });
describe("<EmailAddresses />", () => {
it("should render a loader while loading", async () => {
const { container } = render(<EmailAddresses emails={[emailThreepidFixture]} isLoading={true} />);
expect(container).toMatchSnapshot();
});
it("should render email addresses", async () => {
const { container } = render(<EmailAddresses emails={[emailThreepidFixture]} isLoading={false} />);
expect(container).toMatchSnapshot();
});
it("should handle no email addresses", async () => {
const { container } = render(<EmailAddresses emails={[]} isLoading={false} />);
expect(container).toMatchSnapshot();
});
});

View File

@ -18,10 +18,8 @@ import React from "react";
import { render, screen } from "@testing-library/react"; import { render, screen } from "@testing-library/react";
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { PhoneNumber } from "../../../../../src/components/views/settings/discovery/PhoneNumbers"; import PhoneNumbers, { PhoneNumber } from "../../../../../src/components/views/settings/discovery/PhoneNumbers";
describe("<PhoneNumber/>", () => {
it("should track props.msisdn.bound changes", async () => {
const msisdn: IThreepid = { const msisdn: IThreepid = {
medium: ThreepidMedium.Phone, medium: ThreepidMedium.Phone,
address: "+441111111111", address: "+441111111111",
@ -29,7 +27,8 @@ describe("<PhoneNumber/>", () => {
added_at: 12342, added_at: 12342,
bound: false, bound: false,
}; };
describe("<PhoneNumber/>", () => {
it("should track props.msisdn.bound changes", async () => {
const { rerender } = render(<PhoneNumber msisdn={msisdn} />); const { rerender } = render(<PhoneNumber msisdn={msisdn} />);
await screen.findByText("Share"); await screen.findByText("Share");
@ -38,3 +37,23 @@ describe("<PhoneNumber/>", () => {
await screen.findByText("Revoke"); await screen.findByText("Revoke");
}); });
}); });
describe("<PhoneNumbers />", () => {
it("should render a loader while loading", async () => {
const { container } = render(<PhoneNumbers msisdns={[msisdn]} isLoading={true} />);
expect(container).toMatchSnapshot();
});
it("should render phone numbers", async () => {
const { container } = render(<PhoneNumbers msisdns={[msisdn]} isLoading={false} />);
expect(container).toMatchSnapshot();
});
it("should handle no numbers", async () => {
const { container } = render(<PhoneNumbers msisdns={[]} isLoading={false} />);
expect(container).toMatchSnapshot();
});
});

View File

@ -0,0 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<EmailAddresses /> should handle no email addresses 1`] = `
<div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Email addresses
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Discovery options will appear once you have added an email above.
</div>
</div>
</div>
</div>
`;
exports[`<EmailAddresses /> should render a loader while loading 1`] = `
<div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Email addresses
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_InlineSpinner"
>
<div
aria-label="Loading…"
class="mx_InlineSpinner_icon mx_Spinner_icon"
style="width: 16px; height: 16px;"
/>
</div>
</div>
</div>
</div>
`;
exports[`<EmailAddresses /> should render email addresses 1`] = `
<div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Email addresses
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_GeneralUserSettingsTab_section--discovery_existing"
>
<span
class="mx_GeneralUserSettingsTab_section--discovery_existing_address"
>
foo@bar.com
</span>
<div
class="mx_AccessibleButton mx_GeneralUserSettingsTab_section--discovery_existing_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_sm"
role="button"
tabindex="0"
>
Share
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,101 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PhoneNumbers /> should handle no numbers 1`] = `
<div>
<div
class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Phone numbers
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Discovery options will appear once you have added a phone number above.
</div>
</div>
</div>
</div>
`;
exports[`<PhoneNumbers /> should render a loader while loading 1`] = `
<div>
<div
class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Phone numbers
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_InlineSpinner"
>
<div
aria-label="Loading…"
class="mx_InlineSpinner_icon mx_Spinner_icon"
style="width: 16px; height: 16px;"
/>
</div>
</div>
</div>
</div>
`;
exports[`<PhoneNumbers /> should render phone numbers 1`] = `
<div>
<div
class="mx_SettingsSubsection"
data-testid="mx_PhoneNumbers"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Phone numbers
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_GeneralUserSettingsTab_section--discovery_existing"
>
<span
class="mx_GeneralUserSettingsTab_section--discovery_existing_address"
>
+
+441111111111
</span>
<div
class="mx_AccessibleButton mx_GeneralUserSettingsTab_section--discovery_existing_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_sm"
role="button"
tabindex="0"
>
Revoke
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -11,9 +11,13 @@ exports[`<SecurityRoomSettingsTab /> history visibility uses shared as default h
</legend> </legend>
<div <div
class="mx_SettingsFieldset_description" class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged. Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.
</div> </div>
</div>
<label <label
class="mx_StyledRadioButton mx_StyledRadioButton_enabled" class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
> >

View File

@ -45,9 +45,13 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
</legend> </legend>
<div <div
class="mx_SettingsFieldset_description" class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
> >
Decide who can view and join mock-space. Decide who can view and join mock-space.
</div> </div>
</div>
<label <label
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked" class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked"
> >