mirror of https://github.com/vector-im/riot-web
Split out email & phone number settings to separate components & move discovery to privacy tab (#12670)
* WIP update of threepid settings section * Remove email / phone number section from original place and don't show the new one if 3pids are disabled * Update snapshots * Pull identity server / 3pid binding settings out to separate component and put it in the security & privacy section which is its new home * Update snapshot * Move relevant part of test & update screenshots / snapshots * Remove unnecessary dependency * Add test for discovery settings * Add spacing in terms agreementpull/28217/head
parent
72475240ec
commit
ea0baee101
|
@ -91,11 +91,6 @@ test.describe("General user settings tab", () => {
|
||||||
// Assert that the default value is rendered again
|
// Assert that the default value is rendered again
|
||||||
await expect(languageInput.getByText("English")).toBeVisible();
|
await expect(languageInput.getByText("English")).toBeVisible();
|
||||||
|
|
||||||
const setIdServer = uut.locator(".mx_SetIdServer");
|
|
||||||
await setIdServer.scrollIntoViewIfNeeded();
|
|
||||||
// Assert that an input area for identity server exists
|
|
||||||
await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
|
|
||||||
|
|
||||||
const setIntegrationManager = uut.locator(".mx_SetIntegrationManager");
|
const setIntegrationManager = uut.locator(".mx_SetIntegrationManager");
|
||||||
await setIntegrationManager.scrollIntoViewIfNeeded();
|
await setIntegrationManager.scrollIntoViewIfNeeded();
|
||||||
await expect(
|
await expect(
|
||||||
|
|
|
@ -47,5 +47,14 @@ test.describe("Security user settings tab", () => {
|
||||||
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot();
|
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should contain section to set ID server", async ({ app }) => {
|
||||||
|
const tab = await app.settings.openUserSettings("Security");
|
||||||
|
|
||||||
|
const setIdServer = tab.locator(".mx_SetIdServer");
|
||||||
|
await setIdServer.scrollIntoViewIfNeeded();
|
||||||
|
// Assert that an input area for identity server exists
|
||||||
|
await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_InlineTermsAgreement_cbContainer {
|
.mx_InlineTermsAgreement_cbContainer {
|
||||||
|
margin-top: var(--cpd-space-4x);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font: var(--cpd-font-body-md-regular);
|
font: var(--cpd-font-body-md-regular);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 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, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { ThreepidMedium } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { Alert } from "@vector-im/compound-web";
|
||||||
|
|
||||||
|
import AccountEmailAddresses from "./account/EmailAddresses";
|
||||||
|
import AccountPhoneNumbers from "./account/PhoneNumbers";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import InlineSpinner from "../elements/InlineSpinner";
|
||||||
|
import SettingsSubsection from "./shared/SettingsSubsection";
|
||||||
|
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||||
|
import { ThirdPartyIdentifier } from "../../../AddThreepid";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
|
type LoadingState = "loading" | "loaded" | "error";
|
||||||
|
|
||||||
|
interface ThreepidSectionWrapperProps {
|
||||||
|
error: string;
|
||||||
|
loadingState: LoadingState;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThreepidSectionWrapper: React.FC<ThreepidSectionWrapperProps> = ({ error, loadingState, children }) => {
|
||||||
|
if (loadingState === "loading") {
|
||||||
|
return <InlineSpinner />;
|
||||||
|
} else if (loadingState === "error") {
|
||||||
|
return (
|
||||||
|
<Alert type="critical" title={_t("common|error")}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UserPersonalInfoSettingsProps {
|
||||||
|
canMake3pidChanges: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings controls allowing the user to set personal information like email addresses.
|
||||||
|
*/
|
||||||
|
export const UserPersonalInfoSettings: React.FC<UserPersonalInfoSettingsProps> = ({ canMake3pidChanges }) => {
|
||||||
|
const [emails, setEmails] = useState<ThirdPartyIdentifier[] | undefined>();
|
||||||
|
const [phoneNumbers, setPhoneNumbers] = useState<ThirdPartyIdentifier[] | undefined>();
|
||||||
|
const [loadingState, setLoadingState] = useState<"loading" | "loaded" | "error">("loading");
|
||||||
|
|
||||||
|
const client = useMatrixClientContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const threepids = await client.getThreePids();
|
||||||
|
setEmails(threepids.threepids.filter((a) => a.medium === ThreepidMedium.Email));
|
||||||
|
setPhoneNumbers(threepids.threepids.filter((a) => a.medium === ThreepidMedium.Phone));
|
||||||
|
setLoadingState("loaded");
|
||||||
|
} catch (e) {
|
||||||
|
setLoadingState("error");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
const onEmailsChange = useCallback((emails: ThirdPartyIdentifier[]) => {
|
||||||
|
setEmails(emails);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMsisdnsChange = useCallback((msisdns: ThirdPartyIdentifier[]) => {
|
||||||
|
setPhoneNumbers(msisdns);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>{_t("settings|general|personal_info")}</h2>
|
||||||
|
<SettingsSubsection
|
||||||
|
heading={_t("settings|general|emails_heading")}
|
||||||
|
stretchContent
|
||||||
|
data-testid="mx_AccountEmailAddresses"
|
||||||
|
>
|
||||||
|
<ThreepidSectionWrapper
|
||||||
|
error={_t("settings|general|unable_to_load_emails")}
|
||||||
|
loadingState={loadingState}
|
||||||
|
>
|
||||||
|
<AccountEmailAddresses
|
||||||
|
emails={emails!}
|
||||||
|
onEmailsChange={onEmailsChange}
|
||||||
|
disabled={!canMake3pidChanges}
|
||||||
|
/>
|
||||||
|
</ThreepidSectionWrapper>
|
||||||
|
</SettingsSubsection>
|
||||||
|
|
||||||
|
<SettingsSubsection
|
||||||
|
heading={_t("settings|general|msisdns_heading")}
|
||||||
|
stretchContent
|
||||||
|
data-testid="mx_AccountPhoneNumbers"
|
||||||
|
>
|
||||||
|
<ThreepidSectionWrapper
|
||||||
|
error={_t("settings|general|unable_to_load_msisdns")}
|
||||||
|
loadingState={loadingState}
|
||||||
|
>
|
||||||
|
<AccountPhoneNumbers
|
||||||
|
msisdns={phoneNumbers!}
|
||||||
|
onMsisdnsChange={onMsisdnsChange}
|
||||||
|
disabled={!canMake3pidChanges}
|
||||||
|
/>
|
||||||
|
</ThreepidSectionWrapper>
|
||||||
|
</SettingsSubsection>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserPersonalInfoSettings;
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 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, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { SERVICE_TYPES, ThreepidMedium } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { Alert } from "@vector-im/compound-web";
|
||||||
|
|
||||||
|
import DiscoveryEmailAddresses from "../discovery/EmailAddresses";
|
||||||
|
import DiscoveryPhoneNumbers from "../discovery/PhoneNumbers";
|
||||||
|
import { getThreepidsWithBindStatus } from "../../../../boundThreepids";
|
||||||
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||||
|
import { ThirdPartyIdentifier } from "../../../../AddThreepid";
|
||||||
|
import SettingsStore from "../../../../settings/SettingsStore";
|
||||||
|
import { UIFeature } from "../../../../settings/UIFeature";
|
||||||
|
import { _t } from "../../../../languageHandler";
|
||||||
|
import SetIdServer from "../SetIdServer";
|
||||||
|
import SettingsSubsection from "../shared/SettingsSubsection";
|
||||||
|
import InlineTermsAgreement from "../../terms/InlineTermsAgreement";
|
||||||
|
import { Service, ServicePolicyPair, startTermsFlow } from "../../../../Terms";
|
||||||
|
import IdentityAuthClient from "../../../../IdentityAuthClient";
|
||||||
|
import { abbreviateUrl } from "../../../../utils/UrlUtils";
|
||||||
|
import { useDispatcher } from "../../../../hooks/useDispatcher";
|
||||||
|
import defaultDispatcher from "../../../../dispatcher/dispatcher";
|
||||||
|
import { ActionPayload } from "../../../../dispatcher/payloads";
|
||||||
|
|
||||||
|
type RequiredPolicyInfo =
|
||||||
|
| {
|
||||||
|
// This object is passed along to a component for handling
|
||||||
|
policiesAndServices: null; // From the startTermsFlow callback
|
||||||
|
agreedUrls: null; // From the startTermsFlow callback
|
||||||
|
resolve: null; // Promise resolve function for startTermsFlow callback
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
policiesAndServices: ServicePolicyPair[];
|
||||||
|
agreedUrls: string[];
|
||||||
|
resolve: (values: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings controlling how a user's email addreses and phone numbers can be used to discover them
|
||||||
|
*/
|
||||||
|
export const DiscoverySettings: React.FC = () => {
|
||||||
|
const client = useMatrixClientContext();
|
||||||
|
|
||||||
|
const [emails, setEmails] = useState<ThirdPartyIdentifier[]>([]);
|
||||||
|
const [phoneNumbers, setPhoneNumbers] = useState<ThirdPartyIdentifier[]>([]);
|
||||||
|
const [loadingState, setLoadingState] = useState<"loading" | "loaded" | "error">("loading");
|
||||||
|
const [idServerName, setIdServerName] = useState<string | undefined>(abbreviateUrl(client.getIdentityServerUrl()));
|
||||||
|
const [canMake3pidChanges, setCanMake3pidChanges] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [requiredPolicyInfo, setRequiredPolicyInfo] = useState<RequiredPolicyInfo>({
|
||||||
|
// This object is passed along to a component for handling
|
||||||
|
policiesAndServices: null, // From the startTermsFlow callback
|
||||||
|
agreedUrls: null, // From the startTermsFlow callback
|
||||||
|
resolve: null, // Promise resolve function for startTermsFlow callback
|
||||||
|
});
|
||||||
|
const [hasTerms, setHasTerms] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const getThreepidState = useCallback(async () => {
|
||||||
|
const threepids = await getThreepidsWithBindStatus(client);
|
||||||
|
setEmails(threepids.filter((a) => a.medium === ThreepidMedium.Email));
|
||||||
|
setPhoneNumbers(threepids.filter((a) => a.medium === ThreepidMedium.Phone));
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
useDispatcher(
|
||||||
|
defaultDispatcher,
|
||||||
|
useCallback(
|
||||||
|
(payload: ActionPayload) => {
|
||||||
|
if (payload.action === "id_server_changed") {
|
||||||
|
setIdServerName(abbreviateUrl(client.getIdentityServerUrl()));
|
||||||
|
|
||||||
|
getThreepidState().then();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[client, getThreepidState],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await getThreepidState();
|
||||||
|
|
||||||
|
const capabilities = await client.getCapabilities();
|
||||||
|
setCanMake3pidChanges(
|
||||||
|
!capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// By starting the terms flow we get the logic for checking which terms the user has signed
|
||||||
|
// for free. So we might as well use that for our own purposes.
|
||||||
|
const idServerUrl = client.getIdentityServerUrl();
|
||||||
|
if (!idServerUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authClient = new IdentityAuthClient();
|
||||||
|
try {
|
||||||
|
const idAccessToken = await authClient.getAccessToken({ check: false });
|
||||||
|
await startTermsFlow(
|
||||||
|
client,
|
||||||
|
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
|
||||||
|
(policiesAndServices, agreedUrls, extraClassNames) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setIdServerName(abbreviateUrl(idServerUrl));
|
||||||
|
setHasTerms(true);
|
||||||
|
setRequiredPolicyInfo({
|
||||||
|
policiesAndServices,
|
||||||
|
agreedUrls,
|
||||||
|
resolve,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// User accepted all terms
|
||||||
|
setHasTerms(false);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`,
|
||||||
|
);
|
||||||
|
logger.warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingState("loaded");
|
||||||
|
} catch (e) {
|
||||||
|
setLoadingState("error");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [client, getThreepidState]);
|
||||||
|
|
||||||
|
if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
|
||||||
|
|
||||||
|
if (hasTerms && requiredPolicyInfo.policiesAndServices) {
|
||||||
|
const intro = (
|
||||||
|
<Alert type="info" title={_t("settings|general|discovery_needs_terms_title")}>
|
||||||
|
{_t("settings|general|discovery_needs_terms", { serverName: idServerName })}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InlineTermsAgreement
|
||||||
|
policiesAndServicePairs={requiredPolicyInfo.policiesAndServices}
|
||||||
|
agreedUrls={requiredPolicyInfo.agreedUrls}
|
||||||
|
onFinished={requiredPolicyInfo.resolve}
|
||||||
|
introElement={intro}
|
||||||
|
/>
|
||||||
|
{/* has its own heading as it includes the current identity server */}
|
||||||
|
<SetIdServer missingTerms={true} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const threepidSection = idServerName ? (
|
||||||
|
<>
|
||||||
|
<DiscoveryEmailAddresses
|
||||||
|
emails={emails}
|
||||||
|
isLoading={loadingState === "loading"}
|
||||||
|
disabled={!canMake3pidChanges}
|
||||||
|
/>
|
||||||
|
<DiscoveryPhoneNumbers
|
||||||
|
msisdns={phoneNumbers}
|
||||||
|
isLoading={loadingState === "loading"}
|
||||||
|
disabled={!canMake3pidChanges}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsSubsection heading={_t("settings|discovery|title")} data-testid="discoverySection">
|
||||||
|
{threepidSection}
|
||||||
|
{/* has its own heading as it includes the current identity server */}
|
||||||
|
<SetIdServer missingTerms={false} />
|
||||||
|
</SettingsSubsection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscoverySettings;
|
|
@ -17,10 +17,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { SERVICE_TYPES, HTTPError, IThreepid, ThreepidMedium } from "matrix-js-sdk/src/matrix";
|
import { HTTPError } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { Icon as WarningIcon } from "../../../../../../res/img/feather-customised/warning-triangle.svg";
|
|
||||||
import { UserFriendlyError, _t } from "../../../../../languageHandler";
|
import { UserFriendlyError, _t } from "../../../../../languageHandler";
|
||||||
import UserProfileSettings from "../../UserProfileSettings";
|
import UserProfileSettings from "../../UserProfileSettings";
|
||||||
import * as languageHandler from "../../../../../languageHandler";
|
import * as languageHandler from "../../../../../languageHandler";
|
||||||
|
@ -31,22 +30,10 @@ import AccessibleButton from "../../../elements/AccessibleButton";
|
||||||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
||||||
import PlatformPeg from "../../../../../PlatformPeg";
|
import PlatformPeg from "../../../../../PlatformPeg";
|
||||||
import Modal from "../../../../../Modal";
|
import Modal from "../../../../../Modal";
|
||||||
import dis from "../../../../../dispatcher/dispatcher";
|
|
||||||
import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms";
|
|
||||||
import IdentityAuthClient from "../../../../../IdentityAuthClient";
|
|
||||||
import { abbreviateUrl } from "../../../../../utils/UrlUtils";
|
|
||||||
import { getThreepidsWithBindStatus } from "../../../../../boundThreepids";
|
|
||||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||||
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
|
||||||
import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog";
|
import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog";
|
||||||
import AccountPhoneNumbers from "../../account/PhoneNumbers";
|
|
||||||
import AccountEmailAddresses from "../../account/EmailAddresses";
|
|
||||||
import DiscoveryEmailAddresses from "../../discovery/EmailAddresses";
|
|
||||||
import DiscoveryPhoneNumbers from "../../discovery/PhoneNumbers";
|
|
||||||
import ChangePassword from "../../ChangePassword";
|
import ChangePassword from "../../ChangePassword";
|
||||||
import InlineTermsAgreement from "../../../terms/InlineTermsAgreement";
|
|
||||||
import SetIdServer from "../../SetIdServer";
|
|
||||||
import SetIntegrationManager from "../../SetIntegrationManager";
|
import SetIntegrationManager from "../../SetIntegrationManager";
|
||||||
import ToggleSwitch from "../../../elements/ToggleSwitch";
|
import ToggleSwitch from "../../../elements/ToggleSwitch";
|
||||||
import { IS_MAC } from "../../../../../Keyboard";
|
import { IS_MAC } from "../../../../../Keyboard";
|
||||||
|
@ -54,10 +41,8 @@ import SettingsTab from "../SettingsTab";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
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 InlineSpinner from "../../../elements/InlineSpinner";
|
|
||||||
import { ThirdPartyIdentifier } from "../../../../../AddThreepid";
|
|
||||||
import { SDKContext } from "../../../../../contexts/SDKContext";
|
import { SDKContext } from "../../../../../contexts/SDKContext";
|
||||||
|
import UserPersonalInfoSettings from "../../UserPersonalInfoSettings";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeSettingsFn: () => void;
|
closeSettingsFn: () => void;
|
||||||
|
@ -67,25 +52,6 @@ interface IState {
|
||||||
language: string;
|
language: string;
|
||||||
spellCheckEnabled?: boolean;
|
spellCheckEnabled?: boolean;
|
||||||
spellCheckLanguages: string[];
|
spellCheckLanguages: string[];
|
||||||
haveIdServer: boolean;
|
|
||||||
idServerHasUnsignedTerms: boolean;
|
|
||||||
requiredPolicyInfo:
|
|
||||||
| {
|
|
||||||
// This object is passed along to a component for handling
|
|
||||||
hasTerms: false;
|
|
||||||
policiesAndServices: null; // From the startTermsFlow callback
|
|
||||||
agreedUrls: null; // From the startTermsFlow callback
|
|
||||||
resolve: null; // Promise resolve function for startTermsFlow callback
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
hasTerms: boolean;
|
|
||||||
policiesAndServices: ServicePolicyPair[];
|
|
||||||
agreedUrls: string[];
|
|
||||||
resolve: (values: string[]) => void;
|
|
||||||
};
|
|
||||||
emails: ThirdPartyIdentifier[];
|
|
||||||
msisdns: ThirdPartyIdentifier[];
|
|
||||||
loading3pids: boolean; // whether or not the emails and msisdns have been loaded
|
|
||||||
canChangePassword: boolean;
|
canChangePassword: boolean;
|
||||||
idServerName?: string;
|
idServerName?: string;
|
||||||
externalAccountManagementUrl?: string;
|
externalAccountManagementUrl?: string;
|
||||||
|
@ -96,38 +62,19 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
public static contextType = SDKContext;
|
public static contextType = SDKContext;
|
||||||
public context!: React.ContextType<typeof SDKContext>;
|
public context!: React.ContextType<typeof SDKContext>;
|
||||||
|
|
||||||
private readonly dispatcherRef: string;
|
|
||||||
|
|
||||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||||
super(props);
|
super(props);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
const cli = this.context.client!;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
language: languageHandler.getCurrentLanguage(),
|
language: languageHandler.getCurrentLanguage(),
|
||||||
spellCheckEnabled: false,
|
spellCheckEnabled: false,
|
||||||
spellCheckLanguages: [],
|
spellCheckLanguages: [],
|
||||||
haveIdServer: Boolean(cli.getIdentityServerUrl()),
|
|
||||||
idServerHasUnsignedTerms: false,
|
|
||||||
requiredPolicyInfo: {
|
|
||||||
// This object is passed along to a component for handling
|
|
||||||
hasTerms: false,
|
|
||||||
policiesAndServices: null, // From the startTermsFlow callback
|
|
||||||
agreedUrls: null, // From the startTermsFlow callback
|
|
||||||
resolve: null, // Promise resolve function for startTermsFlow callback
|
|
||||||
},
|
|
||||||
emails: [],
|
|
||||||
msisdns: [],
|
|
||||||
loading3pids: true, // whether or not the emails and msisdns have been loaded
|
|
||||||
canChangePassword: false,
|
canChangePassword: false,
|
||||||
canMake3pidChanges: false,
|
canMake3pidChanges: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
|
|
||||||
this.getCapabilities();
|
this.getCapabilities();
|
||||||
this.getThreepidState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async componentDidMount(): Promise<void> {
|
public async componentDidMount(): Promise<void> {
|
||||||
|
@ -145,25 +92,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload): void => {
|
|
||||||
if (payload.action === "id_server_changed") {
|
|
||||||
this.setState({ haveIdServer: Boolean(this.context.client!.getIdentityServerUrl()) });
|
|
||||||
this.getThreepidState();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onEmailsChange = (emails: ThirdPartyIdentifier[]): void => {
|
|
||||||
this.setState({ emails });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onMsisdnsChange = (msisdns: ThirdPartyIdentifier[]): void => {
|
|
||||||
this.setState({ msisdns });
|
|
||||||
};
|
|
||||||
|
|
||||||
private async getCapabilities(): Promise<void> {
|
private async getCapabilities(): Promise<void> {
|
||||||
const cli = this.context.client!;
|
const cli = this.context.client!;
|
||||||
|
|
||||||
|
@ -185,73 +113,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
this.setState({ canChangePassword, externalAccountManagementUrl, canMake3pidChanges });
|
this.setState({ canChangePassword, externalAccountManagementUrl, canMake3pidChanges });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getThreepidState(): Promise<void> {
|
|
||||||
const cli = this.context.client!;
|
|
||||||
|
|
||||||
// Check to see if terms need accepting
|
|
||||||
this.checkTerms();
|
|
||||||
|
|
||||||
// Need to get 3PIDs generally for Account section and possibly also for
|
|
||||||
// Discovery (assuming we have an IS and terms are agreed).
|
|
||||||
let threepids: IThreepid[] = [];
|
|
||||||
try {
|
|
||||||
threepids = await getThreepidsWithBindStatus(cli);
|
|
||||||
} catch (e) {
|
|
||||||
const idServerUrl = cli.getIdentityServerUrl();
|
|
||||||
logger.warn(
|
|
||||||
`Unable to reach identity server at ${idServerUrl} to check ` + `for 3PIDs bindings in Settings`,
|
|
||||||
);
|
|
||||||
logger.warn(e);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
emails: threepids.filter((a) => a.medium === ThreepidMedium.Email),
|
|
||||||
msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone),
|
|
||||||
loading3pids: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async checkTerms(): Promise<void> {
|
|
||||||
// By starting the terms flow we get the logic for checking which terms the user has signed
|
|
||||||
// for free. So we might as well use that for our own purposes.
|
|
||||||
const idServerUrl = this.context.client!.getIdentityServerUrl();
|
|
||||||
if (!this.state.haveIdServer || !idServerUrl) {
|
|
||||||
this.setState({ idServerHasUnsignedTerms: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const authClient = new IdentityAuthClient();
|
|
||||||
try {
|
|
||||||
const idAccessToken = await authClient.getAccessToken({ check: false });
|
|
||||||
await startTermsFlow(
|
|
||||||
this.context.client!,
|
|
||||||
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
|
|
||||||
(policiesAndServices, agreedUrls, extraClassNames) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.setState({
|
|
||||||
idServerName: abbreviateUrl(idServerUrl),
|
|
||||||
requiredPolicyInfo: {
|
|
||||||
hasTerms: true,
|
|
||||||
policiesAndServices,
|
|
||||||
agreedUrls,
|
|
||||||
resolve,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// User accepted all terms
|
|
||||||
this.setState({
|
|
||||||
requiredPolicyInfo: {
|
|
||||||
...this.state.requiredPolicyInfo, // set first so we can override
|
|
||||||
hasTerms: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`);
|
|
||||||
logger.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onLanguageChange = (newLanguage: string): void => {
|
private onLanguageChange = (newLanguage: string): void => {
|
||||||
if (this.state.language === newLanguage) return;
|
if (this.state.language === newLanguage) return;
|
||||||
|
|
||||||
|
@ -324,48 +185,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderAccountSection(): JSX.Element {
|
private renderAccountSection(): JSX.Element {
|
||||||
let threepidSection: ReactNode = null;
|
|
||||||
|
|
||||||
if (SettingsStore.getValue(UIFeature.ThirdPartyID)) {
|
|
||||||
const emails = this.state.loading3pids ? (
|
|
||||||
<InlineSpinner />
|
|
||||||
) : (
|
|
||||||
<AccountEmailAddresses
|
|
||||||
emails={this.state.emails}
|
|
||||||
onEmailsChange={this.onEmailsChange}
|
|
||||||
disabled={!this.state.canMake3pidChanges}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const msisdns = this.state.loading3pids ? (
|
|
||||||
<InlineSpinner />
|
|
||||||
) : (
|
|
||||||
<AccountPhoneNumbers
|
|
||||||
msisdns={this.state.msisdns}
|
|
||||||
onMsisdnsChange={this.onMsisdnsChange}
|
|
||||||
disabled={!this.state.canMake3pidChanges}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
threepidSection = (
|
|
||||||
<>
|
|
||||||
<SettingsSubsection
|
|
||||||
heading={_t("settings|general|emails_heading")}
|
|
||||||
stretchContent
|
|
||||||
data-testid="mx_AccountEmailAddresses"
|
|
||||||
>
|
|
||||||
{emails}
|
|
||||||
</SettingsSubsection>
|
|
||||||
|
|
||||||
<SettingsSubsection
|
|
||||||
heading={_t("settings|general|msisdns_heading")}
|
|
||||||
stretchContent
|
|
||||||
data-testid="mx_AccountPhoneNumbers"
|
|
||||||
>
|
|
||||||
{msisdns}
|
|
||||||
</SettingsSubsection>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let passwordChangeSection: ReactNode = null;
|
let passwordChangeSection: ReactNode = null;
|
||||||
if (this.state.canChangePassword) {
|
if (this.state.canChangePassword) {
|
||||||
passwordChangeSection = (
|
passwordChangeSection = (
|
||||||
|
@ -419,7 +238,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
{externalAccountManagement}
|
{externalAccountManagement}
|
||||||
{passwordChangeSection}
|
{passwordChangeSection}
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
{threepidSection}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -455,51 +273,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderDiscoverySection(): JSX.Element {
|
|
||||||
if (this.state.requiredPolicyInfo.hasTerms) {
|
|
||||||
const intro = (
|
|
||||||
<SettingsSubsectionText>
|
|
||||||
{_t("settings|general|discovery_needs_terms", { serverName: this.state.idServerName })}
|
|
||||||
</SettingsSubsectionText>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<InlineTermsAgreement
|
|
||||||
policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices}
|
|
||||||
agreedUrls={this.state.requiredPolicyInfo.agreedUrls}
|
|
||||||
onFinished={this.state.requiredPolicyInfo.resolve}
|
|
||||||
introElement={intro}
|
|
||||||
/>
|
|
||||||
{/* has its own heading as it includes the current identity server */}
|
|
||||||
<SetIdServer missingTerms={true} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const threepidSection = this.state.haveIdServer ? (
|
|
||||||
<>
|
|
||||||
<DiscoveryEmailAddresses
|
|
||||||
emails={this.state.emails}
|
|
||||||
isLoading={this.state.loading3pids}
|
|
||||||
disabled={!this.state.canMake3pidChanges}
|
|
||||||
/>
|
|
||||||
<DiscoveryPhoneNumbers
|
|
||||||
msisdns={this.state.msisdns}
|
|
||||||
isLoading={this.state.loading3pids}
|
|
||||||
disabled={!this.state.canMake3pidChanges}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{threepidSection}
|
|
||||||
{/* has its own heading as it includes the current identity server */}
|
|
||||||
<SetIdServer missingTerms={false} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderManagementSection(): JSX.Element {
|
private renderManagementSection(): JSX.Element {
|
||||||
// TODO: Improve warning text for account deactivation
|
// TODO: Improve warning text for account deactivation
|
||||||
return (
|
return (
|
||||||
|
@ -533,40 +306,15 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
accountManagementSection = this.renderManagementSection();
|
accountManagementSection = this.renderManagementSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
let discoverySection;
|
|
||||||
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
|
||||||
const discoWarning = this.state.requiredPolicyInfo.hasTerms ? (
|
|
||||||
<WarningIcon
|
|
||||||
className="mx_GeneralUserSettingsTab_warningIcon"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
// override icon default values
|
|
||||||
aria-hidden={false}
|
|
||||||
aria-label={_t("common|warning")}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
const heading = (
|
|
||||||
<Heading size="2">
|
|
||||||
{discoWarning}
|
|
||||||
{_t("settings|general|discovery_section")}
|
|
||||||
</Heading>
|
|
||||||
);
|
|
||||||
discoverySection = (
|
|
||||||
<SettingsSection heading={heading} data-testid="discoverySection">
|
|
||||||
{this.renderDiscoverySection()}
|
|
||||||
</SettingsSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
|
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<UserProfileSettings />
|
<UserProfileSettings />
|
||||||
|
<UserPersonalInfoSettings canMake3pidChanges={this.state.canMake3pidChanges} />
|
||||||
{this.renderAccountSection()}
|
{this.renderAccountSection()}
|
||||||
{this.renderLanguageSection()}
|
{this.renderLanguageSection()}
|
||||||
{supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null}
|
{supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
{discoverySection}
|
|
||||||
{this.renderIntegrationManagerSection()}
|
{this.renderIntegrationManagerSection()}
|
||||||
{accountManagementSection}
|
{accountManagementSection}
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
|
|
|
@ -43,6 +43,7 @@ import SettingsTab from "../SettingsTab";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
import { SettingsSection } from "../../shared/SettingsSection";
|
||||||
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
|
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
|
||||||
import { useOwnDevices } from "../../devices/useOwnDevices";
|
import { useOwnDevices } from "../../devices/useOwnDevices";
|
||||||
|
import DiscoverySettings from "../../discovery/DiscoverySettings";
|
||||||
|
|
||||||
interface IIgnoredUserProps {
|
interface IIgnoredUserProps {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -336,6 +337,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
};
|
};
|
||||||
privacySection = (
|
privacySection = (
|
||||||
<SettingsSection heading={_t("common|privacy")}>
|
<SettingsSection heading={_t("common|privacy")}>
|
||||||
|
<DiscoverySettings />
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("common|analytics")}
|
heading={_t("common|analytics")}
|
||||||
description={_t("settings|security|analytics_description")}
|
description={_t("settings|security|analytics_description")}
|
||||||
|
|
|
@ -2443,6 +2443,9 @@
|
||||||
"code_block_expand_default": "Expand code blocks by default",
|
"code_block_expand_default": "Expand code blocks by default",
|
||||||
"code_block_line_numbers": "Show line numbers in code blocks",
|
"code_block_line_numbers": "Show line numbers in code blocks",
|
||||||
"disable_historical_profile": "Show current profile picture and name for users in message history",
|
"disable_historical_profile": "Show current profile picture and name for users in message history",
|
||||||
|
"discovery": {
|
||||||
|
"title": "How to find you"
|
||||||
|
},
|
||||||
"emoji_autocomplete": "Enable Emoji suggestions while typing",
|
"emoji_autocomplete": "Enable Emoji suggestions while typing",
|
||||||
"enable_markdown": "Enable Markdown",
|
"enable_markdown": "Enable Markdown",
|
||||||
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
|
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
|
||||||
|
@ -2479,11 +2482,11 @@
|
||||||
"deactivate_section": "Deactivate Account",
|
"deactivate_section": "Deactivate Account",
|
||||||
"deactivate_warning": "Deactivating your account is a permanent action — be careful!",
|
"deactivate_warning": "Deactivating your account is a permanent action — be careful!",
|
||||||
"dialog_title": "<strong>Settings:</strong> General",
|
"dialog_title": "<strong>Settings:</strong> General",
|
||||||
"discovery_email_empty": "Discovery options will appear once you have added an email above.",
|
"discovery_email_empty": "Discovery options will appear once you have added an email.",
|
||||||
"discovery_email_verification_instructions": "Verify the link in your inbox",
|
"discovery_email_verification_instructions": "Verify the link in your inbox",
|
||||||
"discovery_msisdn_empty": "Discovery options will appear once you have added a phone number above.",
|
"discovery_msisdn_empty": "Discovery options will appear once you have added a phone number.",
|
||||||
"discovery_needs_terms": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.",
|
"discovery_needs_terms": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.",
|
||||||
"discovery_section": "Discovery",
|
"discovery_needs_terms_title": "Let people find you",
|
||||||
"display_name": "Display Name",
|
"display_name": "Display Name",
|
||||||
"display_name_error": "Unable to set display name",
|
"display_name_error": "Unable to set display name",
|
||||||
"email_address_in_use": "This email address is already in use",
|
"email_address_in_use": "This email address is already in use",
|
||||||
|
@ -2522,11 +2525,14 @@
|
||||||
"oidc_manage_button": "Manage account",
|
"oidc_manage_button": "Manage account",
|
||||||
"password_change_section": "Set a new account password…",
|
"password_change_section": "Set a new account password…",
|
||||||
"password_change_success": "Your password was successfully changed.",
|
"password_change_success": "Your password was successfully changed.",
|
||||||
|
"personal_info": "Personal info",
|
||||||
"profile_subtitle": "This is how you appear to others on the app.",
|
"profile_subtitle": "This is how you appear to others on the app.",
|
||||||
"remove_email_prompt": "Remove %(email)s?",
|
"remove_email_prompt": "Remove %(email)s?",
|
||||||
"remove_msisdn_prompt": "Remove %(phone)s?",
|
"remove_msisdn_prompt": "Remove %(phone)s?",
|
||||||
"spell_check_locale_placeholder": "Choose a locale",
|
"spell_check_locale_placeholder": "Choose a locale",
|
||||||
"spell_check_section": "Spell check",
|
"spell_check_section": "Spell check",
|
||||||
|
"unable_to_load_emails": "Unable to load email addresses",
|
||||||
|
"unable_to_load_msisdns": "Unable to load phone numbers",
|
||||||
"username": "Username"
|
"username": "Username"
|
||||||
},
|
},
|
||||||
"image_thumbnails": "Show previews/thumbnails for images",
|
"image_thumbnails": "Show previews/thumbnails for images",
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 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 { act, render, screen } from "@testing-library/react";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
import DiscoverySettings from "../../../../../src/components/views/settings/discovery/DiscoverySettings";
|
||||||
|
import { stubClient } from "../../../../test-utils";
|
||||||
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
|
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||||
|
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||||
|
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||||
|
|
||||||
|
const mockGetAccessToken = jest.fn().mockResolvedValue("$$getAccessToken");
|
||||||
|
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
||||||
|
jest.fn().mockImplementation(() => ({
|
||||||
|
getAccessToken: mockGetAccessToken,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("DiscoverySettings", () => {
|
||||||
|
let client: MatrixClient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = stubClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const DiscoveryWrapper = (props = {}) => <MatrixClientContext.Provider value={client} {...props} />;
|
||||||
|
|
||||||
|
it("is empty if 3pid features are disabled", async () => {
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockImplementation((key) => {
|
||||||
|
if (key === UIFeature.ThirdPartyID) return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container } = render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
|
||||||
|
|
||||||
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays alert if an identity server needs terms accepting", async () => {
|
||||||
|
mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
|
||||||
|
mocked(client).getTerms.mockResolvedValue({
|
||||||
|
["policies"]: { en: "No ball games" },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
|
||||||
|
|
||||||
|
await expect(await screen.findByText("Let people find you")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("button to accept terms is disabled if checkbox not checked", async () => {
|
||||||
|
mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
|
||||||
|
mocked(client).getTerms.mockResolvedValue({
|
||||||
|
["policies"]: { en: "No ball games" },
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
|
||||||
|
|
||||||
|
const acceptCheckbox = await screen.findByRole("checkbox", { name: "Accept" });
|
||||||
|
const continueButton = screen.getByRole("button", { name: "Continue" });
|
||||||
|
expect(acceptCheckbox).toBeInTheDocument();
|
||||||
|
expect(continueButton).toHaveAttribute("aria-disabled", "true");
|
||||||
|
|
||||||
|
await userEvent.click(acceptCheckbox);
|
||||||
|
expect(continueButton).not.toHaveAttribute("aria-disabled", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates if ID server is changed", async () => {
|
||||||
|
render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
|
||||||
|
|
||||||
|
mocked(client).getThreePids.mockClear();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
defaultDispatcher.dispatch(
|
||||||
|
{
|
||||||
|
action: "id_server_changed",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.getThreePids).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -20,7 +20,7 @@ exports[`<EmailAddresses /> should handle no email addresses 1`] = `
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSubsection_text"
|
class="mx_SettingsSubsection_text"
|
||||||
>
|
>
|
||||||
Discovery options will appear once you have added an email above.
|
Discovery options will appear once you have added an email.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -83,7 +83,7 @@ exports[`<PhoneNumbers /> should handle no numbers 1`] = `
|
||||||
<div
|
<div
|
||||||
class="mx_SettingsSubsection_text"
|
class="mx_SettingsSubsection_text"
|
||||||
>
|
>
|
||||||
Discovery options will appear once you have added a phone number above.
|
Discovery options will appear once you have added a phone number.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,14 +42,14 @@ exports[`<GeneralUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
id="mx_Field_50"
|
id="mx_Field_41"
|
||||||
label="Email Address"
|
label="Email Address"
|
||||||
placeholder="Email Address"
|
placeholder="Email Address"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
for="mx_Field_50"
|
for="mx_Field_41"
|
||||||
>
|
>
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
|
@ -150,14 +150,14 @@ exports[`<GeneralUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
autocomplete="tel-national"
|
autocomplete="tel-national"
|
||||||
id="mx_Field_51"
|
id="mx_Field_42"
|
||||||
label="Phone Number"
|
label="Phone Number"
|
||||||
placeholder="Phone Number"
|
placeholder="Phone Number"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
for="mx_Field_51"
|
for="mx_Field_42"
|
||||||
>
|
>
|
||||||
Phone Number
|
Phone Number
|
||||||
</label>
|
</label>
|
||||||
|
|
Loading…
Reference in New Issue