diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 6799a593e9..36030713ec 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -430,7 +430,7 @@ export const SETTINGS: Settings = { }, "mediaPreviewConfig": { controller: new MediaPreviewConfigController(), - supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + supportedLevels: LEVELS_ROOM_SETTINGS, default: MediaPreviewConfigController.default, }, "feature_report_to_moderators": { diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts index cdda7b7dea..21e2b3c15b 100644 --- a/test/test-utils/client.ts +++ b/test/test-utils/client.ts @@ -62,7 +62,11 @@ export class MockClientWithEventEmitter extends EventEmitter { export const getMockClientWithEventEmitter = ( mockProperties: Partial>, ): MockedObject => { - const mock = mocked(new MockClientWithEventEmitter(mockProperties) as unknown as MatrixClient); + const mock = mocked( + new MockClientWithEventEmitter({ + ...mockProperties, + }) as unknown as MatrixClient, + ); jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mock); jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mock); diff --git a/test/unit-tests/HtmlUtils-test.tsx b/test/unit-tests/HtmlUtils-test.tsx index 6a28fbad6b..ef458d4330 100644 --- a/test/unit-tests/HtmlUtils-test.tsx +++ b/test/unit-tests/HtmlUtils-test.tsx @@ -14,6 +14,7 @@ import parse from "html-react-parser"; import { bodyToHtml, bodyToNode, formatEmojis, topicToHtml } from "../../src/HtmlUtils"; import SettingsStore from "../../src/settings/SettingsStore"; import { getMockClientWithEventEmitter } from "../test-utils"; +import { SettingLevel } from "../../src/settings/SettingLevel"; jest.mock("../../src/settings/SettingsStore"); @@ -108,7 +109,11 @@ describe("bodyToHtml", () => { describe("feature_latex_maths", () => { beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature === "feature_latex_maths"); + SettingsStore.setValue("feature_latex_maths", null, SettingLevel.ACCOUNT, true); + }); + + afterEach(() => { + SettingsStore.reset(); }); it("should render inline katex", () => { diff --git a/test/unit-tests/components/structures/TimelinePanel-test.tsx b/test/unit-tests/components/structures/TimelinePanel-test.tsx index 886fe777e4..0a577729db 100644 --- a/test/unit-tests/components/structures/TimelinePanel-test.tsx +++ b/test/unit-tests/components/structures/TimelinePanel-test.tsx @@ -50,6 +50,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore"; import ScrollPanel from "../../../../src/components/structures/ScrollPanel"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../src/dispatcher/actions"; +import { SettingLevel } from "../../../../src/settings/SettingLevel"; // ScrollPanel calls this, but jsdom doesn't mock it for us HTMLDivElement.prototype.scrollBy = () => {}; @@ -312,16 +313,11 @@ describe("TimelinePanel", () => { beforeEach(async () => { client.isVersionSupported.mockResolvedValue(true); client.doesServerSupportUnstableFeature.mockResolvedValue(true); - - jest.spyOn(SettingsStore, "getValue").mockImplementation((setting: string): any => { - if (setting === "sendReadReceipts") return false; - - return undefined; - }); + SettingsStore.setValue("sendReadReceipts", null, SettingLevel.ACCOUNT, false); }); afterEach(() => { - mocked(SettingsStore.getValue).mockReset(); + SettingsStore.reset(); }); it("should send a fully read marker and a private receipt", async () => { diff --git a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx index 80ffe0d7e5..9ef692e7f8 100644 --- a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx +++ b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx @@ -17,12 +17,12 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import { LocalRoom } from "../../../../../src/models/LocalRoom"; import * as AvatarModule from "../../../../../src/Avatar"; import { DirectoryMember } from "../../../../../src/utils/direct-messages"; -import SettingsStore from "../../../../../src/settings/SettingsStore"; import { MediaPreviewValue } from "../../../../../src/@types/media_preview"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; +import { SettingLevel } from "../../../../../src/settings/SettingLevel"; describe("RoomAvatar", () => { let client: MatrixClient; - let showAvatarsSetting: MediaPreviewValue.On | MediaPreviewValue.Off = MediaPreviewValue.On; filterConsole( // unrelated for this test @@ -35,16 +35,15 @@ describe("RoomAvatar", () => { jest.spyOn(dmRoomMap, "getUserIdForRoomId"); jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap); jest.spyOn(AvatarModule, "defaultAvatarUrlForString"); - const origFn = SettingsStore.getValue; - jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, ...args) => { - if (setting === "mediaPreviewConfig") { - return { invite_avatars: showAvatarsSetting, media_previews: MediaPreviewValue.Off }; - } - return origFn(setting, ...args); - }); }); afterAll(() => { + SettingsStore.setValue( + "mediaPreviewConfig", + null, + SettingLevel.ACCOUNT, + SettingsStore.getDefaultValue("mediaPreviewConfig"), + ); jest.restoreAllMocks(); }); @@ -82,7 +81,9 @@ describe("RoomAvatar", () => { expect(render().container).toMatchSnapshot(); }); it("should not render an invite avatar if the user has disabled it", () => { - showAvatarsSetting = MediaPreviewValue.Off; + SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, { + invite_avatars: MediaPreviewValue.Off, + }); const room = new Room("!room:example.com", client, client.getSafeUserId()); room.name = "test room"; room.updateMyMembership("invite"); diff --git a/test/unit-tests/components/views/messages/MImageBody-test.tsx b/test/unit-tests/components/views/messages/MImageBody-test.tsx index c41696d8f5..8a918ea788 100644 --- a/test/unit-tests/components/views/messages/MImageBody-test.tsx +++ b/test/unit-tests/components/views/messages/MImageBody-test.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { act } from "react"; +import React from "react"; import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react"; import { EventType, getHttpUriForMxc, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import fetchMock from "fetch-mock-jest"; @@ -28,7 +28,6 @@ import { } from "../../../../test-utils"; import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper"; import SettingsStore from "../../../../../src/settings/SettingsStore"; -import { SettingLevel } from "../../../../../src/settings/SettingLevel"; import { MediaPreviewValue } from "../../../../../src/@types/media_preview"; jest.mock("matrix-encrypt-attachment", () => ({ @@ -88,6 +87,7 @@ describe("", () => { }); afterEach(() => { + SettingsStore.reset(); mocked(encrypt.decryptAttachment).mockReset(); }); @@ -148,17 +148,6 @@ describe("", () => { }); }); - afterEach(() => { - act(() => { - SettingsStore.setValue( - "showMediaEventIds", - null, - SettingLevel.DEVICE, - SettingsStore.getDefaultValue("showMediaEventIds"), - ); - }); - }); - it("should not download image", async () => { fetchMock.getOnce(url, { status: 200 }); diff --git a/test/unit-tests/components/views/messages/MVideoBody-test.tsx b/test/unit-tests/components/views/messages/MVideoBody-test.tsx index 158b19259f..d5dcfca59b 100644 --- a/test/unit-tests/components/views/messages/MVideoBody-test.tsx +++ b/test/unit-tests/components/views/messages/MVideoBody-test.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { act } from "react"; +import React from "react"; import { EventType, getHttpUriForMxc, type IContent, type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { fireEvent, render, screen, type RenderResult } from "jest-matrix-react"; import fetchMock from "fetch-mock-jest"; @@ -25,7 +25,6 @@ import { } from "../../../../test-utils"; import MVideoBody from "../../../../../src/components/views/messages/MVideoBody"; import type { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps"; -import { SettingLevel } from "../../../../../src/settings/SettingLevel"; import SettingsStore from "../../../../../src/settings/SettingsStore"; import { MediaPreviewValue } from "../../../../../src/@types/media_preview"; @@ -70,6 +69,7 @@ describe("MVideoBody", () => { room_id: "!room:server", sender: userId, type: EventType.RoomMessage, + event_id: "$foo:bar", content: { body: "alt for a test video", info: { @@ -114,14 +114,7 @@ describe("MVideoBody", () => { }); afterEach(() => { - act(() => { - SettingsStore.setValue( - "showMediaEventIds", - null, - SettingLevel.DEVICE, - SettingsStore.getDefaultValue("showMediaEventIds"), - ); - }); + SettingsStore.reset(); jest.restoreAllMocks(); }); diff --git a/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx index 846adc8ab4..a68e7fd46f 100644 --- a/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx @@ -57,6 +57,7 @@ import * as UseCall from "../../../../../../src/hooks/useCall"; import { SdkContextClass } from "../../../../../../src/contexts/SDKContext"; import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore"; import { UIFeature } from "../../../../../../src/settings/UIFeature"; +import { SettingLevel } from "../../../../../../src/settings/SettingLevel"; jest.mock("../../../../../../src/utils/ShieldUtils"); jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({ @@ -99,6 +100,7 @@ describe("RoomHeader", () => { afterEach(() => { jest.restoreAllMocks(); + SettingsStore.reset(); }); it("renders the room header", () => { @@ -187,9 +189,7 @@ describe("RoomHeader", () => { it("opens the notifications panel", async () => { const user = userEvent.setup(); - jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => { - if (name === "feature_notifications") return true; - }); + SettingsStore.setValue("feature_notifications", null, SettingLevel.DEVICE, true); render(, getWrapper()); @@ -228,7 +228,15 @@ describe("RoomHeader", () => { describe("UIFeature.Widgets enabled (default)", () => { beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets); + SdkConfig.put({ + features: { + [UIFeature.Widgets]: true, + }, + }); + }); + + afterEach(() => { + SdkConfig.reset(); }); it("should show call buttons in a room with 2 members", () => { @@ -248,7 +256,15 @@ describe("RoomHeader", () => { describe("UIFeature.Widgets disabled", () => { beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => false); + SdkConfig.put({ + features: { + [UIFeature.Widgets]: true, + }, + }); + }); + + afterEach(() => { + SdkConfig.reset(); }); it("should show call buttons in a room with 2 members", () => { @@ -268,7 +284,15 @@ describe("RoomHeader", () => { describe("groups call disabled", () => { beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets); + SdkConfig.put({ + features: { + [UIFeature.Widgets]: true, + }, + }); + }); + + afterEach(() => { + SdkConfig.reset(); }); it("you can't call if you're alone", () => { @@ -333,15 +357,26 @@ describe("RoomHeader", () => { describe("group call enabled", () => { beforeEach(() => { - jest.spyOn(SettingsStore, "getValue").mockImplementation( - (feature) => feature === "feature_group_calls" || feature == UIFeature.Widgets, - ); + SdkConfig.put({ + features: { + [UIFeature.Widgets]: true, + feature_group_calls: true, + }, + }); + }); + + afterEach(() => { + SdkConfig.reset(); }); it("renders only the video call element", async () => { const user = userEvent.setup(); mockRoomMembers(room, 3); - jest.spyOn(SdkConfig, "get").mockReturnValue({ use_exclusively: true }); + SdkConfig.put({ + features: { + use_exclusively: true, + }, + }); // allow element calls jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); @@ -359,7 +394,11 @@ describe("RoomHeader", () => { }); it("can't call if there's an ongoing (pinned) call", () => { - jest.spyOn(SdkConfig, "get").mockReturnValue({ use_exclusively: true }); + SdkConfig.put({ + features: { + use_exclusively: true, + }, + }); // allow element calls jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true); @@ -377,7 +416,11 @@ describe("RoomHeader", () => { it("clicking on ongoing (unpinned) call re-pins it", async () => { const user = userEvent.setup(); mockRoomMembers(room, 3); - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets); + SdkConfig.put({ + features: { + [UIFeature.Widgets]: true, + }, + }); // allow calls jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false); @@ -442,8 +485,10 @@ describe("RoomHeader", () => { jest.spyOn(room.currentState, "maySendStateEvent").mockReturnValue(false); jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite); jest.spyOn(room, "canInvite").mockReturnValue(false); - jest.spyOn(SdkConfig, "get").mockImplementation((key) => { - return { guest_spa_url: "https://guest_spa_url.com", url: "https://spa_url.com" }; + SdkConfig.put({ + element_call: { + guest_spa_url: "https://guest_spa_url.com", + }, }); const { container: containerNoInviteNotPublic } = render(, getWrapper()); expect(queryAllByLabelText(containerNoInviteNotPublic, "There's no one here to call")).toHaveLength(2); @@ -751,7 +796,7 @@ describe("RoomHeader", () => { describe("ask to join enabled", () => { it("does render the RoomKnocksBar", () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature === "feature_ask_to_join"); + SettingsStore.setValue("feature_ask_to_join", null, SettingLevel.DEVICE, true); jest.spyOn(room, "canInvite").mockReturnValue(true); jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock); jest.spyOn(room, "getMembersWithMembership").mockReturnValue([new RoomMember(room.roomId, "@foo")]); diff --git a/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx index 54d8bd7ac0..f4efc39e1f 100644 --- a/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx @@ -37,6 +37,10 @@ describe("NotificatinSettingsTab", () => { NotificationSettingsTab.contextType = React.createContext(cli); }); + afterEach(() => { + SettingsStore.reset(); + }); + it("should prevent »Settings« link click from bubbling up to radio buttons", async () => { const tab = renderTab(); diff --git a/test/unit-tests/hooks/useMediaVisible-test.tsx b/test/unit-tests/hooks/useMediaVisible-test.tsx index 764d473ee1..e86d0c9986 100644 --- a/test/unit-tests/hooks/useMediaVisible-test.tsx +++ b/test/unit-tests/hooks/useMediaVisible-test.tsx @@ -9,10 +9,10 @@ import { act, renderHook, waitFor } from "jest-matrix-react"; import { JoinRule, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; import { useMediaVisible } from "../../../src/hooks/useMediaVisible"; -import SettingsStore from "../../../src/settings/SettingsStore"; import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../test-utils"; import { type MediaPreviewConfig, MediaPreviewValue } from "../../../src/@types/media_preview"; import MediaPreviewConfigController from "../../../src/settings/controllers/MediaPreviewConfigController"; +import SettingsStore from "../../../src/settings/SettingsStore"; const EVENT_ID = "$fibble:example.org"; const ROOM_ID = "!foobar:example.org"; diff --git a/test/unit-tests/utils/notifications-test.ts b/test/unit-tests/utils/notifications-test.ts index 2a761f7670..2c603ce1b1 100644 --- a/test/unit-tests/utils/notifications-test.ts +++ b/test/unit-tests/utils/notifications-test.ts @@ -33,6 +33,7 @@ import { getMockClientWithEventEmitter } from "../../test-utils/client"; import { mkMessage, stubClient } from "../../test-utils/test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { NotificationLevel } from "../../../src/stores/notifications/NotificationLevel"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; jest.mock("../../../src/settings/SettingsStore"); @@ -116,7 +117,11 @@ describe("notifications", () => { const ROOM_ID = "123"; const USER_ID = "@bob:example.org"; let message: MatrixEvent; - let sendReceiptsSetting = true; + const sendReceiptsSetting = true; + + afterEach(() => { + SettingsStore.reset(); + }); beforeEach(() => { stubClient(); @@ -131,9 +136,7 @@ describe("notifications", () => { room.addLiveEvents([message], { addToState: true }); sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({}); jest.spyOn(client, "getRooms").mockReturnValue([room]); - jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => { - return name === "sendReadReceipts" && sendReceiptsSetting; - }); + SettingsStore.setValue("sendReadReceipts", null, SettingLevel.ACCOUNT, sendReceiptsSetting); }); it("sends a request even if everything has been read", async () => { @@ -152,11 +155,8 @@ describe("notifications", () => { }); describe("when sendReadReceipts setting is disabled", () => { - beforeEach(() => { - sendReceiptsSetting = false; - }); - it("should send a private read receipt", async () => { + SettingsStore.setValue("sendReadReceipts", null, SettingLevel.ACCOUNT, sendReceiptsSetting); await clearRoomNotification(room, client); expect(sendReadReceiptSpy).toHaveBeenCalledWith(message, ReceiptType.ReadPrivate, true); }); @@ -177,9 +177,11 @@ describe("notifications", () => { room = new Room(ROOM_ID, client, USER_ID); sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({}); jest.spyOn(client, "getRooms").mockReturnValue([room]); - jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => { - return name === "sendReadReceipts"; - }); + SettingsStore.setValue("sendReadReceipts", null, SettingLevel.ACCOUNT, true); + }); + + afterEach(() => { + SettingsStore.reset(); }); it("does not send any requests if everything has been read", () => { @@ -212,7 +214,7 @@ describe("notifications", () => { room.addLiveEvents([message], { addToState: true }); room.setUnreadNotificationCount(NotificationCountType.Total, 1); - jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false); + SettingsStore.setValue("sendReadReceipts", null, SettingLevel.ACCOUNT, false); await clearAllNotifications(client);