Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
f0ee7f7905
3265 changed files with 484599 additions and 699 deletions
142
test/unit-tests/settings/SettingsStore-test.ts
Normal file
142
test/unit-tests/settings/SettingsStore-test.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ClientEvent, MatrixClient, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import BasePlatform from "../../../src/BasePlatform";
|
||||
import SdkConfig from "../../../src/SdkConfig";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
name: "Electron.showTrayIcon",
|
||||
level: SettingLevel.PLATFORM,
|
||||
value: true,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* An existing setting that has {@link IBaseSetting#supportedLevelsAreOrdered} set to true.
|
||||
*/
|
||||
const SETTING_NAME_WITH_CONFIG_OVERRIDE = "feature_msc3531_hide_messages_pending_moderation";
|
||||
|
||||
describe("SettingsStore", () => {
|
||||
let platformSettings: Record<string, any>;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
platformSettings = {};
|
||||
mockPlatformPeg({
|
||||
isLevelSupported: jest.fn().mockReturnValue(true),
|
||||
supportsSetting: jest.fn().mockReturnValue(true),
|
||||
setSettingValue: jest.fn().mockImplementation((settingName: string, value: any) => {
|
||||
platformSettings[settingName] = value;
|
||||
}),
|
||||
getSettingValue: jest.fn().mockImplementation((settingName: string) => {
|
||||
return platformSettings[settingName];
|
||||
}),
|
||||
reload: jest.fn(),
|
||||
} as unknown as BasePlatform);
|
||||
|
||||
TEST_DATA.forEach((d) => {
|
||||
SettingsStore.setValue(d.name, null, d.level, d.value);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
SdkConfig.reset();
|
||||
});
|
||||
|
||||
describe("getValueAt", () => {
|
||||
TEST_DATA.forEach((d) => {
|
||||
it(`should return the value "${d.level}"."${d.name}"`, () => {
|
||||
expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value);
|
||||
// regression test #22545
|
||||
expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value);
|
||||
});
|
||||
});
|
||||
|
||||
it(`supportedLevelsAreOrdered correctly overrides setting`, async () => {
|
||||
SdkConfig.put({
|
||||
features: {
|
||||
[SETTING_NAME_WITH_CONFIG_OVERRIDE]: false,
|
||||
},
|
||||
});
|
||||
await SettingsStore.setValue(SETTING_NAME_WITH_CONFIG_OVERRIDE, null, SettingLevel.DEVICE, true);
|
||||
expect(SettingsStore.getValue(SETTING_NAME_WITH_CONFIG_OVERRIDE)).toBe(false);
|
||||
});
|
||||
|
||||
it(`supportedLevelsAreOrdered doesn't incorrectly override setting`, async () => {
|
||||
await SettingsStore.setValue(SETTING_NAME_WITH_CONFIG_OVERRIDE, null, SettingLevel.DEVICE, true);
|
||||
expect(SettingsStore.getValueAt(SettingLevel.DEVICE, SETTING_NAME_WITH_CONFIG_OVERRIDE)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("runMigrations", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let localStorageSetItemSpy: jest.SpyInstance;
|
||||
let localStorageSetPromise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = mkStubRoom("!room:example.org", "Room", client);
|
||||
room.getAccountData = jest.fn().mockReturnValue({
|
||||
getContent: jest.fn().mockReturnValue({
|
||||
urlPreviewsEnabled_e2ee: true,
|
||||
}),
|
||||
});
|
||||
client.getRooms = jest.fn().mockReturnValue([room]);
|
||||
client.getRoom = jest.fn().mockReturnValue(room);
|
||||
|
||||
localStorageSetPromise = new Promise((resolve) => {
|
||||
localStorageSetItemSpy = jest
|
||||
.spyOn(localStorage.__proto__, "setItem")
|
||||
.mockImplementation(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("migrates URL previews setting for e2ee rooms", async () => {
|
||||
SettingsStore.runMigrations(false);
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
|
||||
|
||||
expect(room.getAccountData).toHaveBeenCalled();
|
||||
|
||||
await localStorageSetPromise;
|
||||
|
||||
expect(localStorageSetItemSpy!).toHaveBeenCalledWith(
|
||||
`mx_setting_urlPreviewsEnabled_e2ee_${room.roomId}`,
|
||||
JSON.stringify({ value: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not migrate e2ee URL previews on a fresh login", async () => {
|
||||
SettingsStore.runMigrations(true);
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
|
||||
|
||||
expect(room.getAccountData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not migrate if the device is flagged as migrated", async () => {
|
||||
jest.spyOn(localStorage.__proto__, "getItem").mockImplementation((key: unknown): string | undefined => {
|
||||
if (key === "url_previews_e2ee_migration_done") return JSON.stringify({ value: true });
|
||||
return undefined;
|
||||
});
|
||||
SettingsStore.runMigrations(false);
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
|
||||
|
||||
expect(room.getAccountData).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
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
|
||||
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");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
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));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import fetchMockJest 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(() => {
|
||||
fetchMockJest.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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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
|
||||
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";
|
||||
|
||||
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("feature_spotlight", { key: null });
|
||||
settingsGetValueSpy.mockReturnValue(true);
|
||||
// true === true
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
expect(controller.settingDisabled).toEqual(true);
|
||||
expect(settingsGetValueSpy).toHaveBeenCalledWith("feature_spotlight");
|
||||
});
|
||||
|
||||
it("returns false when setting value is not true", () => {
|
||||
// no incompatible value set, defaulted to true
|
||||
const controller = new IncompatibleController("feature_spotlight", { 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("feature_spotlight", { key: null }, "test");
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when setting value is not true", () => {
|
||||
const controller = new IncompatibleController("feature_spotlight", { 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("feature_spotlight", { 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("feature_spotlight", { 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("feature_spotlight", { key: null });
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", true, SettingLevel.ACCOUNT),
|
||||
).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { LabGroup, SETTINGS } from "../../../../src/settings/Settings";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { WatchManager } from "../../../../src/settings/WatchManager";
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
import { TranslationKey } from "../../../../src/languageHandler";
|
||||
|
||||
describe("ServerSupportUnstableFeatureController", () => {
|
||||
const watchers = new WatchManager();
|
||||
const setting = "setting_name";
|
||||
|
||||
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 = defer<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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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
|
||||
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) => {
|
||||
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");
|
||||
});
|
||||
});
|
60
test/unit-tests/settings/controllers/ThemeController-test.ts
Normal file
60
test/unit-tests/settings/controllers/ThemeController-test.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import ThemeController from "../../../../src/settings/controllers/ThemeController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { DEFAULT_THEME } from "../../../../src/theme";
|
||||
|
||||
describe("ThemeController", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue([]);
|
||||
|
||||
afterEach(() => {
|
||||
// reset
|
||||
ThemeController.isLogin = false;
|
||||
});
|
||||
|
||||
it("returns null when calculatedValue is falsy", () => {
|
||||
const controller = new ThemeController();
|
||||
|
||||
expect(
|
||||
controller.getValueOverride(
|
||||
SettingLevel.ACCOUNT,
|
||||
"$room:server",
|
||||
undefined /* calculatedValue */,
|
||||
SettingLevel.ACCOUNT,
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it("returns light when login flag is set", () => {
|
||||
const controller = new ThemeController();
|
||||
|
||||
ThemeController.isLogin = true;
|
||||
|
||||
expect(controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", "dark", SettingLevel.ACCOUNT)).toEqual(
|
||||
"light",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns default theme when value is not a valid theme", () => {
|
||||
const controller = new ThemeController();
|
||||
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", "my-test-theme", SettingLevel.ACCOUNT),
|
||||
).toEqual(DEFAULT_THEME);
|
||||
});
|
||||
|
||||
it("returns null when value is a valid theme", () => {
|
||||
const controller = new ThemeController();
|
||||
|
||||
expect(controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", "dark", SettingLevel.ACCOUNT)).toEqual(
|
||||
null,
|
||||
);
|
||||
});
|
||||
});
|
38
test/unit-tests/settings/enums/ImageSize-test.ts
Normal file
38
test/unit-tests/settings/enums/ImageSize-test.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ImageSize, suggestedSize } from "../../../../src/settings/enums/ImageSize";
|
||||
|
||||
describe("ImageSize", () => {
|
||||
describe("suggestedSize", () => {
|
||||
it("constrains width", () => {
|
||||
const size = suggestedSize(ImageSize.Normal, { w: 648, h: 162 });
|
||||
expect(size).toStrictEqual({ w: 324, h: 81 });
|
||||
});
|
||||
it("constrains height", () => {
|
||||
const size = suggestedSize(ImageSize.Normal, { w: 162, h: 648 });
|
||||
expect(size).toStrictEqual({ w: 81, h: 324 });
|
||||
});
|
||||
it("constrains width in large mode", () => {
|
||||
const size = suggestedSize(ImageSize.Large, { w: 2400, h: 1200 });
|
||||
expect(size).toStrictEqual({ w: 800, h: 400 });
|
||||
});
|
||||
it("returns max values if content size is not specified", () => {
|
||||
const size = suggestedSize(ImageSize.Normal, {});
|
||||
expect(size).toStrictEqual({ w: 324, h: 324 });
|
||||
});
|
||||
it("returns integer values", () => {
|
||||
const size = suggestedSize(ImageSize.Normal, { w: 642, h: 350 }); // does not divide evenly
|
||||
expect(size).toStrictEqual({ w: 324, h: 176 });
|
||||
});
|
||||
it("returns integer values for portrait images", () => {
|
||||
const size = suggestedSize(ImageSize.Normal, { w: 720, h: 1280 });
|
||||
expect(size).toStrictEqual({ w: 182, h: 324 });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import DeviceSettingsHandler from "../../../../src/settings/handlers/DeviceSettingsHandler";
|
||||
import { CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
|
||||
import { stubClient } from "../../../test-utils/test-utils";
|
||||
|
||||
describe("DeviceSettingsHandler", () => {
|
||||
const ROOM_ID_IS_UNUSED = "";
|
||||
|
||||
const unknownSettingKey = "unknown_setting";
|
||||
const featureKey = "my_feature";
|
||||
|
||||
let watchers: WatchManager;
|
||||
let handler: DeviceSettingsHandler;
|
||||
let settingListener: CallbackFn;
|
||||
|
||||
beforeEach(() => {
|
||||
watchers = new WatchManager();
|
||||
handler = new DeviceSettingsHandler([featureKey], watchers);
|
||||
settingListener = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
watchers.unwatchSetting(settingListener);
|
||||
});
|
||||
|
||||
it("Returns undefined for an unknown setting", () => {
|
||||
expect(handler.getValue(unknownSettingKey, ROOM_ID_IS_UNUSED)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("Returns the value for a disabled feature", () => {
|
||||
handler.setValue(featureKey, ROOM_ID_IS_UNUSED, false);
|
||||
expect(handler.getValue(featureKey, ROOM_ID_IS_UNUSED)).toBe(false);
|
||||
});
|
||||
|
||||
it("Returns the value for an enabled feature", () => {
|
||||
handler.setValue(featureKey, ROOM_ID_IS_UNUSED, true);
|
||||
expect(handler.getValue(featureKey, ROOM_ID_IS_UNUSED)).toBe(true);
|
||||
});
|
||||
|
||||
describe("If I am a guest", () => {
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
mocked(client.isGuest).mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
MatrixClientPeg.get = () => null;
|
||||
MatrixClientPeg.safeGet = () => new MatrixClient({ baseUrl: "foobar" });
|
||||
});
|
||||
|
||||
it("Returns the value for a disabled feature", () => {
|
||||
handler.setValue(featureKey, ROOM_ID_IS_UNUSED, false);
|
||||
expect(handler.getValue(featureKey, ROOM_ID_IS_UNUSED)).toBe(false);
|
||||
});
|
||||
|
||||
it("Returns the value for an enabled feature", () => {
|
||||
handler.setValue(featureKey, ROOM_ID_IS_UNUSED, true);
|
||||
expect(handler.getValue(featureKey, ROOM_ID_IS_UNUSED)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import RoomDeviceSettingsHandler from "../../../../src/settings/handlers/RoomDeviceSettingsHandler";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
|
||||
|
||||
describe("RoomDeviceSettingsHandler", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const value = "test value";
|
||||
const testSettings = [
|
||||
"RightPanel.phases",
|
||||
// special case in RoomDeviceSettingsHandler
|
||||
"blacklistUnverifiedDevices",
|
||||
];
|
||||
let watchers: WatchManager;
|
||||
let handler: RoomDeviceSettingsHandler;
|
||||
let settingListener: CallbackFn;
|
||||
|
||||
beforeEach(() => {
|
||||
watchers = new WatchManager();
|
||||
handler = new RoomDeviceSettingsHandler(watchers);
|
||||
settingListener = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
watchers.unwatchSetting(settingListener);
|
||||
});
|
||||
|
||||
it.each(testSettings)("should write/read/clear the value for »%s«", (setting: string): void => {
|
||||
// initial value should be null
|
||||
watchers.watchSetting(setting, roomId, settingListener);
|
||||
|
||||
expect(handler.getValue(setting, roomId)).toBeNull();
|
||||
|
||||
// set and read value
|
||||
handler.setValue(setting, roomId, value);
|
||||
expect(settingListener).toHaveBeenCalledWith(roomId, SettingLevel.ROOM_DEVICE, value);
|
||||
expect(handler.getValue(setting, roomId)).toEqual(value);
|
||||
|
||||
// clear value
|
||||
handler.setValue(setting, roomId, null);
|
||||
expect(settingListener).toHaveBeenCalledWith(roomId, SettingLevel.ROOM_DEVICE, null);
|
||||
expect(handler.getValue(setting, roomId)).toBeNull();
|
||||
});
|
||||
|
||||
it("canSetValue should return true", () => {
|
||||
expect(handler.canSetValue("test setting", roomId)).toBe(true);
|
||||
});
|
||||
});
|
159
test/unit-tests/settings/watchers/FontWatcher-test.tsx
Normal file
159
test/unit-tests/settings/watchers/FontWatcher-test.tsx
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 r00ster91 <r00ster91@proton.me>
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { FontWatcher } from "../../../../src/settings/watchers/FontWatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { untilDispatch } from "../../../test-utils";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
|
||||
async function setSystemFont(font: string | false): Promise<void> {
|
||||
await SettingsStore.setValue("systemFont", null, SettingLevel.DEVICE, font || "");
|
||||
await SettingsStore.setValue("useSystemFont", null, SettingLevel.DEVICE, !!font);
|
||||
await untilDispatch(Action.UpdateSystemFont);
|
||||
await sleep(1); // await the FontWatcher doing its action
|
||||
}
|
||||
|
||||
async function setUseBundledEmojiFont(use: boolean): Promise<void> {
|
||||
await SettingsStore.setValue("useBundledEmojiFont", null, SettingLevel.DEVICE, use);
|
||||
await untilDispatch(Action.UpdateSystemFont);
|
||||
await sleep(1); // await the FontWatcher doing its action
|
||||
}
|
||||
|
||||
const getFontFamily = () => {
|
||||
return document.body.style.getPropertyValue(FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY);
|
||||
};
|
||||
const getEmojiFontFamily = () => {
|
||||
return document.body.style.getPropertyValue(FontWatcher.EMOJI_FONT_FAMILY_CUSTOM_PROPERTY);
|
||||
};
|
||||
|
||||
describe("FontWatcher", function () {
|
||||
it("should load font on start()", async () => {
|
||||
const watcher = new FontWatcher();
|
||||
await setSystemFont("Font Name");
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""`);
|
||||
await watcher.start();
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Font Name", Twemoji"`);
|
||||
});
|
||||
|
||||
it("should load font on Action.OnLoggedIn", async () => {
|
||||
await setSystemFont("Font Name");
|
||||
await new FontWatcher().start();
|
||||
document.body.style.removeProperty(FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY); // clear the fontFamily which was by start which we tested already
|
||||
defaultDispatcher.fire(Action.OnLoggedIn, true);
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Font Name", Twemoji"`);
|
||||
});
|
||||
|
||||
it("should reset font on Action.OnLoggedOut", async () => {
|
||||
await setSystemFont("Font Name");
|
||||
const watcher = new FontWatcher();
|
||||
await watcher.start();
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Font Name", Twemoji"`);
|
||||
defaultDispatcher.fire(Action.OnLoggedOut, true);
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""`);
|
||||
});
|
||||
|
||||
describe("Sets font as expected", () => {
|
||||
let fontWatcher: FontWatcher;
|
||||
beforeEach(async () => {
|
||||
fontWatcher = new FontWatcher();
|
||||
await fontWatcher.start();
|
||||
});
|
||||
afterEach(() => {
|
||||
fontWatcher.stop();
|
||||
});
|
||||
|
||||
it("encloses the fonts by double quotes and sets them as the system font", async () => {
|
||||
await setSystemFont("Fira Sans Thin, Commodore 64");
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Fira Sans Thin","Commodore 64", Twemoji"`);
|
||||
});
|
||||
it("does not add double quotes if already present and sets the font as the system font", async () => {
|
||||
await setSystemFont(`"Commodore 64"`);
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Commodore 64", Twemoji"`);
|
||||
});
|
||||
it("trims whitespace, encloses the fonts by double quotes, and sets them as the system font", async () => {
|
||||
await setSystemFont(` Fira Code , "Commodore 64" `);
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Fira Code","Commodore 64", Twemoji"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sets bundled emoji font as expected", () => {
|
||||
let fontWatcher: FontWatcher;
|
||||
beforeEach(async () => {
|
||||
await setSystemFont(false);
|
||||
fontWatcher = new FontWatcher();
|
||||
await fontWatcher.start();
|
||||
});
|
||||
afterEach(() => {
|
||||
fontWatcher.stop();
|
||||
});
|
||||
|
||||
it("by default adds Twemoji font", async () => {
|
||||
expect(getEmojiFontFamily()).toMatchInlineSnapshot(`"Twemoji"`);
|
||||
});
|
||||
it("does not add Twemoji font when disabled", async () => {
|
||||
await setUseBundledEmojiFont(false);
|
||||
expect(getEmojiFontFamily()).toMatchInlineSnapshot(`""`);
|
||||
});
|
||||
it("works in conjunction with useSystemFont", async () => {
|
||||
await setSystemFont(`"Commodore 64"`);
|
||||
await setUseBundledEmojiFont(true);
|
||||
expect(getFontFamily()).toMatchInlineSnapshot(`""Commodore 64", Twemoji"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Migrates baseFontSize", () => {
|
||||
let watcher: FontWatcher | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
document.documentElement.style.fontSize = "14px";
|
||||
watcher = new FontWatcher();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
watcher!.stop();
|
||||
});
|
||||
|
||||
it("should not run the migration", async () => {
|
||||
await watcher!.start();
|
||||
expect(SettingsStore.getValue("fontSizeDelta")).toBe(0);
|
||||
});
|
||||
|
||||
it("should migrate from V1 font size to V3", async () => {
|
||||
await SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, 13);
|
||||
await watcher!.start();
|
||||
// 13px (V1 font size) + 5px (V1 offset) + 1px (root font size increase) - 14px (default browser font size) = 5px
|
||||
expect(SettingsStore.getValue("fontSizeDelta")).toBe(5);
|
||||
// baseFontSize should be cleared
|
||||
expect(SettingsStore.getValue("baseFontSize")).toBe(0);
|
||||
});
|
||||
|
||||
it("should migrate from V2 font size to V3 using browser font size", async () => {
|
||||
await SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, 18);
|
||||
await watcher!.start();
|
||||
// 18px - 14px (default browser font size) = 2px
|
||||
expect(SettingsStore.getValue("fontSizeDelta")).toBe(4);
|
||||
// baseFontSize should be cleared
|
||||
expect(SettingsStore.getValue("baseFontSizeV2")).toBe(0);
|
||||
});
|
||||
|
||||
it("should migrate from V2 font size to V3 using fallback font size", async () => {
|
||||
document.documentElement.style.fontSize = "";
|
||||
await SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, 18);
|
||||
await watcher!.start();
|
||||
// 18px - 16px (fallback) = 2px
|
||||
expect(SettingsStore.getValue("fontSizeDelta")).toBe(2);
|
||||
// baseFontSize should be cleared
|
||||
expect(SettingsStore.getValue("baseFontSizeV2")).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
187
test/unit-tests/settings/watchers/ThemeWatcher-test.tsx
Normal file
187
test/unit-tests/settings/watchers/ThemeWatcher-test.tsx
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import ThemeWatcher from "../../../../src/settings/watchers/ThemeWatcher";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
||||
function makeMatchMedia(values: any) {
|
||||
class FakeMediaQueryList {
|
||||
matches: false;
|
||||
media?: null;
|
||||
onchange?: null;
|
||||
addListener() {}
|
||||
removeListener() {}
|
||||
addEventListener() {}
|
||||
removeEventListener() {}
|
||||
dispatchEvent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor(query: string) {
|
||||
this.matches = values[query];
|
||||
}
|
||||
}
|
||||
|
||||
return function matchMedia(query: string) {
|
||||
return new FakeMediaQueryList(query) as unknown as MediaQueryList;
|
||||
};
|
||||
}
|
||||
|
||||
function makeGetValue(values: any) {
|
||||
return function getValue<T = any>(settingName: string, _roomId: string | null = null, _excludeDefault = false): T {
|
||||
return values[settingName];
|
||||
};
|
||||
}
|
||||
|
||||
function makeGetValueAt(values: any) {
|
||||
return function getValueAt(
|
||||
_level: SettingLevel,
|
||||
settingName: string,
|
||||
_roomId: string | null = null,
|
||||
_explicit = false,
|
||||
_excludeDefault = false,
|
||||
): any {
|
||||
return values[settingName];
|
||||
};
|
||||
}
|
||||
|
||||
describe("ThemeWatcher", function () {
|
||||
it("should choose a light theme by default", () => {
|
||||
// Given no system settings
|
||||
global.matchMedia = makeMatchMedia({});
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it("should choose default theme if system settings are inconclusive", () => {
|
||||
// Given no system settings but we asked to use them
|
||||
global.matchMedia = makeMatchMedia({});
|
||||
SettingsStore.getValue = makeGetValue({
|
||||
use_system_theme: true,
|
||||
theme: "light",
|
||||
});
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it("should choose a dark theme if that is selected", () => {
|
||||
// Given system says light high contrast but theme is set to dark
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: light)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ theme: "dark" });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it("should choose a light theme if that is selected", () => {
|
||||
// Given system settings say dark high contrast but theme set to light
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: dark)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ theme: "light" });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it("should choose a light-high-contrast theme if that is selected", () => {
|
||||
// Given system settings say dark and theme set to light-high-contrast
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ theme: "light-high-contrast" });
|
||||
|
||||
// Then getEffectiveTheme returns light-high-contrast
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
|
||||
});
|
||||
|
||||
it("should choose a light theme if system prefers it (via default)", () => {
|
||||
// Given system prefers lightness, even though we did not
|
||||
// click "Use system theme" or choose a theme explicitly
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({});
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it("should choose a dark theme if system prefers it (via default)", () => {
|
||||
// Given system prefers darkness, even though we did not
|
||||
// click "Use system theme" or choose a theme explicitly
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({});
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it("should choose a light theme if system prefers it (explicit)", () => {
|
||||
// Given system prefers lightness
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it("should choose a dark theme if system prefers it (explicit)", () => {
|
||||
// Given system prefers darkness
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it("should choose a high-contrast theme if system prefers it", () => {
|
||||
// Given system prefers high contrast and light
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: light)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns light-high-contrast
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
|
||||
});
|
||||
|
||||
it("should not choose a high-contrast theme if not available", () => {
|
||||
// Given system prefers high contrast and dark, but we don't (yet)
|
||||
// have a high-contrast dark theme
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: dark)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: true });
|
||||
SettingsStore.getValue = makeGetValue({ use_system_theme: true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue