Move integrations switch (#12733)

* Move integrations switch

This is the last thing to move out of 'general' now.

* unused import

* Move tests out to the SetIntegrationManager component

* Only a decade out

* Move playwright test to the new tab

* Update snapshot

* Update other snapshot
This commit is contained in:
David Baker 2024-07-10 20:10:19 +01:00 committed by GitHub
parent 19f9f98564
commit 44b98896a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 243 additions and 141 deletions

View file

@ -18,7 +18,6 @@ import { test, expect } from "../../element-web-test";
const USER_NAME = "Bob"; const USER_NAME = "Bob";
const USER_NAME_NEW = "Alice"; const USER_NAME_NEW = "Alice";
const IntegrationManager = "scalar.vector.im";
test.describe("General user settings tab", () => { test.describe("General user settings tab", () => {
test.use({ test.use({
@ -73,17 +72,6 @@ test.describe("General user settings tab", () => {
// Assert that the add button is rendered // Assert that the add button is rendered
await expect(phoneNumbers.getByRole("button", { name: "Add" })).toBeVisible(); await expect(phoneNumbers.getByRole("button", { name: "Add" })).toBeVisible();
const setIntegrationManager = uut.locator(".mx_SetIntegrationManager");
await setIntegrationManager.scrollIntoViewIfNeeded();
await expect(
setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager", { hasText: IntegrationManager }),
).toBeVisible();
// Make sure integration manager's toggle switch is enabled
await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
"Manage integrations(scalar.vector.im)",
);
// Assert the account deactivation button is displayed // Assert the account deactivation button is displayed
const accountManagementSection = uut.getByTestId("account-management-section"); const accountManagementSection = uut.getByTestId("account-management-section");
await accountManagementSection.scrollIntoViewIfNeeded(); await accountManagementSection.scrollIntoViewIfNeeded();

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2023 Suguru Hirahara Copyright 2023 Suguru Hirahara
Copyright 2024 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.
@ -16,6 +17,8 @@ limitations under the License.
import { test, expect } from "../../element-web-test"; import { test, expect } from "../../element-web-test";
const IntegrationManager = "scalar.vector.im";
test.describe("Security user settings tab", () => { test.describe("Security user settings tab", () => {
test.describe("with posthog enabled", () => { test.describe("with posthog enabled", () => {
test.use({ test.use({
@ -56,5 +59,22 @@ test.describe("Security user settings tab", () => {
// Assert that an input area for identity server exists // Assert that an input area for identity server exists
await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible(); await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
}); });
test("should enable show integrations as enabled", async ({ app, page }) => {
const tab = await app.settings.openUserSettings("Security");
const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");
await setIntegrationManager.scrollIntoViewIfNeeded();
await expect(
setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager", {
hasText: IntegrationManager,
}),
).toBeVisible();
// Make sure integration manager's toggle switch is enabled
await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
"Manage integrations(scalar.vector.im)",
);
});
}); });
}); });

View file

@ -25,6 +25,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
import ToggleSwitch from "../elements/ToggleSwitch"; import ToggleSwitch from "../elements/ToggleSwitch";
import Heading from "../typography/Heading"; import Heading from "../typography/Heading";
import { SettingsSubsectionText } from "./shared/SettingsSubsection"; import { SettingsSubsectionText } from "./shared/SettingsSubsection";
import { UIFeature } from "../../../settings/UIFeature";
interface IProps {} interface IProps {}
@ -71,6 +72,8 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
bodyText = _t("integration_manager|use_im"); bodyText = _t("integration_manager|use_im");
} }
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
return ( return (
<label <label
className="mx_SetIntegrationManager" className="mx_SetIntegrationManager"
@ -79,8 +82,8 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
> >
<div className="mx_SettingsFlag"> <div className="mx_SettingsFlag">
<div className="mx_SetIntegrationManager_heading_manager"> <div className="mx_SetIntegrationManager_heading_manager">
<Heading size="2">{_t("integration_manager|manage_title")}</Heading> <Heading size="3">{_t("integration_manager|manage_title")}</Heading>
<Heading size="3">{managerName}</Heading> <Heading size="4">{managerName}</Heading>
</div> </div>
<ToggleSwitch <ToggleSwitch
id="toggle_integration" id="toggle_integration"

View file

@ -29,7 +29,6 @@ import Modal from "../../../../../Modal";
import { UIFeature } from "../../../../../settings/UIFeature"; import { UIFeature } from "../../../../../settings/UIFeature";
import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog"; import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog";
import ChangePassword from "../../ChangePassword"; import ChangePassword from "../../ChangePassword";
import SetIntegrationManager from "../../SetIntegrationManager";
import SettingsTab from "../SettingsTab"; 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";
@ -194,12 +193,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
); );
} }
private renderIntegrationManagerSection(): ReactNode {
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
return <SetIntegrationManager />;
}
public render(): React.ReactNode { public render(): React.ReactNode {
let accountManagementSection: JSX.Element | undefined; let accountManagementSection: JSX.Element | undefined;
const isAccountManagedExternally = !!this.state.externalAccountManagementUrl; const isAccountManagedExternally = !!this.state.externalAccountManagementUrl;
@ -218,7 +211,6 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
<UserPersonalInfoSettings canMake3pidChanges={this.state.canMake3pidChanges} /> <UserPersonalInfoSettings canMake3pidChanges={this.state.canMake3pidChanges} />
{this.renderAccountSection()} {this.renderAccountSection()}
</SettingsSection> </SettingsSection>
{this.renderIntegrationManagerSection()}
{accountManagementSection} {accountManagementSection}
</SettingsTab> </SettingsTab>
); );

View file

@ -44,6 +44,7 @@ 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"; import DiscoverySettings from "../../discovery/DiscoverySettings";
import SetIntegrationManager from "../../SetIntegrationManager";
interface IIgnoredUserProps { interface IIgnoredUserProps {
userId: string; userId: string;
@ -376,6 +377,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
return ( return (
<SettingsTab> <SettingsTab>
{warning} {warning}
<SetIntegrationManager />
<SettingsSection heading={_t("settings|security|encryption_section")}> <SettingsSection heading={_t("settings|security|encryption_section")}>
{secureBackup} {secureBackup}
{eventIndex} {eventIndex}

View file

@ -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 { fireEvent, render, screen, within } from "@testing-library/react";
import { logger } from "matrix-js-sdk/src/logger";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { SDKContext, SdkContextClass } from "../../../../src/contexts/SDKContext";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { UIFeature } from "../../../../src/settings/UIFeature";
import {
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
flushPromises,
} from "../../../test-utils";
import SetIntegrationManager from "../../../../src/components/views/settings/SetIntegrationManager";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
describe("SetIntegrationManager", () => {
const userId = "@alice:server.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getCapabilities: jest.fn(),
getThreePids: jest.fn(),
getIdentityServerUrl: jest.fn(),
deleteThreePid: jest.fn(),
});
let stores: SdkContextClass;
const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>
<SDKContext.Provider value={stores}>
<SetIntegrationManager />
</SDKContext.Provider>
</MatrixClientContext.Provider>
);
it("should not render manage integrations section when widgets feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName !== UIFeature.Widgets);
render(getComponent());
expect(screen.queryByTestId("mx_SetIntegrationManager")).not.toBeInTheDocument();
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Widgets);
});
it("should render manage integrations sections", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
render(getComponent());
expect(screen.getByTestId("mx_SetIntegrationManager")).toMatchSnapshot();
});
it("should update integrations provisioning on toggle", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
render(getComponent());
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
fireEvent.click(within(integrationSection).getByRole("switch"));
expect(SettingsStore.setValue).toHaveBeenCalledWith(
"integrationProvisioning",
null,
SettingLevel.ACCOUNT,
true,
);
expect(within(integrationSection).getByRole("switch")).toBeChecked();
});
it("handles error when updating setting fails", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
jest.spyOn(logger, "error").mockImplementation(() => {});
jest.spyOn(SettingsStore, "setValue").mockRejectedValue("oups");
render(getComponent());
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
fireEvent.click(within(integrationSection).getByRole("switch"));
await flushPromises();
expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
expect(logger.error).toHaveBeenCalledWith("oups");
expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
});
});

View file

@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SetIntegrationManager should render manage integrations sections 1`] = `
<label
class="mx_SetIntegrationManager"
data-testid="mx_SetIntegrationManager"
for="toggle_integration"
>
<div
class="mx_SettingsFlag"
>
<div
class="mx_SetIntegrationManager_heading_manager"
>
<h3
class="mx_Heading_h3"
>
Manage integrations
</h3>
<h4
class="mx_Heading_h4"
>
(scalar.vector.im)
</h4>
</div>
<div
aria-checked="false"
aria-disabled="false"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
id="toggle_integration"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
>
<span>
Use an integration manager
<b>
(scalar.vector.im)
</b>
to manage bots, widgets, and sticker packs.
</span>
</div>
<div
class="mx_SettingsSubsection_text"
>
Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
</div>
</label>
`;

View file

@ -27,7 +27,6 @@ import {
flushPromises, flushPromises,
} from "../../../../../test-utils"; } from "../../../../../test-utils";
import { UIFeature } from "../../../../../../src/settings/UIFeature"; import { UIFeature } from "../../../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
import { OidcClientStore } from "../../../../../../src/stores/oidc/OidcClientStore"; import { OidcClientStore } from "../../../../../../src/stores/oidc/OidcClientStore";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
@ -98,65 +97,6 @@ describe("<GeneralUserSettingsTab />", () => {
expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink); expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink);
}); });
describe("Manage integrations", () => {
it("should not render manage integrations section when widgets feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName !== UIFeature.Widgets,
);
render(getComponent());
expect(screen.queryByTestId("mx_SetIntegrationManager")).not.toBeInTheDocument();
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Widgets);
});
it("should render manage integrations sections", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Widgets,
);
render(getComponent());
expect(screen.getByTestId("mx_SetIntegrationManager")).toMatchSnapshot();
});
it("should update integrations provisioning on toggle", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Widgets,
);
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
render(getComponent());
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
fireEvent.click(within(integrationSection).getByRole("switch"));
expect(SettingsStore.setValue).toHaveBeenCalledWith(
"integrationProvisioning",
null,
SettingLevel.ACCOUNT,
true,
);
expect(within(integrationSection).getByRole("switch")).toBeChecked();
});
it("handles error when updating setting fails", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Widgets,
);
jest.spyOn(logger, "error").mockImplementation(() => {});
jest.spyOn(SettingsStore, "setValue").mockRejectedValue("oups");
render(getComponent());
const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
fireEvent.click(within(integrationSection).getByRole("switch"));
await flushPromises();
expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
expect(logger.error).toHaveBeenCalledWith("oups");
expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
});
});
describe("deactive account", () => { describe("deactive account", () => {
it("should not render section when account deactivation feature is disabled", () => { it("should not render section when account deactivation feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation( jest.spyOn(SettingsStore, "getValue").mockImplementation(

View file

@ -42,14 +42,14 @@ exports[`<GeneralUserSettingsTab /> 3pids should display 3pid email addresses an
> >
<input <input
autocomplete="email" autocomplete="email"
id="mx_Field_41" id="mx_Field_27"
label="Email Address" label="Email Address"
placeholder="Email Address" placeholder="Email Address"
type="text" type="text"
value="" value=""
/> />
<label <label
for="mx_Field_41" for="mx_Field_27"
> >
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_42" id="mx_Field_28"
label="Phone Number" label="Phone Number"
placeholder="Phone Number" placeholder="Phone Number"
type="text" type="text"
value="" value=""
/> />
<label <label
for="mx_Field_42" for="mx_Field_28"
> >
Phone Number Phone Number
</label> </label>
@ -175,61 +175,6 @@ exports[`<GeneralUserSettingsTab /> 3pids should display 3pid email addresses an
</div> </div>
`; `;
exports[`<GeneralUserSettingsTab /> Manage integrations should render manage integrations sections 1`] = `
<label
class="mx_SetIntegrationManager"
data-testid="mx_SetIntegrationManager"
for="toggle_integration"
>
<div
class="mx_SettingsFlag"
>
<div
class="mx_SetIntegrationManager_heading_manager"
>
<h2
class="mx_Heading_h2"
>
Manage integrations
</h2>
<h3
class="mx_Heading_h3"
>
(scalar.vector.im)
</h3>
</div>
<div
aria-checked="false"
aria-disabled="false"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
id="toggle_integration"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
>
<span>
Use an integration manager
<b>
(scalar.vector.im)
</b>
to manage bots, widgets, and sticker packs.
</span>
</div>
<div
class="mx_SettingsSubsection_text"
>
Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
</div>
</label>
`;
exports[`<GeneralUserSettingsTab /> deactive account should render section when account deactivation feature is enabled 1`] = ` exports[`<GeneralUserSettingsTab /> deactive account should render section when account deactivation feature is enabled 1`] = `
<div <div
class="mx_SettingsSection" class="mx_SettingsSection"

View file

@ -8,6 +8,58 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
<div <div
class="mx_SettingsTab_sections" class="mx_SettingsTab_sections"
> >
<label
class="mx_SetIntegrationManager"
data-testid="mx_SetIntegrationManager"
for="toggle_integration"
>
<div
class="mx_SettingsFlag"
>
<div
class="mx_SetIntegrationManager_heading_manager"
>
<h3
class="mx_Heading_h3"
>
Manage integrations
</h3>
<h4
class="mx_Heading_h4"
>
(scalar.vector.im)
</h4>
</div>
<div
aria-checked="true"
aria-disabled="false"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
id="toggle_integration"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
>
<span>
Use an integration manager
<b>
(scalar.vector.im)
</b>
to manage bots, widgets, and sticker packs.
</span>
</div>
<div
class="mx_SettingsSubsection_text"
>
Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
</div>
</label>
<div <div
class="mx_SettingsSection" class="mx_SettingsSection"
> >