Move more tests over to vitest (#34249)
* Move more tests over to vitest * Add exception for test_setting
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024 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 PosthogTrackers from "../../../../src/PosthogTrackers";
|
||||
import AnalyticsController from "../../../../src/settings/controllers/AnalyticsController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
||||
describe("AnalyticsController", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("Tracks a Posthog interaction on change", () => {
|
||||
const trackInteractionSpy = jest.spyOn(PosthogTrackers, "trackInteraction");
|
||||
|
||||
const controller = new AnalyticsController("WebSettingsNotificationsTACOnlyNotificationsToggle");
|
||||
|
||||
controller.onChange(SettingLevel.DEVICE, null, false);
|
||||
|
||||
expect(trackInteractionSpy).toHaveBeenCalledWith("WebSettingsNotificationsTACOnlyNotificationsToggle");
|
||||
});
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
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.stable";
|
||||
});
|
||||
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("m.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("m.invite_permission_config", {});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a mock implementation for {@link MatrixClient.getAccountData} which will return the given data
|
||||
* in response to any request for `m.invite_permission_config`.
|
||||
*/
|
||||
function mockAccountData(cli: MatrixClient, mockAccountData: object) {
|
||||
mocked(cli.getAccountData).mockImplementation((eventType) => {
|
||||
if (eventType == "m.invite_permission_config") {
|
||||
return new MatrixEvent({
|
||||
type: "m.invite_permission_config",
|
||||
content: mockAccountData,
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 { AllDevicesIsolationMode, OnlySignedDevicesIsolationMode } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import DeviceIsolationModeController from "../../../../src/settings/controllers/DeviceIsolationModeController.ts";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
||||
describe("DeviceIsolationModeController", () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("tracks enabling and disabling", () => {
|
||||
it("on sets signed device isolation mode", () => {
|
||||
const cli = stubClient();
|
||||
const controller = new DeviceIsolationModeController();
|
||||
controller.onChange(SettingLevel.DEVICE, "", true);
|
||||
expect(cli.getCrypto()?.setDeviceIsolationMode).toHaveBeenCalledWith(new OnlySignedDevicesIsolationMode());
|
||||
});
|
||||
|
||||
it("off sets all device isolation mode", () => {
|
||||
const cli = stubClient();
|
||||
const controller = new DeviceIsolationModeController();
|
||||
controller.onChange(SettingLevel.DEVICE, "", false);
|
||||
expect(cli.getCrypto()?.setDeviceIsolationMode).toHaveBeenCalledWith(new AllDevicesIsolationMode(false));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 fetchMock from "@fetch-mock/jest";
|
||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import FallbackIceServerController from "../../../../src/settings/controllers/FallbackIceServerController.ts";
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController.ts";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore.ts";
|
||||
|
||||
describe("FallbackIceServerController", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.get("https://matrix.org/_matrix/client/versions", { versions: ["v1.4"] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should update MatrixClient's state when the setting is updated", async () => {
|
||||
const client = new MatrixClient({
|
||||
baseUrl: "https://matrix.org",
|
||||
userId: "@alice:matrix.org",
|
||||
accessToken: "token",
|
||||
});
|
||||
MatrixClientBackedController.matrixClient = client;
|
||||
|
||||
expect(client.isFallbackICEServerAllowed()).toBeFalsy();
|
||||
await SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, true);
|
||||
expect(client.isFallbackICEServerAllowed()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should force the setting to be disabled if disable_fallback_ice=true", async () => {
|
||||
const controller = new FallbackIceServerController();
|
||||
const client = new MatrixClient({
|
||||
baseUrl: "https://matrix.org",
|
||||
userId: "@alice:matrix.org",
|
||||
accessToken: "token",
|
||||
});
|
||||
MatrixClientBackedController.matrixClient = client;
|
||||
expect(controller.settingDisabled).toBeFalsy();
|
||||
|
||||
client["clientWellKnown"] = {
|
||||
"io.element.voip": {
|
||||
disable_fallback_ice: true,
|
||||
},
|
||||
};
|
||||
client.emit(ClientEvent.ClientWellKnown, client["clientWellKnown"]);
|
||||
|
||||
expect(controller.settingDisabled).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 { Action } from "../../../../src/dispatcher/actions";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import FontSizeController from "../../../../src/settings/controllers/FontSizeController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
||||
const dispatchSpy = jest.spyOn(dis, "fire");
|
||||
|
||||
describe("FontSizeController", () => {
|
||||
it("dispatches a font size action on change", () => {
|
||||
const controller = new FontSizeController();
|
||||
|
||||
controller.onChange(SettingLevel.ACCOUNT, "$room:server", 12);
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(Action.MigrateBaseFontSize);
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 IncompatibleController from "../../../../src/settings/controllers/IncompatibleController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
declare module "../../../../src/settings/Settings.tsx" {
|
||||
interface Settings {
|
||||
test_setting: IBaseSetting<string>;
|
||||
}
|
||||
}
|
||||
|
||||
describe("IncompatibleController", () => {
|
||||
const settingsGetValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||
beforeEach(() => {
|
||||
settingsGetValueSpy.mockClear();
|
||||
});
|
||||
|
||||
describe("incompatibleSetting", () => {
|
||||
describe("when incompatibleValue is not set", () => {
|
||||
it("returns true when setting value is true", () => {
|
||||
// no incompatible value set, defaulted to true
|
||||
const controller = new IncompatibleController("test_setting", { key: null });
|
||||
settingsGetValueSpy.mockReturnValue(true);
|
||||
// true === true
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(settingsGetValueSpy).toHaveBeenCalledWith("test_setting");
|
||||
});
|
||||
|
||||
it("returns false when setting value is not true", () => {
|
||||
// no incompatible value set, defaulted to true
|
||||
const controller = new IncompatibleController("test_setting", { key: null });
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when incompatibleValue is set to a value", () => {
|
||||
it("returns true when setting value matches incompatible value", () => {
|
||||
const controller = new IncompatibleController("test_setting", { key: null }, "test");
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when setting value is not true", () => {
|
||||
const controller = new IncompatibleController("test_setting", { key: null }, "test");
|
||||
settingsGetValueSpy.mockReturnValue("not test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when incompatibleValue is set to a function", () => {
|
||||
it("returns result from incompatibleValue function", () => {
|
||||
const incompatibleValueFn = jest.fn().mockReturnValue(false);
|
||||
const controller = new IncompatibleController("test_setting", { key: null }, incompatibleValueFn);
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
expect(incompatibleValueFn).toHaveBeenCalledWith("test");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getValueOverride()", () => {
|
||||
it("returns forced value when setting is incompatible", () => {
|
||||
settingsGetValueSpy.mockReturnValue(true);
|
||||
const controller = new IncompatibleController("test_setting", { key: null });
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", true, SettingLevel.ACCOUNT),
|
||||
).toEqual({ key: null });
|
||||
});
|
||||
|
||||
it("returns null when setting is not incompatible", () => {
|
||||
settingsGetValueSpy.mockReturnValue(false);
|
||||
const controller = new IncompatibleController("test_setting", { key: null });
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", true, SettingLevel.ACCOUNT),
|
||||
).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
import InviteRulesConfigController from "../../../../src/settings/controllers/InviteRulesConfigController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils";
|
||||
import { INVITE_RULES_ACCOUNT_DATA_TYPE, type InviteConfigAccountData } from "../../../../src/@types/invite-rules";
|
||||
|
||||
describe("InviteRulesConfigController", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("gets the default settings when none are specified.", () => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(null),
|
||||
});
|
||||
|
||||
const value = controller.getValueOverride(SettingLevel.ACCOUNT);
|
||||
expect(value).toEqual(InviteRulesConfigController.default);
|
||||
});
|
||||
|
||||
it("gets the default settings when the setting is empty.", () => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest
|
||||
.fn()
|
||||
.mockReturnValue(new MatrixEvent({ type: INVITE_RULES_ACCOUNT_DATA_TYPE, content: {} })),
|
||||
});
|
||||
|
||||
const value = controller.getValueOverride(SettingLevel.ACCOUNT);
|
||||
expect(value).toEqual(InviteRulesConfigController.default);
|
||||
});
|
||||
|
||||
it.each<InviteConfigAccountData>([{ blocked_users: ["foo_bar"] }, { blocked_users: [] }, {}])(
|
||||
"calculates blockAll to be false",
|
||||
(content: InviteConfigAccountData) => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
content,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT);
|
||||
expect(globalValue.allBlocked).toEqual(false);
|
||||
},
|
||||
);
|
||||
|
||||
it.each<InviteConfigAccountData>([
|
||||
{ blocked_users: ["*"] },
|
||||
{ blocked_users: ["*", "bob"] },
|
||||
{ allowed_users: ["*"], blocked_users: ["*"] },
|
||||
])("calculates blockAll to be true", (content: InviteConfigAccountData) => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
content,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT);
|
||||
expect(globalValue.allBlocked).toEqual(true);
|
||||
});
|
||||
|
||||
it("sets the account data correctly for blockAll = true", async () => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
existing_content: {},
|
||||
allowed_servers: ["*"],
|
||||
},
|
||||
}),
|
||||
),
|
||||
setAccountData: jest.fn(),
|
||||
}));
|
||||
|
||||
expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked: true })).toBe(true);
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(INVITE_RULES_ACCOUNT_DATA_TYPE, {
|
||||
existing_content: {},
|
||||
allowed_servers: ["*"],
|
||||
blocked_users: ["*"],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the account data correctly for blockAll = false", async () => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
existing_content: {},
|
||||
allowed_servers: ["*"],
|
||||
blocked_users: ["extra_user", "*"],
|
||||
},
|
||||
}),
|
||||
),
|
||||
setAccountData: jest.fn(),
|
||||
}));
|
||||
|
||||
expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked: false })).toBe(true);
|
||||
expect(client.setAccountData).toHaveBeenCalledWith(INVITE_RULES_ACCOUNT_DATA_TYPE, {
|
||||
existing_content: {},
|
||||
allowed_servers: ["*"],
|
||||
blocked_users: ["extra_user"],
|
||||
});
|
||||
});
|
||||
it.each([true, false])("ignores a no-op when allBlocked = %s", async (allBlocked) => {
|
||||
const controller = new InviteRulesConfigController();
|
||||
const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
existing_content: {},
|
||||
allowed_servers: ["*"],
|
||||
blocked_users: allBlocked ? ["*"] : [],
|
||||
},
|
||||
}),
|
||||
),
|
||||
setAccountData: jest.fn(),
|
||||
}));
|
||||
|
||||
expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked })).toBe(false);
|
||||
expect(client.setAccountData).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
import MediaPreviewConfigController from "../../../../src/settings/controllers/MediaPreviewConfigController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils";
|
||||
import { MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, MediaPreviewValue } from "../../../../src/@types/media_preview";
|
||||
|
||||
describe("MediaPreviewConfigController", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const ROOM_ID = "!room:example.org";
|
||||
|
||||
it("gets the default settings when none are specified.", () => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(null),
|
||||
});
|
||||
|
||||
const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(value).toEqual(MediaPreviewConfigController.default);
|
||||
});
|
||||
|
||||
it("gets the default settings when the setting is empty.", () => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest
|
||||
.fn()
|
||||
.mockReturnValue(new MatrixEvent({ type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, content: {} })),
|
||||
});
|
||||
|
||||
const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(value).toEqual(MediaPreviewConfigController.default);
|
||||
});
|
||||
|
||||
it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the global level", (key) => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
[key]: MediaPreviewValue.Off,
|
||||
},
|
||||
}),
|
||||
),
|
||||
getRoom: jest.fn().mockReturnValue({
|
||||
getAccountData: jest.fn().mockReturnValue(null),
|
||||
}),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(globalValue[key]).toEqual(MediaPreviewValue.Off);
|
||||
|
||||
// Should follow the global value.
|
||||
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||
expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
|
||||
});
|
||||
|
||||
it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the room level", (key) => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(null),
|
||||
getRoom: jest.fn().mockReturnValue({
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
[key]: MediaPreviewValue.Off,
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(globalValue[key]).toEqual(MediaPreviewValue.On);
|
||||
|
||||
// Should follow the global value.
|
||||
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||
expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
|
||||
});
|
||||
|
||||
it.each([["media_previews"], ["invite_avatars"]])(
|
||||
"uses defaults when an invalid value is set on the global level",
|
||||
(key) => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
[key]: "bibble",
|
||||
},
|
||||
}),
|
||||
),
|
||||
getRoom: jest.fn().mockReturnValue({
|
||||
getAccountData: jest.fn().mockReturnValue(null),
|
||||
}),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(globalValue[key]).toEqual(MediaPreviewValue.On);
|
||||
|
||||
// Should follow the global value.
|
||||
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||
expect(roomValue[key]).toEqual(MediaPreviewValue.On);
|
||||
},
|
||||
);
|
||||
it.each([["media_previews"], ["invite_avatars"]])(
|
||||
"uses global value when an invalid value is set on the room level",
|
||||
(key) => {
|
||||
const controller = new MediaPreviewConfigController();
|
||||
|
||||
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
[key]: MediaPreviewValue.Off,
|
||||
},
|
||||
}),
|
||||
),
|
||||
getRoom: jest.fn().mockReturnValue({
|
||||
getAccountData: jest.fn().mockReturnValue(
|
||||
new MatrixEvent({
|
||||
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||
content: {
|
||||
[key]: "bibble",
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||
expect(globalValue[key]).toEqual(MediaPreviewValue.Off);
|
||||
|
||||
// Should follow the global value.
|
||||
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||
expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations 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 { Capabilities } from "matrix-js-sdk/src/matrix";
|
||||
import RequiresSettingsController from "../../../../src/settings/controllers/RequiresSettingsController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils";
|
||||
|
||||
describe("RequiresSettingsController", () => {
|
||||
afterEach(() => {
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
it("forces a value if a setting is false", async () => {
|
||||
const forcedValue = true;
|
||||
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true);
|
||||
await SettingsStore.setValue("useCustomFontSize", null, SettingLevel.DEVICE, false);
|
||||
const controller = new RequiresSettingsController(["useCompactLayout", "useCustomFontSize"], forcedValue);
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(controller.getValueOverride()).toEqual(forcedValue);
|
||||
});
|
||||
|
||||
it("does not force a value if all settings are true", async () => {
|
||||
const controller = new RequiresSettingsController(["useCompactLayout", "useCustomFontSize"]);
|
||||
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true);
|
||||
await SettingsStore.setValue("useCustomFontSize", null, SettingLevel.DEVICE, true);
|
||||
expect(controller.settingDisabled).toEqual(false);
|
||||
expect(controller.getValueOverride()).toEqual(null);
|
||||
});
|
||||
|
||||
describe("with capabilites", () => {
|
||||
let client: ReturnType<typeof getMockClientWithEventEmitter>;
|
||||
beforeEach(() => {
|
||||
client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
getCachedCapabilities: jest.fn().mockImplementation(() => {}),
|
||||
getCapabilities: jest.fn().mockRejectedValue({}),
|
||||
});
|
||||
MatrixClientBackedController["_matrixClient"] = client;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("will disable setting if capability check is true", async () => {
|
||||
const caps = {
|
||||
"m.change_password": {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
client.getCachedCapabilities.mockImplementation(() => caps);
|
||||
const controller = new RequiresSettingsController([], false, (c: Capabilities) => {
|
||||
expect(c).toEqual(caps);
|
||||
return !c["m.change_password"]?.enabled;
|
||||
});
|
||||
|
||||
// Test that we fetch caps
|
||||
controller["initMatrixClient"]();
|
||||
expect(client.getCapabilities).toHaveBeenCalled();
|
||||
|
||||
// Test that we check caps.
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(controller.getValueOverride()).toEqual(false);
|
||||
expect(client.getCachedCapabilities).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("will not disable setting if capability check is false", async () => {
|
||||
const caps = {
|
||||
"m.change_password": {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
client.getCachedCapabilities.mockImplementation(() => caps);
|
||||
const controller = new RequiresSettingsController([], false, (c: Capabilities) => {
|
||||
expect(c).toEqual(caps);
|
||||
return !c["m.change_password"]?.enabled;
|
||||
});
|
||||
|
||||
// Test that we fetch caps
|
||||
controller["initMatrixClient"]();
|
||||
expect(client.getCapabilities).toHaveBeenCalled();
|
||||
|
||||
// Test that we check caps.
|
||||
expect(controller.settingDisabled).toEqual(false);
|
||||
expect(controller.getValueOverride()).toEqual(null);
|
||||
expect(client.getCachedCapabilities).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("will check dependency settings before checking capabilites", async () => {
|
||||
const caps = {
|
||||
"m.change_password": {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
client.getCachedCapabilities.mockImplementation(() => caps);
|
||||
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, false);
|
||||
const controller = new RequiresSettingsController(["useCompactLayout"], false, (c: Capabilities) => false);
|
||||
|
||||
// Test that we fetch caps
|
||||
controller["initMatrixClient"]();
|
||||
expect(client.getCapabilities).toHaveBeenCalled();
|
||||
|
||||
// Test that we check caps.
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(controller.getValueOverride()).toEqual(false);
|
||||
expect(client.getCachedCapabilities).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("will disable setting if capability check is true and dependency settings are true", async () => {
|
||||
const caps = {
|
||||
"m.change_password": {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
client.getCachedCapabilities.mockImplementation(() => caps);
|
||||
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true);
|
||||
const controller = new RequiresSettingsController(["useCompactLayout"], false, (c: Capabilities) => {
|
||||
expect(c).toEqual(caps);
|
||||
return !c["m.change_password"]?.enabled;
|
||||
});
|
||||
|
||||
// Test that we fetch caps
|
||||
controller["initMatrixClient"]();
|
||||
expect(client.getCapabilities).toHaveBeenCalled();
|
||||
|
||||
// Test that we check caps.
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(controller.getValueOverride()).toEqual(false);
|
||||
expect(client.getCachedCapabilities).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
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 } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { type FeatureSettingKey, LabGroup, SETTINGS } from "../../../../src/settings/Settings";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { WatchManager } from "../../../../src/settings/WatchManager";
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
|
||||
describe("ServerSupportUnstableFeatureController", () => {
|
||||
const watchers = new WatchManager();
|
||||
const setting = "setting_name" as FeatureSettingKey;
|
||||
|
||||
async function prepareSetting(
|
||||
cli: MatrixClient,
|
||||
controller: ServerSupportUnstableFeatureController,
|
||||
): Promise<void> {
|
||||
SETTINGS[setting] = {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Messaging,
|
||||
displayName: "name of some kind" as TranslationKey,
|
||||
supportedLevels: [SettingLevel.DEVICE, SettingLevel.CONFIG],
|
||||
default: false,
|
||||
controller,
|
||||
};
|
||||
|
||||
const deferred = Promise.withResolvers<any>();
|
||||
watchers.watchSetting(setting, null, deferred.resolve);
|
||||
MatrixClientBackedController.matrixClient = cli;
|
||||
await deferred.promise;
|
||||
}
|
||||
|
||||
describe("getValueOverride()", () => {
|
||||
it("should return forced value is setting is disabled", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async () => false);
|
||||
|
||||
const controller = new ServerSupportUnstableFeatureController(
|
||||
setting,
|
||||
watchers,
|
||||
[["feature"]],
|
||||
undefined,
|
||||
undefined,
|
||||
"other_value",
|
||||
);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.getValueOverride(SettingLevel.DEVICE, null, true, SettingLevel.ACCOUNT)).toEqual(
|
||||
"other_value",
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass through to the handler if setting is not disabled", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async () => true);
|
||||
|
||||
const controller = new ServerSupportUnstableFeatureController(
|
||||
setting,
|
||||
watchers,
|
||||
[["feature"]],
|
||||
"other_value",
|
||||
);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.getValueOverride(SettingLevel.DEVICE, null, true, SettingLevel.ACCOUNT)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("settingDisabled()", () => {
|
||||
it("considered disabled if there is no matrix client", () => {
|
||||
const controller = new ServerSupportUnstableFeatureController(setting, watchers, [["org.matrix.msc3030"]]);
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
});
|
||||
|
||||
it("considered disabled if not all required features in the only feature group are supported", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async (featureName) => {
|
||||
return featureName === "org.matrix.msc3827.stable";
|
||||
});
|
||||
|
||||
const controller = new ServerSupportUnstableFeatureController(setting, watchers, [
|
||||
["org.matrix.msc3827.stable", "org.matrix.msc3030"],
|
||||
]);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
});
|
||||
|
||||
it("considered enabled if all required features in the only feature group are supported", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async (featureName) => {
|
||||
return featureName === "org.matrix.msc3827.stable" || featureName === "org.matrix.msc3030";
|
||||
});
|
||||
const controller = new ServerSupportUnstableFeatureController(setting, watchers, [
|
||||
["org.matrix.msc3827.stable", "org.matrix.msc3030"],
|
||||
]);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.settingDisabled).toEqual(false);
|
||||
});
|
||||
|
||||
it("considered enabled if all required features in one of the feature groups are supported", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async (featureName) => {
|
||||
return featureName === "org.matrix.msc3827.stable" || featureName === "org.matrix.msc3030";
|
||||
});
|
||||
const controller = new ServerSupportUnstableFeatureController(setting, watchers, [
|
||||
["foo-unsupported", "bar-unsupported"],
|
||||
["org.matrix.msc3827.stable", "org.matrix.msc3030"],
|
||||
]);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.settingDisabled).toEqual(false);
|
||||
});
|
||||
|
||||
it("considered disabled if not all required features in one of the feature groups are supported", async () => {
|
||||
const cli = stubClient();
|
||||
cli.doesServerSupportUnstableFeature = jest.fn(async (featureName) => {
|
||||
return featureName === "org.matrix.msc3827.stable";
|
||||
});
|
||||
|
||||
const controller = new ServerSupportUnstableFeatureController(setting, watchers, [
|
||||
["foo-unsupported", "bar-unsupported"],
|
||||
["org.matrix.msc3827.stable", "org.matrix.msc3030"],
|
||||
]);
|
||||
await prepareSetting(cli, controller);
|
||||
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 { Action } from "../../../../src/dispatcher/actions";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import SystemFontController from "../../../../src/settings/controllers/SystemFontController";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
const dispatchSpy = jest.spyOn(dis, "dispatch");
|
||||
|
||||
describe("SystemFontController", () => {
|
||||
it("dispatches a system font update action on change", () => {
|
||||
const controller = new SystemFontController();
|
||||
|
||||
const getValueSpy = jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any => {
|
||||
if (settingName === "useBundledEmojiFont") return false;
|
||||
if (settingName === "useSystemFont") return true;
|
||||
if (settingName === "systemFont") return "Comic Sans MS";
|
||||
});
|
||||
controller.onChange();
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith({
|
||||
action: Action.UpdateSystemFont,
|
||||
useBundledEmojiFont: false,
|
||||
useSystemFont: true,
|
||||
font: "Comic Sans MS",
|
||||
});
|
||||
|
||||
expect(getValueSpy).toHaveBeenCalledWith("useSystemFont");
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
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 {
|
||||
humanReadableNotificationLevel,
|
||||
NotificationLevel,
|
||||
} from "../../../../src/stores/notifications/NotificationLevel";
|
||||
|
||||
describe("NotificationLevel", () => {
|
||||
describe("humanReadableNotificationLevel", () => {
|
||||
it.each([
|
||||
[NotificationLevel.None, "None"],
|
||||
[NotificationLevel.Activity, "Activity"],
|
||||
[NotificationLevel.Notification, "Notification"],
|
||||
[NotificationLevel.Highlight, "Highlight"],
|
||||
[NotificationLevel.Unsent, "Unsent"],
|
||||
])("correctly maps the output", (color, output) => {
|
||||
expect(humanReadableNotificationLevel(color)).toBe(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 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 {
|
||||
Room,
|
||||
MatrixEventEvent,
|
||||
PendingEventOrdering,
|
||||
EventStatus,
|
||||
NotificationCountType,
|
||||
EventType,
|
||||
MatrixEvent,
|
||||
RoomEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { mkEvent, muteRoom, stubClient } from "../../../test-utils";
|
||||
import { RoomNotificationState } from "../../../../src/stores/notifications/RoomNotificationState";
|
||||
import { NotificationStateEvents } from "../../../../src/stores/notifications/NotificationState";
|
||||
import { NotificationLevel } from "../../../../src/stores/notifications/NotificationLevel";
|
||||
import { createMessageEventContent } from "../../../test-utils/events";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import * as UnreadModule from "../../../../src/Unread";
|
||||
|
||||
describe("RoomNotificationState", () => {
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room("!room:example.com", client, "@user:example.org", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
function addThread(room: Room): void {
|
||||
const threadId = "thread_id";
|
||||
jest.spyOn(room, "eventShouldLiveIn").mockReturnValue({
|
||||
shouldLiveInRoom: true,
|
||||
shouldLiveInThread: true,
|
||||
threadId,
|
||||
});
|
||||
const thread = room.createThread(
|
||||
threadId,
|
||||
new MatrixEvent({
|
||||
room_id: room.roomId,
|
||||
event_id: "event_root_1",
|
||||
type: EventType.RoomMessage,
|
||||
sender: "userId",
|
||||
content: createMessageEventContent("RootEvent"),
|
||||
}),
|
||||
[],
|
||||
true,
|
||||
);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
thread.addEvent(
|
||||
new MatrixEvent({
|
||||
room_id: room.roomId,
|
||||
event_id: "event_reply_1" + i,
|
||||
type: EventType.RoomMessage,
|
||||
sender: "userId",
|
||||
content: createMessageEventContent("ReplyEvent" + 1),
|
||||
}),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setUnreads(room: Room, greys: number, reds: number): void {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, reds);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, greys);
|
||||
}
|
||||
|
||||
it("updates on event decryption", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, true);
|
||||
const listener = jest.fn();
|
||||
roomNotifState.addListener(NotificationStateEvents.Update, listener);
|
||||
const testEvent = {
|
||||
getRoomId: () => room.roomId,
|
||||
} as unknown as MatrixEvent;
|
||||
room.getUnreadNotificationCount = jest.fn().mockReturnValue(1);
|
||||
client.emit(MatrixEventEvent.Decrypted, testEvent);
|
||||
expect(listener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits an Update event on marked unread room account data", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, true);
|
||||
const listener = jest.fn();
|
||||
roomNotifState.addListener(NotificationStateEvents.Update, listener);
|
||||
const accountDataEvent = {
|
||||
getType: () => "m.marked_unread",
|
||||
getContent: () => {
|
||||
return { unread: true };
|
||||
},
|
||||
} as unknown as MatrixEvent;
|
||||
room.getAccountData = jest.fn().mockReturnValue(accountDataEvent);
|
||||
room.emit(RoomEvent.AccountData, accountDataEvent, room);
|
||||
expect(listener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not update on other account data", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, true);
|
||||
const listener = jest.fn();
|
||||
roomNotifState.addListener(NotificationStateEvents.Update, listener);
|
||||
const accountDataEvent = {
|
||||
getType: () => "else.something",
|
||||
getContent: () => {
|
||||
return {};
|
||||
},
|
||||
} as unknown as MatrixEvent;
|
||||
room.getAccountData = jest.fn().mockReturnValue(accountDataEvent);
|
||||
room.emit(RoomEvent.AccountData, accountDataEvent, room);
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes listeners", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(() => roomNotifState.destroy()).not.toThrow();
|
||||
});
|
||||
|
||||
it("suggests an 'unread' ! if there are unsent messages", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: "m.message",
|
||||
user: "@user:example.org",
|
||||
content: {},
|
||||
});
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
room.addPendingEvent(event, "txn");
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.Unsent);
|
||||
expect(roomNotifState.symbol).toBe("!");
|
||||
expect(roomNotifState.count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("suggests nothing if the room is muted", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
|
||||
muteRoom(room);
|
||||
setUnreads(room, 1234, 0);
|
||||
room.updateMyMembership(KnownMembership.Join); // emit
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.None);
|
||||
expect(roomNotifState.symbol).toBe(null);
|
||||
expect(roomNotifState.count).toBe(0);
|
||||
});
|
||||
|
||||
it("suggests a red ! if the user has been invited to a room", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
|
||||
room.updateMyMembership(KnownMembership.Invite); // emit
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.Highlight);
|
||||
expect(roomNotifState.symbol).toBe("!");
|
||||
expect(roomNotifState.count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("returns a proper count and color for regular unreads", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
|
||||
setUnreads(room, 4321, 0);
|
||||
room.updateMyMembership(KnownMembership.Join); // emit
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.Notification);
|
||||
expect(roomNotifState.symbol).toBe(null);
|
||||
expect(roomNotifState.count).toBe(4321);
|
||||
});
|
||||
|
||||
it("returns a proper count and color for highlights", () => {
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
|
||||
setUnreads(room, 0, 69);
|
||||
room.updateMyMembership(KnownMembership.Join); // emit
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.Highlight);
|
||||
expect(roomNotifState.symbol).toBe(null);
|
||||
expect(roomNotifState.count).toBe(69);
|
||||
});
|
||||
|
||||
it("includes threads", async () => {
|
||||
const roomNotifState = new RoomNotificationState(room, true);
|
||||
|
||||
room.timeline.push(
|
||||
new MatrixEvent({
|
||||
room_id: room.roomId,
|
||||
type: EventType.RoomMessage,
|
||||
sender: "userId",
|
||||
content: createMessageEventContent("timeline event"),
|
||||
}),
|
||||
);
|
||||
|
||||
addThread(room);
|
||||
room.updateMyMembership(KnownMembership.Join); // emit
|
||||
|
||||
expect(roomNotifState.level).toBe(NotificationLevel.Activity);
|
||||
expect(roomNotifState.symbol).toBe(null);
|
||||
});
|
||||
|
||||
describe("computed attributes", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
|
||||
jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("should has invited at true", () => {
|
||||
room.updateMyMembership(KnownMembership.Invite);
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.invited).toBe(true);
|
||||
});
|
||||
|
||||
it("should has isUnsetMessage at true", () => {
|
||||
jest.spyOn(room, "getPendingEvents").mockReturnValue([
|
||||
mkEvent({ status: EventStatus.NOT_SENT, user: "@foobar:example.org", type: "any.event", content: {} }),
|
||||
]);
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.isUnsentMessage).toBe(true);
|
||||
});
|
||||
|
||||
it("should has isMention at false if the notification is invitation, an unset message or a knock", () => {
|
||||
setUnreads(room, 0, 2);
|
||||
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.isMention).toBe(true);
|
||||
|
||||
room.updateMyMembership(KnownMembership.Invite);
|
||||
expect(roomNotifState.isMention).toBe(false);
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
room.updateMyMembership(KnownMembership.Knock);
|
||||
expect(roomNotifState.isMention).toBe(false);
|
||||
|
||||
jest.spyOn(room, "getPendingEvents").mockReturnValue([
|
||||
mkEvent({ status: EventStatus.NOT_SENT, user: "@foobar:example.org", type: "any.event", content: {} }),
|
||||
]);
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
expect(roomNotifState.isMention).toBe(false);
|
||||
});
|
||||
|
||||
it("should has isNotification at true", () => {
|
||||
setUnreads(room, 1, 0);
|
||||
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.isNotification).toBe(true);
|
||||
});
|
||||
|
||||
it("should has isActivityNotification at true", () => {
|
||||
jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(true);
|
||||
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.isActivityNotification).toBe(true);
|
||||
});
|
||||
|
||||
it("should has hasAnyNotificationOrActivity at true", () => {
|
||||
// Hidebold is disabled
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
// Unread message, generate activity notification
|
||||
jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(true);
|
||||
// Highlight notification
|
||||
setUnreads(room, 0, 1);
|
||||
|
||||
// There is one highlight notification
|
||||
const roomNotifState = new RoomNotificationState(room, false);
|
||||
expect(roomNotifState.hasAnyNotificationOrActivity).toBe(true);
|
||||
|
||||
// Activity notification
|
||||
setUnreads(room, 0, 0);
|
||||
// Trigger update
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
// hidebold is disabled and we have an activity notification
|
||||
expect(roomNotifState.hasAnyNotificationOrActivity).toBe(true);
|
||||
|
||||
// hidebold is enabled and we have an activity notification
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
expect(roomNotifState.hasAnyNotificationOrActivity).toBe(false);
|
||||
|
||||
// No unread
|
||||
jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(false);
|
||||
room.updateMyMembership(KnownMembership.Join);
|
||||
expect(roomNotifState.hasAnyNotificationOrActivity).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,280 +0,0 @@
|
||||
/*
|
||||
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 { AutoDiscovery, AutoDiscoveryAction, type ClientConfig } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import fetchMock from "@fetch-mock/jest";
|
||||
|
||||
import AutoDiscoveryUtils from "../../../src/utils/AutoDiscoveryUtils";
|
||||
import { makeDelegatedAuthMetadata } from "../../test-utils/auth";
|
||||
|
||||
describe("AutoDiscoveryUtils", () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.catch({
|
||||
status: 404,
|
||||
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildValidatedConfigFromDiscovery()", () => {
|
||||
const serverName = "my-server";
|
||||
|
||||
beforeEach(() => {
|
||||
// don't litter console with expected errors
|
||||
jest.spyOn(logger, "error")
|
||||
.mockClear()
|
||||
.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
});
|
||||
|
||||
const validIsConfig = {
|
||||
"m.identity_server": {
|
||||
state: AutoDiscoveryAction.SUCCESS,
|
||||
base_url: "identity.com",
|
||||
},
|
||||
};
|
||||
const validHsConfig = {
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.SUCCESS,
|
||||
base_url: "https://matrix.org",
|
||||
},
|
||||
};
|
||||
|
||||
const expectedValidatedConfig = {
|
||||
hsName: serverName,
|
||||
hsNameIsDifferent: true,
|
||||
hsUrl: "https://matrix.org",
|
||||
isDefault: false,
|
||||
isNameResolvable: true,
|
||||
isUrl: "identity.com",
|
||||
};
|
||||
|
||||
it("throws an error when discovery result is falsy", async () => {
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, undefined as any),
|
||||
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws an error when discovery result does not include homeserver config", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
} as unknown as ClientConfig;
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws an error when identity server config has fail error and recognised error string", async () => {
|
||||
const discoveryResult = {
|
||||
...validHsConfig,
|
||||
"m.identity_server": {
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
error: "GenericFailure",
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws an error when homeserver config has fail error and recognised error string", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Homeserver URL does not appear to be a valid Matrix homeserver");
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws an error with fallback message identity server config has fail error", async () => {
|
||||
const discoveryResult = {
|
||||
...validHsConfig,
|
||||
"m.identity_server": {
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Unexpected error resolving identity server configuration");
|
||||
});
|
||||
|
||||
it("throws an error when error is ERROR_INVALID_HOMESERVER", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Homeserver URL does not appear to be a valid Matrix homeserver");
|
||||
});
|
||||
|
||||
it("throws an error when homeserver base_url is falsy", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.SUCCESS,
|
||||
base_url: "",
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Unexpected error resolving homeserver configuration");
|
||||
expect(logger.error).toHaveBeenCalledWith("No homeserver URL configured");
|
||||
});
|
||||
|
||||
it("throws an error when homeserver base_url is not a valid URL", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.SUCCESS,
|
||||
base_url: "banana",
|
||||
},
|
||||
};
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).rejects.toThrow("Invalid URL");
|
||||
});
|
||||
|
||||
it("uses hs url hostname when serverName is falsy in args and config", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
...validHsConfig,
|
||||
};
|
||||
await expect(AutoDiscoveryUtils.buildValidatedConfigFromDiscovery("", discoveryResult)).resolves.toEqual({
|
||||
...expectedValidatedConfig,
|
||||
hsNameIsDifferent: false,
|
||||
hsName: "matrix.org",
|
||||
warning: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses serverName from props", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
...validHsConfig["m.homeserver"],
|
||||
server_name: "should not use this name",
|
||||
},
|
||||
};
|
||||
const syntaxOnly = true;
|
||||
await expect(
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||
).resolves.toEqual({
|
||||
...expectedValidatedConfig,
|
||||
hsNameIsDifferent: true,
|
||||
hsName: serverName,
|
||||
warning: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores liveliness error when checking syntax only", async () => {
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
...validHsConfig["m.homeserver"],
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||
},
|
||||
};
|
||||
const syntaxOnly = true;
|
||||
await expect(
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||
).resolves.toEqual({
|
||||
...expectedValidatedConfig,
|
||||
warning: "Homeserver URL does not appear to be a valid Matrix homeserver",
|
||||
});
|
||||
});
|
||||
|
||||
it("handles homeserver too old error", async () => {
|
||||
const discoveryResult: ClientConfig = {
|
||||
...validIsConfig,
|
||||
"m.homeserver": {
|
||||
state: AutoDiscoveryAction.FAIL_ERROR,
|
||||
error: AutoDiscovery.ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION,
|
||||
base_url: "https://matrix.org",
|
||||
},
|
||||
};
|
||||
const syntaxOnly = true;
|
||||
await expect(() =>
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, syntaxOnly),
|
||||
).rejects.toThrow(
|
||||
"Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should validate delegated oidc auth", async () => {
|
||||
const issuer = "https://auth.matrix.org/";
|
||||
fetchMock.get(`${validHsConfig["m.homeserver"].base_url}/_matrix/client/versions`, { versions: ["v1.15"] });
|
||||
fetchMock.get(`${validHsConfig["m.homeserver"].base_url}/_matrix/client/v1/auth_metadata`, {
|
||||
...makeDelegatedAuthMetadata(issuer),
|
||||
scopes_supported: ["email"],
|
||||
response_modes_supported: ["query", "fragment"],
|
||||
prompt_values_supported: ["none", "login", "create"],
|
||||
device_authorization_endpoint: `${issuer}oauth2/device`,
|
||||
account_management_uri: `${issuer}account/`,
|
||||
account_management_actions_supported: [
|
||||
"org.matrix.profile",
|
||||
"org.matrix.sessions_list",
|
||||
"org.matrix.session_view",
|
||||
"org.matrix.session_end",
|
||||
"org.matrix.cross_signing_reset",
|
||||
],
|
||||
});
|
||||
|
||||
const discoveryResult = {
|
||||
...validIsConfig,
|
||||
...validHsConfig,
|
||||
};
|
||||
await expect(
|
||||
AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult),
|
||||
).resolves.toEqual({
|
||||
...expectedValidatedConfig,
|
||||
hsNameIsDifferent: true,
|
||||
hsName: serverName,
|
||||
delegatedAuthentication: expect.objectContaining({
|
||||
issuer,
|
||||
account_management_actions_supported: [
|
||||
"org.matrix.profile",
|
||||
"org.matrix.sessions_list",
|
||||
"org.matrix.session_view",
|
||||
"org.matrix.session_end",
|
||||
"org.matrix.cross_signing_reset",
|
||||
],
|
||||
account_management_uri: "https://auth.matrix.org/account/",
|
||||
authorization_endpoint: "https://auth.matrix.org/auth",
|
||||
registration_endpoint: "https://auth.matrix.org/registration",
|
||||
token_endpoint: "https://auth.matrix.org/token",
|
||||
}),
|
||||
warning: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("authComponentStateForError", () => {
|
||||
const error = new Error("TEST");
|
||||
|
||||
it("should return expected error for the registration page", () => {
|
||||
expect(AutoDiscoveryUtils.authComponentStateForError(error, "register")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
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 { mocked, type Mocked } from "jest-mock";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import {
|
||||
ClientEvent,
|
||||
EventType,
|
||||
type IContent,
|
||||
type MatrixClient,
|
||||
type MatrixEvent,
|
||||
type Room,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
describe("DMRoomMap", () => {
|
||||
const roomId1 = "!room1:example.com";
|
||||
const roomId2 = "!room2:example.com";
|
||||
const roomId3 = "!room3:example.com";
|
||||
const roomId4 = "!room4:example.com";
|
||||
|
||||
const validMDirectContent = {
|
||||
"user@example.com": [roomId1, roomId2],
|
||||
"@user:example.com": [roomId1, roomId3, roomId4],
|
||||
"@user2:example.com": [] as string[],
|
||||
} as IContent;
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let dmRoomMap: DMRoomMap;
|
||||
|
||||
const mkMDirectEvent = (content: any): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: EventType.Direct,
|
||||
user: client.getSafeUserId(),
|
||||
content: content,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = mocked(stubClient());
|
||||
jest.spyOn(logger, "warn");
|
||||
});
|
||||
|
||||
describe("when m.direct has valid content", () => {
|
||||
beforeEach(() => {
|
||||
client.getAccountData.mockReturnValue(mkMDirectEvent(validMDirectContent));
|
||||
dmRoomMap = new DMRoomMap(client);
|
||||
dmRoomMap.start();
|
||||
});
|
||||
|
||||
it("getRoomIds should return the room Ids", () => {
|
||||
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId3, roomId4]));
|
||||
});
|
||||
|
||||
describe("and there is an update with valid data", () => {
|
||||
beforeEach(() => {
|
||||
client.emit(
|
||||
ClientEvent.AccountData,
|
||||
mkMDirectEvent({
|
||||
"@user:example.com": [roomId1, roomId3],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("getRoomIds should return the new room Ids", () => {
|
||||
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId3]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is an update with invalid data", () => {
|
||||
const partiallyInvalidContent = {
|
||||
"@user1:example.com": [roomId1, roomId3],
|
||||
"@user2:example.com": "room2, room3",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client.emit(ClientEvent.AccountData, mkMDirectEvent(partiallyInvalidContent));
|
||||
});
|
||||
|
||||
it("getRoomIds should return the valid room Ids", () => {
|
||||
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId3]));
|
||||
});
|
||||
|
||||
it("should log the invalid content", () => {
|
||||
expect(logger.warn).toHaveBeenCalledWith("Invalid m.direct content occurred", partiallyInvalidContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when m.direct content contains the entire event", () => {
|
||||
const mDirectContentContent = {
|
||||
type: EventType.Direct,
|
||||
content: validMDirectContent,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client.getAccountData.mockReturnValue(mkMDirectEvent(mDirectContentContent));
|
||||
dmRoomMap = new DMRoomMap(client);
|
||||
});
|
||||
|
||||
it("should log the invalid content", () => {
|
||||
expect(logger.warn).toHaveBeenCalledWith("Invalid m.direct content occurred", mDirectContentContent);
|
||||
});
|
||||
|
||||
it("getRoomIds should return an empty list", () => {
|
||||
expect(dmRoomMap.getRoomIds()).toEqual(new Set([]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("when partially crap m.direct content appears", () => {
|
||||
const partiallyCrapContent = {
|
||||
"hello": 23,
|
||||
"@user1:example.com": [] as string[],
|
||||
"@user2:example.com": [roomId1, roomId2],
|
||||
"@user3:example.com": "room1, room2, room3",
|
||||
"@user4:example.com": [roomId4],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client.getAccountData.mockReturnValue(mkMDirectEvent(partiallyCrapContent));
|
||||
dmRoomMap = new DMRoomMap(client);
|
||||
});
|
||||
|
||||
it("should log the invalid content", () => {
|
||||
expect(logger.warn).toHaveBeenCalledWith("Invalid m.direct content occurred", partiallyCrapContent);
|
||||
});
|
||||
|
||||
it("getRoomIds should only return the valid items", () => {
|
||||
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId4]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUniqueRoomsWithIndividuals()", () => {
|
||||
const bigRoom = {
|
||||
roomId: "!bigRoom:server.org",
|
||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(5000),
|
||||
} as unknown as Room;
|
||||
const dmWithBob = {
|
||||
roomId: "!dmWithBob:server.org",
|
||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2),
|
||||
} as unknown as Room;
|
||||
const dmWithCharlie = {
|
||||
roomId: "!dmWithCharlie:server.org",
|
||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2),
|
||||
} as unknown as Room;
|
||||
const smallRoom = {
|
||||
roomId: "!smallRoom:server.org",
|
||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(3),
|
||||
} as unknown as Room;
|
||||
|
||||
const mDirectContent = {
|
||||
"@bob:server.org": [bigRoom.roomId, dmWithBob.roomId, smallRoom.roomId],
|
||||
"@charlie:server.org": [dmWithCharlie.roomId, smallRoom.roomId],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client.getAccountData.mockReturnValue(mkMDirectEvent(mDirectContent));
|
||||
client.getRoom.mockImplementation(
|
||||
(roomId: string) =>
|
||||
[bigRoom, smallRoom, dmWithCharlie, dmWithBob].find((room) => room.roomId === roomId) ?? null,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an empty object when room map has not been populated", () => {
|
||||
const instance = new DMRoomMap(client);
|
||||
expect(instance.getUniqueRoomsWithIndividuals()).toEqual({});
|
||||
});
|
||||
|
||||
it("returns map of users to rooms with 2 members", () => {
|
||||
const dmRoomMap = new DMRoomMap(client);
|
||||
dmRoomMap.start();
|
||||
expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({
|
||||
"@bob:server.org": dmWithBob,
|
||||
"@charlie:server.org": dmWithCharlie,
|
||||
});
|
||||
});
|
||||
|
||||
it("excludes rooms that are not found by matrixClient", () => {
|
||||
client.getRoom.mockReset().mockReturnValue(null);
|
||||
const dmRoomMap = new DMRoomMap(client);
|
||||
dmRoomMap.start();
|
||||
expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 SdkConfig from "../../../src/SdkConfig";
|
||||
import { shouldShowFeedback } from "../../../src/utils/Feedback";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../../src/settings/UIFeature";
|
||||
import { BugReportEndpointURLLocal } from "../../../src/IConfigOptions";
|
||||
|
||||
const realGetValue = SettingsStore.getValue;
|
||||
|
||||
describe("shouldShowFeedback", () => {
|
||||
afterEach(() => {
|
||||
SdkConfig.reset();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should return false if bug_report_endpoint_url is falsey", () => {
|
||||
SdkConfig.put({
|
||||
bug_report_endpoint_url: undefined,
|
||||
});
|
||||
expect(shouldShowFeedback()).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return false if bug_report_endpoint_url is 'test'", () => {
|
||||
SdkConfig.put({
|
||||
bug_report_endpoint_url: BugReportEndpointURLLocal,
|
||||
});
|
||||
expect(shouldShowFeedback()).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return false if UIFeature.Feedback is disabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((key, ...params) => {
|
||||
if (key === UIFeature.Feedback) {
|
||||
return false;
|
||||
}
|
||||
return realGetValue(key, ...params);
|
||||
});
|
||||
expect(shouldShowFeedback()).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true if bug_report_endpoint_url is set and UIFeature.Feedback is true", () => {
|
||||
SdkConfig.put({
|
||||
bug_report_endpoint_url: "https://rageshake.server",
|
||||
});
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((key, ...params) => {
|
||||
if (key === UIFeature.Feedback) {
|
||||
return true;
|
||||
}
|
||||
return realGetValue(key, ...params);
|
||||
});
|
||||
expect(shouldShowFeedback()).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MediaEventHelper } from "../../../src/utils/MediaEventHelper.ts";
|
||||
import { stubClient } from "../../test-utils";
|
||||
|
||||
describe("MediaEventHelper", () => {
|
||||
it("should set the mime type on the blob based on the event metadata", async () => {
|
||||
stubClient();
|
||||
|
||||
const event = new MatrixEvent({
|
||||
type: "m.room.message",
|
||||
content: {
|
||||
msgtype: "m.image",
|
||||
body: "image.png",
|
||||
info: {
|
||||
mimetype: "image/png",
|
||||
size: 1234,
|
||||
w: 100,
|
||||
h: 100,
|
||||
thumbnail_info: {
|
||||
mimetype: "image/png",
|
||||
},
|
||||
thumbnail_url: "mxc://matrix.org/thumbnail",
|
||||
},
|
||||
url: "mxc://matrix.org/abcdef",
|
||||
},
|
||||
});
|
||||
const helper = new MediaEventHelper(event);
|
||||
|
||||
const blob = await helper.thumbnailBlob.value;
|
||||
expect(blob?.type).toBe(event.getContent().info.thumbnail_info?.mimetype);
|
||||
});
|
||||
});
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 { mocked } from "jest-mock";
|
||||
import { EventType, type MatrixClient, MatrixError, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import Modal, { type ComponentType, type ComponentProps } from "../../../src/Modal";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import MultiInviter, { type CompletionStates } from "../../../src/utils/MultiInviter";
|
||||
import * as TestUtilsMatrix from "../../test-utils";
|
||||
import AskInviteAnywayDialog from "../../../src/components/views/dialogs/AskInviteAnywayDialog";
|
||||
import ConfirmUserActionDialog from "../../../src/components/views/dialogs/ConfirmUserActionDialog";
|
||||
|
||||
const ROOMID = "!room:server";
|
||||
|
||||
const MXID1 = "@user1:server";
|
||||
const MXID2 = "@user2:server";
|
||||
const MXID3 = "@user3:server";
|
||||
|
||||
const MXID_PROFILE_STATES: Record<string, () => {}> = {
|
||||
[MXID1]: () => ({}),
|
||||
[MXID2]: () => {
|
||||
throw new MatrixError({ errcode: "M_FORBIDDEN" });
|
||||
},
|
||||
[MXID3]: () => {
|
||||
throw new MatrixError({ errcode: "M_NOT_FOUND" });
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock("../../../src/Modal", () => ({
|
||||
createDialog: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../src/settings/SettingsStore", () => ({
|
||||
getValue: jest.fn(),
|
||||
monitorSetting: jest.fn(),
|
||||
watchSetting: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockPromptBeforeInviteUnknownUsers = (value: boolean) => {
|
||||
mocked(SettingsStore.getValue).mockImplementation(
|
||||
(settingName: string, roomId: string, _excludeDefault = false): any => {
|
||||
if (settingName === "promptBeforeInviteUnknownUsers" && roomId === ROOMID) {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const mockCreateTrackedDialog = (callbackName: "onInviteAnyways" | "onGiveUp") => {
|
||||
mocked(Modal.createDialog).mockImplementation((Element: ComponentType, props?: ComponentProps<ComponentType>) => {
|
||||
if (Element === AskInviteAnywayDialog) {
|
||||
(props as ComponentProps<typeof AskInviteAnywayDialog>)[callbackName]();
|
||||
}
|
||||
return { close: jest.fn(), finished: new Promise(() => {}) };
|
||||
});
|
||||
};
|
||||
|
||||
const expectAllInvitedResult = (result: CompletionStates) => {
|
||||
expect(result).toEqual({
|
||||
[MXID1]: "invited",
|
||||
[MXID2]: "invited",
|
||||
[MXID3]: "invited",
|
||||
});
|
||||
};
|
||||
|
||||
describe("MultiInviter", () => {
|
||||
let client: jest.Mocked<MatrixClient>;
|
||||
let inviter: MultiInviter;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mocked(Modal.createDialog).mockReturnValue({ close: jest.fn(), finished: new Promise(() => {}) });
|
||||
|
||||
TestUtilsMatrix.stubClient();
|
||||
client = MatrixClientPeg.safeGet() as jest.Mocked<MatrixClient>;
|
||||
|
||||
client.invite = jest.fn();
|
||||
client.invite.mockResolvedValue({});
|
||||
|
||||
client.getProfileInfo = jest.fn();
|
||||
client.getProfileInfo.mockImplementation(async (userId: string) => {
|
||||
const m = MXID_PROFILE_STATES[userId];
|
||||
if (m) return m();
|
||||
throw new Error();
|
||||
});
|
||||
client.unban = jest.fn();
|
||||
|
||||
inviter = new MultiInviter(client, ROOMID);
|
||||
});
|
||||
|
||||
describe("invite", () => {
|
||||
it("should show a progress dialog while the invite happens", async () => {
|
||||
const mockModalHandle = { close: jest.fn(), finished: new Promise<[]>(() => {}) };
|
||||
mocked(Modal.createDialog).mockReturnValue(mockModalHandle);
|
||||
|
||||
const invitePromise = Promise.withResolvers<{}>();
|
||||
client.invite.mockReturnValue(invitePromise.promise);
|
||||
|
||||
const resultPromise = inviter.invite([MXID1]);
|
||||
expect(Modal.createDialog).toHaveBeenCalledTimes(1);
|
||||
expect(mockModalHandle.close).not.toHaveBeenCalled();
|
||||
|
||||
invitePromise.resolve({});
|
||||
await resultPromise;
|
||||
expect(mockModalHandle.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("with promptBeforeInviteUnknownUsers = false", () => {
|
||||
beforeEach(() => mockPromptBeforeInviteUnknownUsers(false));
|
||||
|
||||
it("should invite all users", async () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(3);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, { shareEncryptedHistory: true });
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, { shareEncryptedHistory: true });
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, { shareEncryptedHistory: true });
|
||||
|
||||
expectAllInvitedResult(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with promptBeforeInviteUnknownUsers = true and", () => {
|
||||
beforeEach(() => mockPromptBeforeInviteUnknownUsers(true));
|
||||
|
||||
describe("confirming the unknown user dialog", () => {
|
||||
beforeEach(() => mockCreateTrackedDialog("onInviteAnyways"));
|
||||
|
||||
it("should invite all users", async () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(3);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, { shareEncryptedHistory: true });
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, { shareEncryptedHistory: true });
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, { shareEncryptedHistory: true });
|
||||
|
||||
expectAllInvitedResult(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("declining the unknown user dialog", () => {
|
||||
beforeEach(() => mockCreateTrackedDialog("onGiveUp"));
|
||||
|
||||
it("should only invite existing users", async () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(1);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, { shareEncryptedHistory: true });
|
||||
|
||||
// The resolved state is 'invited' for all users.
|
||||
// With the above client expectations, the test ensures that only the first user is invited.
|
||||
expectAllInvitedResult(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should show sensible error when attempting 3pid invite with no identity server", async () => {
|
||||
client.inviteByEmail = jest.fn().mockRejectedValueOnce(
|
||||
new MatrixError({
|
||||
errcode: "ORG.MATRIX.JSSDK_MISSING_PARAM",
|
||||
}),
|
||||
);
|
||||
await inviter.invite(["foo@bar.com"]);
|
||||
expect(inviter.getErrorText("foo@bar.com")).toMatchInlineSnapshot(
|
||||
`"Cannot invite user by email without an identity server. You can connect to one under "Settings"."`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should ask if user wants to unban user if they have permission", async () => {
|
||||
mocked(Modal.createDialog).mockImplementation(
|
||||
(Element: ComponentType, props?: ComponentProps<ComponentType>): any => {
|
||||
// We stub out the modal with an immediate affirmative (proceed) return
|
||||
return { finished: Promise.resolve([true]) };
|
||||
},
|
||||
);
|
||||
|
||||
const room = new Room(ROOMID, client, client.getSafeUserId());
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
const ourMember = new RoomMember(ROOMID, client.getSafeUserId());
|
||||
ourMember.membership = KnownMembership.Join;
|
||||
ourMember.powerLevel = 100;
|
||||
const member = new RoomMember(ROOMID, MXID1);
|
||||
member.membership = KnownMembership.Ban;
|
||||
member.powerLevel = 0;
|
||||
room.getMember = (userId: string) => {
|
||||
if (userId === client.getSafeUserId()) return ourMember;
|
||||
if (userId === MXID1) return member;
|
||||
return null;
|
||||
};
|
||||
|
||||
await inviter.invite([MXID1]);
|
||||
expect(Modal.createDialog).toHaveBeenCalledWith(ConfirmUserActionDialog, {
|
||||
member,
|
||||
title: "User cannot be invited until they are unbanned",
|
||||
action: "Unban",
|
||||
});
|
||||
expect(client.unban).toHaveBeenCalledWith(ROOMID, MXID1);
|
||||
});
|
||||
|
||||
it("should show sensible error when attempting to invite over federation with m.federate=false", async () => {
|
||||
mocked(client.invite).mockRejectedValueOnce(
|
||||
new MatrixError({
|
||||
errcode: "M_FORBIDDEN",
|
||||
}),
|
||||
);
|
||||
const room = new Room(ROOMID, client, client.getSafeUserId());
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomCreate,
|
||||
state_key: "",
|
||||
content: {
|
||||
"m.federate": false,
|
||||
},
|
||||
room_id: ROOMID,
|
||||
}),
|
||||
]);
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
|
||||
await inviter.invite(["@user:other_server"]);
|
||||
expect(inviter.getErrorText("@user:other_server")).toMatchInlineSnapshot(
|
||||
`"This room is unfederated. You cannot invite people from external servers."`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should show sensible error when attempting to invite over federation with m.federate=false to space", async () => {
|
||||
mocked(client.invite).mockRejectedValueOnce(
|
||||
new MatrixError({
|
||||
errcode: "M_FORBIDDEN",
|
||||
}),
|
||||
);
|
||||
const room = new Room(ROOMID, client, client.getSafeUserId());
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomCreate,
|
||||
state_key: "",
|
||||
content: {
|
||||
"m.federate": false,
|
||||
"type": "m.space",
|
||||
},
|
||||
room_id: ROOMID,
|
||||
}),
|
||||
]);
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
|
||||
await inviter.invite(["@user:other_server"]);
|
||||
expect(inviter.getErrorText("@user:other_server")).toMatchInlineSnapshot(
|
||||
`"This space is unfederated. You cannot invite people from external servers."`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 Boluwatife Omosowon <boluomosowon@gmail.com>
|
||||
|
||||
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 { mocked } from "jest-mock";
|
||||
|
||||
import { parsePermalink } from "../../../src/utils/permalinks/Permalinks";
|
||||
import { transformSearchTerm } from "../../../src/utils/SearchInput";
|
||||
|
||||
jest.mock("../../../src/utils/permalinks/Permalinks");
|
||||
jest.mock("../../../src/stores/WidgetStore");
|
||||
jest.mock("../../../src/stores/widgets/WidgetLayoutStore");
|
||||
|
||||
describe("transforming search term", () => {
|
||||
it("should return the primaryEntityId if the search term was a permalink", () => {
|
||||
const roomLink = "https://matrix.to/#/#element-dev:matrix.org";
|
||||
const parsedPermalink = "#element-dev:matrix.org";
|
||||
|
||||
mocked(parsePermalink).mockReturnValue({
|
||||
primaryEntityId: parsedPermalink,
|
||||
roomIdOrAlias: parsedPermalink,
|
||||
eventId: "",
|
||||
userId: "",
|
||||
viaServers: [],
|
||||
});
|
||||
|
||||
expect(transformSearchTerm(roomLink)).toBe(parsedPermalink);
|
||||
});
|
||||
|
||||
it("should return the original search term if the search term is a permalink and the primaryEntityId is null", () => {
|
||||
const searchTerm = "https://matrix.to/#/#random-link:matrix.org";
|
||||
|
||||
mocked(parsePermalink).mockReturnValue({
|
||||
primaryEntityId: null,
|
||||
roomIdOrAlias: null,
|
||||
eventId: null,
|
||||
userId: null,
|
||||
viaServers: null,
|
||||
});
|
||||
|
||||
expect(transformSearchTerm(searchTerm)).toBe(searchTerm);
|
||||
});
|
||||
|
||||
it("should return the original search term if the search term was not a permalink", () => {
|
||||
const searchTerm = "search term";
|
||||
mocked(parsePermalink).mockReturnValue(null);
|
||||
expect(transformSearchTerm(searchTerm)).toBe(searchTerm);
|
||||
});
|
||||
});
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import { shieldStatusForRoom } from "../../../src/utils/ShieldUtils";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
|
||||
function mkClient(selfTrust = false) {
|
||||
return {
|
||||
getUserId: () => "@self:localhost",
|
||||
getCrypto: () => ({
|
||||
getDeviceVerificationStatus: (userId: string, deviceId: string) =>
|
||||
Promise.resolve({
|
||||
isVerified: () => (userId === "@self:localhost" ? selfTrust : userId[2] == "T"),
|
||||
}),
|
||||
getUserDeviceInfo: async (userIds: string[]) => {
|
||||
return new Map(userIds.map((u) => [u, new Map([["DEVICE", {}]])]));
|
||||
},
|
||||
getUserVerificationStatus: async (userId: string): Promise<UserVerificationStatus> =>
|
||||
new UserVerificationStatus(userId[1] == "T", userId[1] == "T" || userId[1] == "W", false),
|
||||
}),
|
||||
} as unknown as MatrixClient;
|
||||
}
|
||||
|
||||
describe("mkClient self-test", function () {
|
||||
test.each([true, false])("behaves well for self-trust=%s", async (v) => {
|
||||
const client = mkClient(v);
|
||||
const status = await client.getCrypto()!.getDeviceVerificationStatus("@self:localhost", "DEVICE");
|
||||
expect(status?.isVerified()).toBe(v);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["@TT:h", true],
|
||||
["@TF:h", true],
|
||||
["@FT:h", false],
|
||||
["@FF:h", false],
|
||||
])("behaves well for user trust %s", async (userId, trust) => {
|
||||
const status = await mkClient().getCrypto()?.getUserVerificationStatus(userId);
|
||||
expect(status!.isCrossSigningVerified()).toBe(trust);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["@TT:h", true],
|
||||
["@TF:h", false],
|
||||
["@FT:h", true],
|
||||
["@FF:h", false],
|
||||
])("behaves well for device trust %s", async (userId, trust) => {
|
||||
const status = await mkClient().getCrypto()!.getDeviceVerificationStatus(userId, "device");
|
||||
expect(status?.isVerified()).toBe(trust);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shieldStatusForMembership self-trust behaviour", function () {
|
||||
beforeAll(() => {
|
||||
const mockInstance = {
|
||||
getUserIdForRoomId: (roomId: string) => (roomId === "DM" ? "@any:h" : null),
|
||||
} as unknown as DMRoomMap;
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(mockInstance);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(DMRoomMap, "shared").mockRestore();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[true, true],
|
||||
[true, false],
|
||||
[false, true],
|
||||
[false, false],
|
||||
])("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF1:h", "@FF2:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual("normal");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["verified", true, true],
|
||||
["verified", true, false],
|
||||
["verified", false, true],
|
||||
["warning", false, false],
|
||||
])("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@TT2:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["normal", true, true],
|
||||
["normal", true, false],
|
||||
["normal", false, true],
|
||||
["warning", false, false],
|
||||
])("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@FF2:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["verified", true, true],
|
||||
["verified", true, false],
|
||||
["warning", false, true],
|
||||
["warning", false, false],
|
||||
])("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["verified", true, true],
|
||||
["verified", true, false],
|
||||
["verified", false, true],
|
||||
["verified", false, false],
|
||||
])("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["normal", true, true],
|
||||
["normal", true, false],
|
||||
["normal", false, true],
|
||||
["normal", false, false],
|
||||
])("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shieldStatusForMembership other-trust behaviour", function () {
|
||||
beforeAll(() => {
|
||||
const mockInstance = {
|
||||
getUserIdForRoomId: (roomId: string) => (roomId === "DM" ? "@any:h" : null),
|
||||
} as unknown as DMRoomMap;
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(mockInstance);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["warning", true],
|
||||
["warning", false],
|
||||
])("1 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["warning", true],
|
||||
["warning", false],
|
||||
])("2 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h", "@TT:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["normal", true],
|
||||
["normal", false],
|
||||
])("2 unverified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h", "@FT:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["warning", true],
|
||||
["warning", false],
|
||||
])("2 was verified: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@WF:h", "@FT:h"].map((userId) => ({ userId })),
|
||||
} as unknown as Room;
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 Oliver Sand
|
||||
Copyright 2022 Nordeck IT + Consulting GmbH.
|
||||
|
||||
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 WidgetUtils from "../../../src/utils/WidgetUtils";
|
||||
import { mockPlatformPeg } from "../../test-utils";
|
||||
|
||||
describe("getLocalJitsiWrapperUrl", () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, "location", {
|
||||
value: {
|
||||
origin: "https://app.element.io",
|
||||
pathname: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate jitsi URL (for defaults)", () => {
|
||||
mockPlatformPeg();
|
||||
|
||||
expect(WidgetUtils.getLocalJitsiWrapperUrl()).toEqual(
|
||||
"https://app.element.io/jitsi.html" +
|
||||
"#conferenceDomain=$domain" +
|
||||
"&conferenceId=$conferenceId" +
|
||||
"&isAudioOnly=$isAudioOnly" +
|
||||
"&startWithAudioMuted=$startWithAudioMuted" +
|
||||
"&startWithVideoMuted=$startWithVideoMuted" +
|
||||
"&isVideoChannel=$isVideoChannel" +
|
||||
"&displayName=$matrix_display_name" +
|
||||
"&avatarUrl=$matrix_avatar_url" +
|
||||
"&userId=$matrix_user_id" +
|
||||
"&roomId=$matrix_room_id" +
|
||||
"&theme=$theme" +
|
||||
"&roomName=$roomName" +
|
||||
"&supportsScreensharing=true" +
|
||||
"&language=$org.matrix.msc2873.client_language",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`AutoDiscoveryUtils authComponentStateForError should return expected error for the registration page 1`] = `
|
||||
{
|
||||
"serverDeadError": <div>
|
||||
<strong>
|
||||
Your Element is misconfigured
|
||||
</strong>
|
||||
<div>
|
||||
<span>
|
||||
Ask your Element admin to check
|
||||
<a
|
||||
href="https://github.com/vector-im/element-web/blob/master/docs/config.md"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
your config
|
||||
</a>
|
||||
for incorrect or duplicate entries.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
"serverErrorIsFatal": true,
|
||||
"serverIsAlive": false,
|
||||
}
|
||||
`;
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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,
|
||||
type MatrixEvent,
|
||||
Room,
|
||||
type RoomMember,
|
||||
type RoomState,
|
||||
RoomStateEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { isKnockDenied, waitForMember } from "../../../src/utils/membership";
|
||||
import { createTestClient, mkRoomMember, stubClient } from "../../test-utils";
|
||||
|
||||
describe("isKnockDenied", () => {
|
||||
const userId = "alice";
|
||||
let client: jest.Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient() as jest.Mocked<MatrixClient>;
|
||||
room = new Room("!room-id:example.com", client, "@user:example.com");
|
||||
});
|
||||
|
||||
it("checks that the user knock has been denied", () => {
|
||||
const roomMember = mkRoomMember(room.roomId, userId, KnownMembership.Leave, true, {
|
||||
membership: KnownMembership.Knock,
|
||||
});
|
||||
jest.spyOn(room, "getMember").mockReturnValue(roomMember);
|
||||
expect(isKnockDenied(room)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ membership: KnownMembership.Leave, isKicked: false, prevMembership: KnownMembership.Invite },
|
||||
{ membership: KnownMembership.Leave, isKicked: true, prevMembership: KnownMembership.Invite },
|
||||
{ membership: KnownMembership.Leave, isKicked: false, prevMembership: KnownMembership.Join },
|
||||
{ membership: KnownMembership.Leave, isKicked: true, prevMembership: KnownMembership.Join },
|
||||
])("checks that the user knock has been not denied", ({ membership, isKicked, prevMembership }) => {
|
||||
const roomMember = mkRoomMember(room.roomId, userId, membership, isKicked, { membership: prevMembership });
|
||||
jest.spyOn(room, "getMember").mockReturnValue(roomMember);
|
||||
expect(isKnockDenied(room)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/* Shorter timeout, we've got tests to run */
|
||||
const timeout = 30;
|
||||
|
||||
describe("waitForMember", () => {
|
||||
const STUB_ROOM_ID = "!stub_room:domain";
|
||||
const STUB_MEMBER_ID = "!stub_member:domain";
|
||||
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
|
||||
// getRoom() only knows about !stub_room, which has only one member
|
||||
const stubRoom = {
|
||||
getMember: jest.fn().mockImplementation((userId) => {
|
||||
return userId === STUB_MEMBER_ID ? ({} as RoomMember) : null;
|
||||
}),
|
||||
};
|
||||
mocked(client.getRoom).mockImplementation((roomId) => {
|
||||
return roomId === STUB_ROOM_ID ? (stubRoom as unknown as Room) : null;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("resolves with false if the timeout is reached", async () => {
|
||||
const result = await waitForMember(client, "", "", { timeout: 0 });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", async () => {
|
||||
jest.useFakeTimers();
|
||||
const roomId = "!roomId:domain";
|
||||
const userId = "@clientId:domain";
|
||||
const resultProm = waitForMember(client, roomId, userId, { timeout });
|
||||
jest.advanceTimersByTime(50);
|
||||
expect(await resultProm).toBe(false);
|
||||
client.emit(
|
||||
RoomStateEvent.NewMember,
|
||||
undefined as unknown as MatrixEvent,
|
||||
undefined as unknown as RoomState,
|
||||
{
|
||||
roomId,
|
||||
userId: "@anotherClient:domain",
|
||||
} as RoomMember,
|
||||
);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("resolves with true if RoomState.newMember fires", async () => {
|
||||
const roomId = "!roomId:domain";
|
||||
const userId = "@clientId:domain";
|
||||
const resultProm = waitForMember(client, roomId, userId, { timeout });
|
||||
client.emit(
|
||||
RoomStateEvent.NewMember,
|
||||
undefined as unknown as MatrixEvent,
|
||||
undefined as unknown as RoomState,
|
||||
{ roomId, userId } as RoomMember,
|
||||
);
|
||||
expect(await resultProm).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves immediately if the user is already a member", async () => {
|
||||
jest.useFakeTimers();
|
||||
const resultProm = waitForMember(client, STUB_ROOM_ID, STUB_MEMBER_ID, { timeout });
|
||||
expect(await resultProm).toBe(true);
|
||||
});
|
||||
|
||||
it("waits for the timeout if the room is known but the user is not", async () => {
|
||||
const result = await waitForMember(client, STUB_ROOM_ID, "@other_user", { timeout: 0 });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user