From 5ff302539e48df30afda02339332b08223564d7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Apr 2026 09:46:09 +0100 Subject: [PATCH] Harden settings types (#33311) * Harden settings types * Fix types * Update apps/web/src/emojipicker/recent.ts Co-authored-by: Will Hunt <2072976+Half-Shot@users.noreply.github.com> * Fix suggestion --------- Co-authored-by: Will Hunt <2072976+Half-Shot@users.noreply.github.com> --- apps/web/src/@types/media_preview.ts | 4 +- apps/web/src/LegacyCallHandler.tsx | 2 +- apps/web/src/Notifier.ts | 17 ++++-- apps/web/src/PosthogAnalytics.ts | 6 +- .../eventindex/ManageEventIndexDialog.tsx | 2 +- .../src/components/structures/MatrixChat.tsx | 4 +- .../views/settings/LayoutSwitcher.tsx | 4 +- .../tabs/user/PreferencesUserSettingsTab.tsx | 6 +- .../web/src/device-listener/DeviceListener.ts | 2 +- .../payloads/SettingUpdatedPayload.ts | 22 +++++-- apps/web/src/emojipicker/recent.ts | 4 +- apps/web/src/mjolnir/Mjolnir.ts | 15 ++--- apps/web/src/settings/Settings.tsx | 11 +--- apps/web/src/settings/SettingsStore.ts | 60 ++++++++++--------- .../MediaPreviewConfigController.ts | 4 +- .../right-panel/RightPanelStoreIPanelState.ts | 4 +- .../web/src/stores/room-list/RoomListStore.ts | 2 +- .../message-body/EventContentBodyViewModel.ts | 4 +- .../message-body/RedactedBodyViewModel.ts | 2 +- .../message-body/UrlPreviewGroupViewModel.ts | 2 +- .../room/timeline/DateSeparatorViewModel.tsx | 4 +- .../components/structures/MatrixChat-test.tsx | 2 +- .../views/dialogs/UserSettingsDialog-test.tsx | 2 +- .../stores/ReleaseAnnouncementStore-test.tsx | 2 +- .../stores/room-list/RoomListStore-test.ts | 12 ++-- .../room-list/RoomListItemViewModel-test.tsx | 2 +- 26 files changed, 104 insertions(+), 97 deletions(-) diff --git a/apps/web/src/@types/media_preview.ts b/apps/web/src/@types/media_preview.ts index d340e64caf..ffce5944ee 100644 --- a/apps/web/src/@types/media_preview.ts +++ b/apps/web/src/@types/media_preview.ts @@ -25,9 +25,9 @@ export interface MediaPreviewConfig extends Record { /** * Media preview setting for thumbnails of media in rooms. */ - media_previews: MediaPreviewValue; + media_previews?: MediaPreviewValue; /** * Media preview settings for avatars of rooms we have been invited to. */ - invite_avatars: MediaPreviewValue.On | MediaPreviewValue.Off; + invite_avatars?: MediaPreviewValue.On | MediaPreviewValue.Off; } diff --git a/apps/web/src/LegacyCallHandler.tsx b/apps/web/src/LegacyCallHandler.tsx index 81fb0093d0..3ccf500b26 100644 --- a/apps/web/src/LegacyCallHandler.tsx +++ b/apps/web/src/LegacyCallHandler.tsx @@ -725,7 +725,7 @@ export default class LegacyCallHandler extends TypedEventEmitter { - SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow); + SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow ?? null); }); } diff --git a/apps/web/src/Notifier.ts b/apps/web/src/Notifier.ts index b0a2ce5b42..b7e2144132 100644 --- a/apps/web/src/Notifier.ts +++ b/apps/web/src/Notifier.ts @@ -142,6 +142,16 @@ interface EmittedEvents { [NotifierEvent.NotificationHiddenChange]: (hidden: boolean) => void; } +/** + * Type representing a notification sound setting + */ +export type NotificationSound = { + url: string; + name?: string; + type?: string; + size?: number; +}; + class NotifierClass extends TypedEventEmitter { private notifsByRoom: Record = {}; @@ -223,12 +233,7 @@ class NotifierClass extends TypedEventEmitter): void => { this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) }); - SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; public render(): React.ReactNode { diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index af4fe18a9f..0dbc2af2e4 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -1794,11 +1794,11 @@ export default class MatrixChat extends React.PureComponent { SettingsStore.watchSetting( "blacklistUnverifiedDevices", null, - (_settingName, _roomId, atLevel, blacklistEnabled: boolean) => { + (_settingName, _roomId, atLevel, blacklistEnabled) => { if (atLevel != SettingLevel.DEVICE) { return; } - crypto.globalBlacklistUnverifiedDevices = blacklistEnabled; + crypto.globalBlacklistUnverifiedDevices = !!blacklistEnabled; }, ); } diff --git a/apps/web/src/components/views/settings/LayoutSwitcher.tsx b/apps/web/src/components/views/settings/LayoutSwitcher.tsx index 9df3968859..5997c95707 100644 --- a/apps/web/src/components/views/settings/LayoutSwitcher.tsx +++ b/apps/web/src/components/views/settings/LayoutSwitcher.tsx @@ -38,8 +38,8 @@ function LayoutSelector(): JSX.Element { { - // We don't have any file in the form, we can cast it as string safely - const newLayout = new FormData(evt.currentTarget).get("layout") as string | null; + // We don't have any file in the form, we can cast it as Layout safely + const newLayout = new FormData(evt.currentTarget).get("layout") as Layout; await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, newLayout); }} > diff --git a/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 51ccb7bd3e..84febf8e5d 100644 --- a/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -211,17 +211,17 @@ export default class PreferencesUserSettingsTab extends React.Component): void => { this.setState({ autocompleteDelay: e.target.value }); - SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerInViewThresholdMs: e.target.value }); - SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerOutOfViewThresholdMs: e.target.value }); - SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): JSX.Element { diff --git a/apps/web/src/device-listener/DeviceListener.ts b/apps/web/src/device-listener/DeviceListener.ts index 4d62bf0ab5..fea00c0380 100644 --- a/apps/web/src/device-listener/DeviceListener.ts +++ b/apps/web/src/device-listener/DeviceListener.ts @@ -340,7 +340,7 @@ export class DeviceListener { }); } - private onRecordClientInformationSettingChange: CallbackFn = ( + private onRecordClientInformationSettingChange: CallbackFn<"deviceClientInformationOptIn"> = ( _originalSettingName, _roomId, _level, diff --git a/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts b/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts index ed84c2b18e..51844faa16 100644 --- a/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts +++ b/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts @@ -9,14 +9,26 @@ Please see LICENSE files in the repository root for full details. import { type ActionPayload } from "../payloads"; import { type Action } from "../actions"; import { type SettingLevel } from "../../settings/SettingLevel"; -import { type SettingValueType } from "../../settings/Settings"; +import { type SettingKey, type Settings } from "../../settings/Settings"; -export interface SettingUpdatedPayload extends ActionPayload { +export interface SettingUpdatedPayload extends ActionPayload { action: Action.SettingUpdated; - settingName: string; + settingName: S; roomId: string | null; level: SettingLevel; - newValueAtLevel: SettingLevel; - newValue: SettingValueType; + newValueAtLevel: Settings[S]["default"]; + newValue: Settings[S]["default"]; +} + +/** + * Type guard to check if a payload is a SettingUpdatedPayload for a specific setting. + * @param payload the payload to assert + * @param settingName the setting name to check for + */ +export function isSettingUpdatedPayload( + payload: SettingUpdatedPayload, + settingName: S, +): payload is SettingUpdatedPayload { + return payload.settingName === settingName; } diff --git a/apps/web/src/emojipicker/recent.ts b/apps/web/src/emojipicker/recent.ts index 05c1321716..42b3c4b276 100644 --- a/apps/web/src/emojipicker/recent.ts +++ b/apps/web/src/emojipicker/recent.ts @@ -29,7 +29,7 @@ const STORAGE_LIMIT = 100; function migrate(): void { const data: ILegacyFormat = JSON.parse(window.localStorage.mx_reaction_count || "{}"); const sorted = Object.entries(data).sort(([, [count1, date1]], [, [count2, date2]]) => date2 - date1); - const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]); + const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]); SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, newFormat.slice(0, STORAGE_LIMIT)); } @@ -41,7 +41,7 @@ export function add(emoji: string): void { const recents = getRecentEmoji(); const i = recents.findIndex(([e]) => e === emoji); - let newEntry; + let newEntry: RecentEmojiData[number]; if (i >= 0) { // first remove the existing tuple so that we can increment it and push it to the front [newEntry] = recents.splice(i, 1); diff --git a/apps/web/src/mjolnir/Mjolnir.ts b/apps/web/src/mjolnir/Mjolnir.ts index be4acf5965..63059b3c04 100644 --- a/apps/web/src/mjolnir/Mjolnir.ts +++ b/apps/web/src/mjolnir/Mjolnir.ts @@ -11,7 +11,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { ALL_RULE_TYPES, BanList } from "./BanList"; -import SettingsStore from "../settings/SettingsStore"; +import SettingsStore, { type CallbackFn } from "../settings/SettingsStore"; import { _t } from "../languageHandler"; import dis from "../dispatcher/dispatcher"; import { SettingLevel } from "../settings/SettingLevel"; @@ -38,7 +38,7 @@ export class Mjolnir { } public start(): void { - this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this)); + this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged); this.dispatcherRef = dis.register(this.onAction); dis.dispatch>({ @@ -130,15 +130,10 @@ export class Mjolnir { this.updateLists(this._roomIds); }; - private onListsChanged( - settingName: string, - roomId: string | null, - atLevel: SettingLevel, - newValue: string[], - ): void { + private onListsChanged: CallbackFn<"mjolnirRooms"> = (settingName, roomId, atLevel, newValue): void => { // We know that ban lists are only recorded at one level so we don't need to re-eval them - this.updateLists(newValue); - } + this.updateLists(newValue ?? []); + }; private updateLists(listRoomIds: string[]): void { if (!MatrixClientPeg.get()) return; diff --git a/apps/web/src/settings/Settings.tsx b/apps/web/src/settings/Settings.tsx index 0cd889bb3c..ba6b84e9c1 100644 --- a/apps/web/src/settings/Settings.tsx +++ b/apps/web/src/settings/Settings.tsx @@ -54,6 +54,7 @@ import { type ComputedInviteConfig } from "../@types/invite-rules.ts"; import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts"; import RequiresSettingsController from "./controllers/RequiresSettingsController.ts"; import { type OrderedCustomSections, type CustomSectionsData } from "../stores/room-list-v3/section.ts"; +import { type NotificationSound } from "../Notifier.ts"; export const defaultWatchManager = new WatchManager(); @@ -311,15 +312,7 @@ export interface Settings { "urlPreviewsEnabled_e2ee": IBaseSetting; "notificationsEnabled": IBaseSetting; "deviceNotificationsEnabled": IBaseSetting; - "notificationSound": IBaseSetting< - | { - name: string; - type: string; - size: number; - url: string; - } - | false - >; + "notificationSound": IBaseSetting; "notificationBodyEnabled": IBaseSetting; "audioNotificationsEnabled": IBaseSetting; "enableWidgetScreenshots": IBaseSetting; diff --git a/apps/web/src/settings/SettingsStore.ts b/apps/web/src/settings/SettingsStore.ts index c2f0eb98c8..10100a1af6 100644 --- a/apps/web/src/settings/SettingsStore.ts +++ b/apps/web/src/settings/SettingsStore.ts @@ -79,7 +79,7 @@ export const LEVEL_ORDER = [ SettingLevel.DEFAULT, ]; -function getLevelOrder(setting: ISetting): SettingLevel[] { +function getLevelOrder(setting: Settings[keyof Settings]): SettingLevel[] { // Settings which support only a single setting level are inherently ordered if (setting.supportedLevelsAreOrdered || setting.supportedLevels.length === 1) { // return a copy to prevent callers from modifying the array @@ -88,12 +88,12 @@ function getLevelOrder(setting: ISetting): SettingLevel[] { return LEVEL_ORDER; } -export type CallbackFn = ( - settingName: SettingKey, +export type CallbackFn = ( + settingName: S, roomId: string | null, atLevel: SettingLevel, - newValAtLevel: any, - newVal: any, + newValAtLevel: Settings[S]["default"] | null, + newVal: Settings[S]["default"] | null, ) => void; type HandlerMap = Partial<{ @@ -167,7 +167,11 @@ export default class SettingsStore { * if the change in value is worthwhile enough to react upon. * @returns {string} A reference to the watcher that was employed. */ - public static watchSetting(settingName: SettingKey, roomId: string | null, callbackFn: CallbackFn): string { + public static watchSetting( + settingName: S, + roomId: string | null, + callbackFn: CallbackFn, + ): string { const setting = SETTINGS[settingName]; if (!setting) throw new Error(`${settingName} is not a setting`); @@ -175,7 +179,11 @@ export default class SettingsStore { const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${finalSettingName}_${roomId}`; - const localizedCallback = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any): void => { + const localizedCallback = ( + changedInRoomId: string | null, + atLevel: SettingLevel, + newValAtLevel: Settings[S]["default"], + ): void => { if (!SettingsStore.doesSettingSupportLevel(settingName, atLevel)) { logger.warn( `Setting handler notified for an update of an invalid setting level: ` + @@ -220,7 +228,7 @@ export default class SettingsStore { * @param {string} settingName The setting name to monitor. * @param {String} roomId The room ID to monitor for changes in. Use null for all rooms. */ - public static monitorSetting(settingName: SettingKey, roomId: string | null): void { + public static monitorSetting(settingName: S, roomId: string | null): void { roomId = roomId || null; // the thing wants null specifically to work, so appease it. if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); @@ -228,7 +236,7 @@ export default class SettingsStore { const registerWatcher = (): void => { this.monitors.get(settingName)!.set( roomId, - SettingsStore.watchSetting( + SettingsStore.watchSetting( settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => { @@ -449,11 +457,10 @@ export default class SettingsStore { /** * Gets the default value of a setting. - * @param {string} settingName The name of the setting to read the value of. - * @param {String} roomId The room ID to read the setting value in, may be null. - * @return {*} The default value + * @param settingName The name of the setting to read the value of. + * @return The default value */ - public static getDefaultValue(settingName: SettingKey): any { + public static getDefaultValue(settingName: S): Settings[S]["default"] { // Verify that the setting is actually a setting if (!SETTINGS[settingName]) { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); @@ -462,13 +469,13 @@ export default class SettingsStore { return SETTINGS[settingName].default; } - private static getFinalValue( - setting: ISetting, + private static getFinalValue( + setting: Settings[S], level: SettingLevel, roomId: string | null, - calculatedValue: any, + calculatedValue: Settings[S]["default"], calculatedAtLevel: SettingLevel | null, - ): any { + ): Settings[S]["default"] { let resultingValue = calculatedValue; if (setting.controller) { @@ -480,25 +487,22 @@ export default class SettingsStore { return resultingValue; } - /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 /** * Sets the value for a setting. The room ID is optional if the setting is not being * set for a particular room, otherwise it should be supplied. The value may be null * to indicate that the level should no longer have an override. - * @param {string} settingName The name of the setting to change. - * @param {String} roomId The room ID to change the value in, may be null. - * @param {SettingLevel} level The level + * @param settingName The name of the setting to change. + * @param roomId The room ID to change the value in, may be null. + * @param level The level * to change the value at. - * @param {*} value The new value of the setting, may be null. - * @return {Promise} Resolves when the setting has been changed. + * @param value The new value of the setting, may be null. + * @return Resolves when the setting has been changed. */ - - /* eslint-enable valid-jsdoc */ - public static async setValue( - settingName: SettingKey, + public static async setValue( + settingName: S, roomId: string | null, level: SettingLevel, - value: any, + value: Settings[S]["default"] | null, ): Promise { // Verify that the setting is actually a setting const setting = SETTINGS[settingName]; diff --git a/apps/web/src/settings/controllers/MediaPreviewConfigController.ts b/apps/web/src/settings/controllers/MediaPreviewConfigController.ts index d349ef33fa..beda3d1cf5 100644 --- a/apps/web/src/settings/controllers/MediaPreviewConfigController.ts +++ b/apps/web/src/settings/controllers/MediaPreviewConfigController.ts @@ -38,8 +38,8 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont const validMediaPreviews = Object.values(MediaPreviewValue); const validInviteAvatars = [MediaPreviewValue.Off, MediaPreviewValue.On]; return { - invite_avatars: validInviteAvatars.includes(inviteAvatars) ? inviteAvatars : undefined, - media_previews: validMediaPreviews.includes(mediaPreviews) ? mediaPreviews : undefined, + invite_avatars: validInviteAvatars.includes(inviteAvatars!) ? inviteAvatars : undefined, + media_previews: validMediaPreviews.includes(mediaPreviews!) ? mediaPreviews : undefined, }; } diff --git a/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts b/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts index 8b145c25ec..cd690ca147 100644 --- a/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts +++ b/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts @@ -60,8 +60,8 @@ export type IRightPanelForRoomStored = { history: Array; }; -export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | undefined { - if (!cacheRoom) return undefined; +export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | null { + if (!cacheRoom) return null; const storeHistory = [...cacheRoom.history].map((panelState) => convertCardToStore(panelState)); return { isOpen: cacheRoom.isOpen, history: storeHistory }; } diff --git a/apps/web/src/stores/room-list/RoomListStore.ts b/apps/web/src/stores/room-list/RoomListStore.ts index d35328bdad..fc2343d441 100644 --- a/apps/web/src/stores/room-list/RoomListStore.ts +++ b/apps/web/src/stores/room-list/RoomListStore.ts @@ -69,7 +69,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient implem "feature_dynamic_room_predecessors", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.msc3946ProcessDynamicPredecessor = newVal; + this.msc3946ProcessDynamicPredecessor = !!newVal; this.regenerateAllLists({ trigger: true }); }, ); diff --git a/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts b/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts index e1f765403f..cfa49544b9 100644 --- a/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts @@ -231,7 +231,7 @@ export class EventContentBodyViewModel "TextualBody.enableBigEmoji", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.setEnableBigEmoji(newVal); + this.setEnableBigEmoji(!!newVal); }, ); this.disposables.track(() => SettingsStore.unwatchSetting(enableBigEmojiWatcherRef)); @@ -240,7 +240,7 @@ export class EventContentBodyViewModel "Pill.shouldShowPillAvatar", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.setShouldShowPillAvatar(newVal); + this.setShouldShowPillAvatar(!!newVal); }, ); this.disposables.track(() => SettingsStore.unwatchSetting(shouldShowPillAvatarWatcherRef)); diff --git a/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts b/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts index 4086c1db14..490f7be43f 100644 --- a/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts @@ -75,7 +75,7 @@ export class RedactedBodyViewModel (_settingName, _roomId, _level, _newValAtLevel, newVal) => { if (this.showTwelveHour === newVal) return; - this.showTwelveHour = newVal; + this.showTwelveHour = !!newVal; this.updateTooltip(); }, ); diff --git a/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts b/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts index 55109ac876..b722def238 100644 --- a/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts +++ b/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts @@ -296,7 +296,7 @@ export class UrlPreviewGroupViewModel null, (_setting, _roomid, _level, compactLayout) => { this.snapshot.merge({ - compactLayout, + compactLayout: !!compactLayout, }); }, ); diff --git a/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx b/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx index 2f3f4bf1ba..131c365530 100644 --- a/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx +++ b/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx @@ -79,7 +79,7 @@ export class DateSeparatorViewModel "feature_jump_to_date", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.jumpToDateEnabled = newVal; + this.jumpToDateEnabled = !!newVal; this.updateSnapshot(); }, ); @@ -89,7 +89,7 @@ export class DateSeparatorViewModel UIFeature.TimelineEnableRelativeDates, null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.relativeDatesEnabled = newVal; + this.relativeDatesEnabled = !!newVal; this.updateSnapshot(); }, ); diff --git a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx index 04ce9de90f..bf53fe8910 100644 --- a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -557,7 +557,7 @@ describe("", () => { }); it("should not persist device language when not available", async () => { - await SettingsStore.setValue("language", null, SettingLevel.DEVICE, undefined); + await SettingsStore.setValue("language", null, SettingLevel.DEVICE, null); const languageBefore = SettingsStore.getValueAt(SettingLevel.DEVICE, "language", null, true, true); jest.spyOn(Lifecycle, "attemptDelegatedAuthLogin"); diff --git a/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx b/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx index 6582bd4d3a..cd04abfde7 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx @@ -225,7 +225,7 @@ describe("", () => { }); it("watches settings", async () => { - const watchSettingCallbacks: Record = {}; + const watchSettingCallbacks: Record> = {}; mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => { watchSettingCallbacks[settingName] = callback; diff --git a/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx b/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx index 44d40f91d8..dca3429f1f 100644 --- a/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx +++ b/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx @@ -28,7 +28,7 @@ describe("ReleaseAnnouncementStore", () => { settings = { releaseAnnouncementData: {}, }; - const watchCallbacks: Array = []; + const watchCallbacks: Array> = []; mocked(SettingsStore.getValue).mockImplementation((setting: string) => { return settings[setting]; diff --git a/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts b/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts index ec771a1d82..268162909b 100644 --- a/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts +++ b/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts @@ -203,13 +203,11 @@ describe("RoomListStore", () => { let featureFlagValue = false; jest.spyOn(SettingsStore, "getValue").mockImplementation(() => featureFlagValue); - let watchCallback: CallbackFn | undefined; - jest.spyOn(SettingsStore, "watchSetting").mockImplementation( - (_settingName: string, _roomId: string | null, callbackFn: CallbackFn) => { - watchCallback = callbackFn; - return "dyn_pred_ref"; - }, - ); + let watchCallback: CallbackFn | undefined; + jest.spyOn(SettingsStore, "watchSetting").mockImplementation((_settingName, _roomId, callbackFn) => { + watchCallback = callbackFn; + return "dyn_pred_ref"; + }); jest.spyOn(SettingsStore, "unwatchSetting"); const { store } = createStore(); diff --git a/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx b/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx index 10c7d66855..b545f316f9 100644 --- a/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx +++ b/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx @@ -664,7 +664,7 @@ describe("RoomListItemViewModel", () => { }); it("should update sections when OrderedCustomSections setting changes", () => { - let watchCallback: CallbackFn = () => {}; + let watchCallback: CallbackFn<"RoomList.OrderedCustomSections"> = () => {}; jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => { if (setting === "RoomList.OrderedCustomSections") watchCallback = callback; return "watcher-id";