mirror of
https://github.com/vector-im/element-web.git
synced 2025-11-30 15:01:43 +01:00
MSC4380: Invite blocking (#31268)
* Initial implementation of MSC4380 * fix lint * Update InviteRulesAccountSetting-test * add some docs * `block_all` -> `default_action` * Add a unit test for BlockInvitesConfigController
This commit is contained in:
parent
5869c519ed
commit
1c684489da
@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4155.invite_permission_config";
|
export const INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4155.invite_permission_config";
|
||||||
|
export const MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4380.invite_permission_config";
|
||||||
|
|
||||||
export interface InviteConfigAccountData {
|
export interface InviteConfigAccountData {
|
||||||
allowed_users?: string[];
|
allowed_users?: string[];
|
||||||
|
|||||||
9
src/@types/matrix-js-sdk.d.ts
vendored
9
src/@types/matrix-js-sdk.d.ts
vendored
@ -15,7 +15,11 @@ import type { EmptyObject } from "matrix-js-sdk/src/matrix";
|
|||||||
import type { DeviceClientInformation } from "../utils/device/types.ts";
|
import type { DeviceClientInformation } from "../utils/device/types.ts";
|
||||||
import type { UserWidget } from "../utils/WidgetUtils-types.ts";
|
import type { UserWidget } from "../utils/WidgetUtils-types.ts";
|
||||||
import { type MediaPreviewConfig } from "./media_preview.ts";
|
import { type MediaPreviewConfig } from "./media_preview.ts";
|
||||||
import { type INVITE_RULES_ACCOUNT_DATA_TYPE, type InviteConfigAccountData } from "./invite-rules.ts";
|
import {
|
||||||
|
type INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||||
|
type InviteConfigAccountData,
|
||||||
|
type MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||||
|
} from "./invite-rules.ts";
|
||||||
|
|
||||||
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
||||||
declare module "matrix-js-sdk/src/types" {
|
declare module "matrix-js-sdk/src/types" {
|
||||||
@ -91,6 +95,9 @@ declare module "matrix-js-sdk/src/types" {
|
|||||||
|
|
||||||
// MSC4155: Invite filtering
|
// MSC4155: Invite filtering
|
||||||
[INVITE_RULES_ACCOUNT_DATA_TYPE]: InviteConfigAccountData;
|
[INVITE_RULES_ACCOUNT_DATA_TYPE]: InviteConfigAccountData;
|
||||||
|
|
||||||
|
[MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE]: { default_action?: "allow" | "block" };
|
||||||
|
|
||||||
"io.element.msc4278.media_preview_config": MediaPreviewConfig;
|
"io.element.msc4278.media_preview_config": MediaPreviewConfig;
|
||||||
|
|
||||||
// Indicate whether recovery is enabled or disabled
|
// Indicate whether recovery is enabled or disabled
|
||||||
|
|||||||
@ -15,32 +15,57 @@ import SettingsStore from "../../../../../settings/SettingsStore";
|
|||||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A settings component which allows the user to enable/disable invite blocking.
|
||||||
|
*
|
||||||
|
* Uses whichever of MSC4155 and MSC4380 is available on the server; if neither is available, the toggle is disabled. If
|
||||||
|
* both are available, the toggle will use MSC4380 to block invites.
|
||||||
|
*/
|
||||||
export const InviteRulesAccountSetting: FC = () => {
|
export const InviteRulesAccountSetting: FC = () => {
|
||||||
const rules = useSettingValue("inviteRules");
|
const msc4155Rules = useSettingValue("inviteRules");
|
||||||
const settingsDisabled = SettingsStore.disabledMessage("inviteRules");
|
const msc4380BlockInvites = useSettingValue("blockInvites");
|
||||||
|
|
||||||
|
const msc4155Disabled = SettingsStore.disabledMessage("inviteRules");
|
||||||
|
const msc4380Disabled = SettingsStore.disabledMessage("blockInvites");
|
||||||
|
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
const onChange = useCallback(async (checked: boolean) => {
|
const onChange = useCallback(
|
||||||
|
async (allowInvites: boolean) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, {
|
if (allowInvites) {
|
||||||
allBlocked: !checked,
|
// When allowing invites, clear the block setting on both bits of account data.
|
||||||
});
|
await SettingsStore.setValue("blockInvites", null, SettingLevel.ACCOUNT, false);
|
||||||
|
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { allBlocked: false });
|
||||||
|
} else {
|
||||||
|
// When blocking invites, prefer MSC4380 over MSC4155.
|
||||||
|
if (!msc4380Disabled) {
|
||||||
|
await SettingsStore.setValue("blockInvites", null, SettingLevel.ACCOUNT, true);
|
||||||
|
} else if (!msc4155Disabled) {
|
||||||
|
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { allBlocked: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
logger.error(`Unable to set invite rules`, ex);
|
logger.error(`Unable to set invite rules`, ex);
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[msc4155Disabled, msc4380Disabled, setBusy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabledMessage = msc4155Disabled && msc4380Disabled;
|
||||||
|
const invitesBlocked = (!msc4155Disabled && msc4155Rules.allBlocked) || (!msc4380Disabled && msc4380BlockInvites);
|
||||||
return (
|
return (
|
||||||
<Root className="mx_MediaPreviewAccountSetting_Form">
|
<Root className="mx_MediaPreviewAccountSetting_Form">
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
className="mx_MediaPreviewAccountSetting_ToggleSwitch"
|
className="mx_MediaPreviewAccountSetting_ToggleSwitch"
|
||||||
label={_t("settings|invite_controls|default_label")}
|
label={_t("settings|invite_controls|default_label")}
|
||||||
value={!rules.allBlocked}
|
value={!invitesBlocked}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
tooltip={settingsDisabled}
|
tooltip={disabledMessage}
|
||||||
disabled={!!settingsDisabled || busy}
|
disabled={!!disabledMessage || busy}
|
||||||
/>
|
/>
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import { SortingAlgorithm } from "../stores/room-list-v3/skip-list/sorters/index
|
|||||||
import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts";
|
import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts";
|
||||||
import InviteRulesConfigController from "./controllers/InviteRulesConfigController.ts";
|
import InviteRulesConfigController from "./controllers/InviteRulesConfigController.ts";
|
||||||
import { type ComputedInviteConfig } from "../@types/invite-rules.ts";
|
import { type ComputedInviteConfig } from "../@types/invite-rules.ts";
|
||||||
|
import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts";
|
||||||
|
|
||||||
export const defaultWatchManager = new WatchManager();
|
export const defaultWatchManager = new WatchManager();
|
||||||
|
|
||||||
@ -368,6 +369,7 @@ export interface Settings {
|
|||||||
"Electron.enableContentProtection": IBaseSetting<boolean>;
|
"Electron.enableContentProtection": IBaseSetting<boolean>;
|
||||||
"mediaPreviewConfig": IBaseSetting<MediaPreviewConfig>;
|
"mediaPreviewConfig": IBaseSetting<MediaPreviewConfig>;
|
||||||
"inviteRules": IBaseSetting<ComputedInviteConfig>;
|
"inviteRules": IBaseSetting<ComputedInviteConfig>;
|
||||||
|
"blockInvites": IBaseSetting<boolean>;
|
||||||
"Developer.elementCallUrl": IBaseSetting<string>;
|
"Developer.elementCallUrl": IBaseSetting<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,6 +460,11 @@ export const SETTINGS: Settings = {
|
|||||||
// Contains server names
|
// Contains server names
|
||||||
shouldExportToRageshake: false,
|
shouldExportToRageshake: false,
|
||||||
},
|
},
|
||||||
|
"blockInvites": {
|
||||||
|
controller: new BlockInvitesConfigController("blockInvites"),
|
||||||
|
supportedLevels: [SettingLevel.ACCOUNT],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
"feature_report_to_moderators": {
|
"feature_report_to_moderators": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
labsGroup: LabGroup.Moderation,
|
labsGroup: LabGroup.Moderation,
|
||||||
|
|||||||
36
src/settings/controllers/BlockInvitesConfigController.ts
Normal file
36
src/settings/controllers/BlockInvitesConfigController.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type SettingLevel } from "../SettingLevel.ts";
|
||||||
|
import { MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE } from "../../@types/invite-rules.ts";
|
||||||
|
import { _td } from "../../languageHandler.tsx";
|
||||||
|
import ServerSupportUnstableFeatureController from "./ServerSupportUnstableFeatureController.ts";
|
||||||
|
import { defaultWatchManager, type SettingKey } from "../Settings.tsx";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles invite filtering rules provided by MSC4380.
|
||||||
|
* This handler does not make use of the roomId parameter.
|
||||||
|
*/
|
||||||
|
export default class BlockInvitesConfigController extends ServerSupportUnstableFeatureController {
|
||||||
|
public constructor(settingName: SettingKey) {
|
||||||
|
super(settingName, defaultWatchManager, [["org.matrix.msc4380"]], undefined, _td("settings|not_supported"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValueOverride(_level: SettingLevel): boolean {
|
||||||
|
const accountData = this.client?.getAccountData(MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE)?.getContent();
|
||||||
|
return accountData?.default_action == "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async beforeChange(_level: SettingLevel, _roomId: string | null, newValue: boolean): Promise<boolean> {
|
||||||
|
if (!this.client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const newDefault = newValue ? "block" : "allow";
|
||||||
|
await this.client.setAccountData(MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE, { default_action: newDefault });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,16 +13,25 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
|||||||
import { type ComputedInviteConfig } from "../../../../../../../src/@types/invite-rules";
|
import { type ComputedInviteConfig } from "../../../../../../../src/@types/invite-rules";
|
||||||
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
|
||||||
|
|
||||||
function mockSetting(mediaPreviews: ComputedInviteConfig, supported = true) {
|
function mockSetting(inviteRules: ComputedInviteConfig, blockInvites: boolean) {
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
|
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
|
||||||
if (settingName === "inviteRules") {
|
if (settingName === "inviteRules") {
|
||||||
return mediaPreviews;
|
return inviteRules;
|
||||||
|
}
|
||||||
|
if (settingName === "blockInvites") {
|
||||||
|
return blockInvites;
|
||||||
}
|
}
|
||||||
throw Error(`Unexpected setting ${settingName}`);
|
throw Error(`Unexpected setting ${settingName}`);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockDisabledMessage(msc4155supported: boolean, msc4380Supported: boolean) {
|
||||||
jest.spyOn(SettingsStore, "disabledMessage").mockImplementation((settingName) => {
|
jest.spyOn(SettingsStore, "disabledMessage").mockImplementation((settingName) => {
|
||||||
if (settingName === "inviteRules") {
|
if (settingName === "inviteRules") {
|
||||||
return supported ? undefined : "test-not-supported";
|
return msc4155supported ? undefined : "test-not-supported";
|
||||||
|
}
|
||||||
|
if (settingName === "blockInvites") {
|
||||||
|
return msc4380Supported ? undefined : "test-not-supported";
|
||||||
}
|
}
|
||||||
throw Error(`Unexpected setting ${settingName}`);
|
throw Error(`Unexpected setting ${settingName}`);
|
||||||
});
|
});
|
||||||
@ -33,44 +42,103 @@ describe("InviteRulesAccountSetting", () => {
|
|||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render if not supported", async () => {
|
it("does not render if neither MSC4155 nor MSC4380 are supported", async () => {
|
||||||
mockSetting({ allBlocked: false }, false);
|
mockSetting({ allBlocked: false }, false);
|
||||||
|
mockDisabledMessage(false, false);
|
||||||
const { findByText, findByLabelText } = render(<InviteRulesAccountSetting />);
|
const { findByText, findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
const input = await findByLabelText("Allow users to invite you to rooms");
|
const input = await findByLabelText("Allow users to invite you to rooms");
|
||||||
await userEvent.hover(input);
|
await userEvent.hover(input);
|
||||||
const result = await findByText("test-not-supported");
|
const result = await findByText("test-not-supported");
|
||||||
expect(result).toBeInTheDocument();
|
expect(result).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when MSC4155 is supported", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDisabledMessage(true, false);
|
||||||
|
});
|
||||||
|
|
||||||
it("renders correct state when invites are not blocked", async () => {
|
it("renders correct state when invites are not blocked", async () => {
|
||||||
mockSetting({ allBlocked: false }, true);
|
mockSetting({ allBlocked: false }, false);
|
||||||
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
const result = await findByLabelText("Allow users to invite you to rooms");
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
expect(result).toBeChecked();
|
expect(result).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders correct state when invites are blocked", async () => {
|
it("renders correct state when invites are blocked", async () => {
|
||||||
mockSetting({ allBlocked: true }, true);
|
mockSetting({ allBlocked: true }, false);
|
||||||
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
const result = await findByLabelText("Allow users to invite you to rooms");
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
expect(result).not.toBeChecked();
|
expect(result).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles disabling all invites", async () => {
|
it("handles disabling all invites", async () => {
|
||||||
mockSetting({ allBlocked: false }, true);
|
mockSetting({ allBlocked: false }, false);
|
||||||
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
||||||
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
const result = await findByLabelText("Allow users to invite you to rooms");
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
await userEvent.click(result);
|
await userEvent.click(result);
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledTimes(1);
|
||||||
expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, {
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, {
|
||||||
allBlocked: true,
|
allBlocked: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles enabling all invites", async () => {
|
it("handles enabling all invites", async () => {
|
||||||
mockSetting({ allBlocked: true }, true);
|
mockSetting({ allBlocked: true }, true);
|
||||||
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
||||||
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
const result = await findByLabelText("Allow users to invite you to rooms");
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
await userEvent.click(result);
|
await userEvent.click(result);
|
||||||
|
// Should clear both MSC4155 and MSC4380 settings
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledTimes(2);
|
||||||
expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, {
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, {
|
||||||
allBlocked: false,
|
allBlocked: false,
|
||||||
});
|
});
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("blockInvites", null, SettingLevel.ACCOUNT, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when MSC4380 is supported", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDisabledMessage(false, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders correct state when invites are not blocked", async () => {
|
||||||
|
mockSetting({ allBlocked: false }, false);
|
||||||
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
|
expect(result).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders correct state when invites are blocked", async () => {
|
||||||
|
mockSetting({ allBlocked: false }, true);
|
||||||
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
|
expect(result).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles disabling all invites", async () => {
|
||||||
|
mockSetting({ allBlocked: false }, false);
|
||||||
|
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
||||||
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
|
await userEvent.click(result);
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledTimes(1);
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("blockInvites", null, SettingLevel.ACCOUNT, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles enabling all invites", async () => {
|
||||||
|
mockSetting({ allBlocked: true }, true);
|
||||||
|
jest.spyOn(SettingsStore, "setValue").mockImplementation();
|
||||||
|
const { findByLabelText } = render(<InviteRulesAccountSetting />);
|
||||||
|
const result = await findByLabelText("Allow users to invite you to rooms");
|
||||||
|
await userEvent.click(result);
|
||||||
|
// Should clear both MSC4155 and MSC4380 settings
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledTimes(2);
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, {
|
||||||
|
allBlocked: false,
|
||||||
|
});
|
||||||
|
expect(SettingsStore.setValue).toHaveBeenCalledWith("blockInvites", null, SettingLevel.ACCOUNT, false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
|
import { SETTINGS } from "../../../../src/settings/Settings";
|
||||||
|
import { stubClient } from "../../../test-utils";
|
||||||
|
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||||
|
import { SettingLevel } from "../../../../src/settings/SettingLevel.ts";
|
||||||
|
|
||||||
|
describe("BlockInvitesConfigController", () => {
|
||||||
|
describe("When server does not support MSC4380", () => {
|
||||||
|
let cli: MatrixClient;
|
||||||
|
beforeEach(() => {
|
||||||
|
cli = stubClient();
|
||||||
|
cli.doesServerSupportUnstableFeature = jest.fn(async () => false);
|
||||||
|
MatrixClientBackedController.matrixClient = cli;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("settingDisabled() should give a message", () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
expect(controller.settingDisabled).toEqual("Your server does not implement this feature.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("When server supports MSC4380", () => {
|
||||||
|
let cli: MatrixClient;
|
||||||
|
beforeEach(async () => {
|
||||||
|
cli = stubClient();
|
||||||
|
cli.doesServerSupportUnstableFeature = jest.fn(async (feature) => {
|
||||||
|
return feature == "org.matrix.msc4380";
|
||||||
|
});
|
||||||
|
MatrixClientBackedController.matrixClient = cli;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("settingDisabled() should be false", () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
expect(controller.settingDisabled).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getValueOverride()", () => {
|
||||||
|
it("should return true when invites are blocked", async () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
|
||||||
|
mockAccountData(cli, { default_action: "block" });
|
||||||
|
expect(controller.getValueOverride(SettingLevel.DEVICE, null, null, null)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when invites are not blocked", async () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
|
||||||
|
mockAccountData(cli, { default_action: {} });
|
||||||
|
expect(controller.getValueOverride(SettingLevel.DEVICE, null, null, null)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("beforeChange()", () => {
|
||||||
|
it("should set the account data when the value is enabled", async () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
await controller.beforeChange(SettingLevel.DEVICE, null, true);
|
||||||
|
expect(cli.setAccountData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cli.setAccountData).toHaveBeenCalledWith("org.matrix.msc4380.invite_permission_config", {
|
||||||
|
default_action: "block",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the account data when the value is disabled", async () => {
|
||||||
|
const controller = SETTINGS.blockInvites.controller!;
|
||||||
|
await controller.beforeChange(SettingLevel.DEVICE, null, false);
|
||||||
|
expect(cli.setAccountData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cli.setAccountData).toHaveBeenCalledWith("org.matrix.msc4380.invite_permission_config", {
|
||||||
|
default_action: "allow",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a mock implementation for {@link MatrixClient.getAccountData} which will return the given data
|
||||||
|
* in respomsnse to any request for `org.matrix.msc4380.invite_permission_config`.
|
||||||
|
*/
|
||||||
|
function mockAccountData(cli: MatrixClient, mockAccountData: object) {
|
||||||
|
mocked(cli.getAccountData).mockImplementation((eventType) => {
|
||||||
|
if (eventType == "org.matrix.msc4380.invite_permission_config") {
|
||||||
|
return new MatrixEvent({
|
||||||
|
type: "org.matrix.msc4380.invite_permission_config",
|
||||||
|
content: mockAccountData,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user