Expose SDKContext on the window & avoid using singleton store .instance (#34098)
* Expose SDKContextClass via window for debugging * Remove stores from window if they are exposed via sdkContext * Avoid usages of global store instance where React context is accessible * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Iterate * Discard changes to apps/web/src/components/views/elements/AppTile.tsx
This commit is contained in:
Vendored
+2
-10
@@ -19,13 +19,8 @@ import { type PlatformPeg } from "../PlatformPeg";
|
||||
import { type IntegrationManagers } from "../integrations/IntegrationManagers";
|
||||
import { type ModalManager } from "../Modal";
|
||||
import type SettingsStore from "../settings/SettingsStore";
|
||||
import type RightPanelStore from "../stores/right-panel/RightPanelStore";
|
||||
import type WidgetStore from "../stores/WidgetStore";
|
||||
import type UserActivity from "../UserActivity";
|
||||
import { type ModalWidgetStore } from "../stores/ModalWidgetStore";
|
||||
import { type WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||
import { type SpaceStoreClass } from "../stores/spaces/SpaceStore";
|
||||
import type TypingStore from "../stores/TypingStore";
|
||||
import { type EventIndexPeg } from "../indexing/EventIndexPeg";
|
||||
import { type VoiceRecordingStore } from "../stores/VoiceRecordingStore";
|
||||
import type PerformanceMonitor from "../performance";
|
||||
@@ -41,6 +36,7 @@ import type MatrixChat from "../components/structures/MatrixChat";
|
||||
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
|
||||
import { type ModuleApiType } from "../modules/Api.ts";
|
||||
import type { RoomListStoreV3Class } from "../stores/room-list-v3/RoomListStoreV3.ts";
|
||||
import { type SDKContextClass } from "../contexts/SDKContextClass.ts";
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
@@ -95,14 +91,9 @@ declare global {
|
||||
mxIntegrationManagers: typeof IntegrationManagers;
|
||||
singletonModalManager: ModalManager;
|
||||
mxSettingsStore: SettingsStore;
|
||||
mxRightPanelStore: RightPanelStore;
|
||||
mxWidgetStore: WidgetStore;
|
||||
mxWidgetLayoutStore: WidgetLayoutStore;
|
||||
mxUserActivity: UserActivity;
|
||||
mxModalWidgetStore: ModalWidgetStore;
|
||||
mxSpaceStore: SpaceStoreClass;
|
||||
mxVoiceRecordingStore: VoiceRecordingStore;
|
||||
mxTypingStore: TypingStore;
|
||||
mxEventIndexPeg: EventIndexPeg;
|
||||
mxPerformanceMonitor: PerformanceMonitor;
|
||||
mxPerformanceEntryNames: any;
|
||||
@@ -114,6 +105,7 @@ declare global {
|
||||
mxOnRecaptchaLoaded?: () => void;
|
||||
mxModuleLoader: ModuleLoader;
|
||||
mxModuleApi: ModuleApiType;
|
||||
mxSdkContext: SDKContextClass;
|
||||
|
||||
// electron-only
|
||||
electron?: Electron;
|
||||
|
||||
@@ -93,7 +93,6 @@ import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
|
||||
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
|
||||
import SoftLogout from "./auth/SoftLogout";
|
||||
import { copyPlaintext } from "../../utils/strings";
|
||||
import { PosthogAnalytics } from "../../PosthogAnalytics";
|
||||
import { initSentry } from "../../sentry";
|
||||
import { showSpaceInvite } from "../../utils/space";
|
||||
import { type ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
@@ -245,6 +244,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
super(props);
|
||||
this.stores = SDKContextClass.instance;
|
||||
this.stores.constructEagerStores();
|
||||
window.mxSdkContext = this.stores;
|
||||
|
||||
this.state = {
|
||||
view: Views.LOADING,
|
||||
@@ -1792,7 +1792,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
|
||||
// Cannot be done in OnLoggedIn as at that point the AccountSettingsHandler doesn't yet have a client
|
||||
// Will be moved to a pre-login flow as well
|
||||
if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) {
|
||||
if (this.stores.posthogAnalytics.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) {
|
||||
this.initPosthogAnalyticsToast();
|
||||
}
|
||||
|
||||
|
||||
@@ -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, { type JSX, createRef, type ReactNode, type TransitionEventHandler } from "react";
|
||||
import React, { type JSX, createRef, type ReactNode, type TransitionEventHandler, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
type Room,
|
||||
@@ -58,6 +58,7 @@ import { _t } from "../../languageHandler";
|
||||
import { getLateEventInfo } from "./grouper/LateEventGrouper";
|
||||
import { DateSeparatorViewModel } from "../../viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { isEligibleForSpecialReceipt } from "../../viewmodels/room/timeline/event-tile/EventTileReceiptState";
|
||||
import { SDKContext } from "../../contexts/SDKContext.ts";
|
||||
|
||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
|
||||
@@ -66,7 +67,10 @@ const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
|
||||
* Creates and auto-disposes the DateSeparatorViewModel for message panel rendering.
|
||||
*/
|
||||
function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): JSX.Element {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts }));
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() => new DateSeparatorViewModel({ roomId, ts, roomViewStore: sdkContext.roomViewStore }),
|
||||
);
|
||||
return <DateSeparatorView vm={vm} className="mx_TimelineSeparator" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { useCreateAutoDisposedViewModel, WidgetPipView } from "@element-hq/web-shared-components";
|
||||
|
||||
import LegacyCallView from "../views/voip/LegacyCallView";
|
||||
import type LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import PictureInPictureDragger, { type CreatePipChildren } from "./PictureInPictureDragger";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
@@ -54,44 +53,6 @@ interface IState {
|
||||
showWidgetInPip: boolean;
|
||||
}
|
||||
|
||||
// Splits a list of calls into one 'primary' one and a list
|
||||
// (which should be a single element) of other calls.
|
||||
// The primary will be the one not on hold, or an arbitrary one
|
||||
// if they're all on hold)
|
||||
function getPrimarySecondaryCallsForPip(
|
||||
legacyCallHandler: LegacyCallHandler,
|
||||
roomId: string | null,
|
||||
): [MatrixCall | null, MatrixCall[]] {
|
||||
if (!roomId) return [null, []];
|
||||
|
||||
const calls = legacyCallHandler.getAllActiveCallsForPip(roomId);
|
||||
|
||||
let primary: MatrixCall | null = null;
|
||||
let secondaries: MatrixCall[] = [];
|
||||
|
||||
for (const call of calls) {
|
||||
if (!SHOW_CALL_IN_STATES.includes(call.state)) continue;
|
||||
|
||||
if (!call.isRemoteOnHold() && primary === null) {
|
||||
primary = call;
|
||||
} else {
|
||||
secondaries.push(call);
|
||||
}
|
||||
}
|
||||
|
||||
if (primary === null && secondaries.length > 0) {
|
||||
primary = secondaries[0];
|
||||
secondaries = secondaries.slice(1);
|
||||
}
|
||||
|
||||
if (secondaries.length > 1) {
|
||||
// We should never be in more than two calls so this shouldn't happen
|
||||
logger.log("Found more than 1 secondary call! Other calls will not be shown.");
|
||||
}
|
||||
|
||||
return [primary, secondaries];
|
||||
}
|
||||
|
||||
/**
|
||||
* PipContainer shows a small version of the LegacyCallView or a sticky widget hovering over the UI in
|
||||
* 'picture-in-picture' (PiP mode). It displays the call(s) which is *not* in the room the user is currently viewing
|
||||
@@ -103,11 +64,11 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props);
|
||||
super(props, context);
|
||||
|
||||
const roomId = context.roomViewStore.getRoomId();
|
||||
const roomId = this.context.roomViewStore.getRoomId();
|
||||
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(context.legacyCallHandler, roomId);
|
||||
const [primaryCall, secondaryCalls] = this.getPrimarySecondaryCallsForPip(roomId);
|
||||
|
||||
this.state = {
|
||||
viewedRoomId: roomId || undefined,
|
||||
@@ -147,6 +108,43 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
ActiveWidgetStore.instance.off(ActiveWidgetStoreEvent.Undock, this.onWidgetDockChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a list of calls into one 'primary' one and a list
|
||||
* (which should be a single element) of other calls.
|
||||
* The primary will be the one not on hold, or an arbitrary one
|
||||
* if they're all on hold)
|
||||
*/
|
||||
private getPrimarySecondaryCallsForPip(roomId: string | null): [MatrixCall | null, MatrixCall[]] {
|
||||
if (!roomId) return [null, []];
|
||||
|
||||
const calls = this.context.legacyCallHandler.getAllActiveCallsForPip(roomId);
|
||||
|
||||
let primary: MatrixCall | null = null;
|
||||
let secondaries: MatrixCall[] = [];
|
||||
|
||||
for (const call of calls) {
|
||||
if (!SHOW_CALL_IN_STATES.includes(call.state)) continue;
|
||||
|
||||
if (!call.isRemoteOnHold() && primary === null) {
|
||||
primary = call;
|
||||
} else {
|
||||
secondaries.push(call);
|
||||
}
|
||||
}
|
||||
|
||||
if (primary === null && secondaries.length > 0) {
|
||||
primary = secondaries[0];
|
||||
secondaries = secondaries.slice(1);
|
||||
}
|
||||
|
||||
if (secondaries.length > 1) {
|
||||
// We should never be in more than two calls so this shouldn't happen
|
||||
logger.log("Found more than 1 secondary call! Other calls will not be shown.");
|
||||
}
|
||||
|
||||
return [primary, secondaries];
|
||||
}
|
||||
|
||||
private onMove = (): void => this.props.movePersistedElement.current?.();
|
||||
|
||||
private onRoomViewStoreUpdate = (): void => {
|
||||
@@ -165,7 +163,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
}
|
||||
if (!newRoomId) return;
|
||||
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.context.legacyCallHandler, newRoomId);
|
||||
const [primaryCall, secondaryCalls] = this.getPrimarySecondaryCallsForPip(newRoomId);
|
||||
this.setState({
|
||||
viewedRoomId: newRoomId,
|
||||
primaryCall: primaryCall,
|
||||
@@ -184,10 +182,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
|
||||
private updateCalls = (): void => {
|
||||
if (!this.state.viewedRoomId) return;
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(
|
||||
this.context.legacyCallHandler,
|
||||
this.state.viewedRoomId,
|
||||
);
|
||||
const [primaryCall, secondaryCalls] = this.getPrimarySecondaryCallsForPip(this.state.viewedRoomId);
|
||||
|
||||
this.setState({
|
||||
primaryCall: primaryCall,
|
||||
@@ -198,10 +193,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
|
||||
private onCallRemoteHold = (): void => {
|
||||
if (!this.state.viewedRoomId) return;
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(
|
||||
this.context.legacyCallHandler,
|
||||
this.state.viewedRoomId,
|
||||
);
|
||||
const [primaryCall, secondaryCalls] = this.getPrimarySecondaryCallsForPip(this.state.viewedRoomId);
|
||||
|
||||
this.setState({
|
||||
primaryCall: primaryCall,
|
||||
|
||||
@@ -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, { type ReactNode } from "react";
|
||||
import React, { type ReactNode, useContext } from "react";
|
||||
import { EventType, M_BEACON_INFO, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { DateSeparatorView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||
@@ -19,6 +19,7 @@ import { _t } from "../../../languageHandler";
|
||||
import NewRoomIntro from "../../views/rooms/NewRoomIntro";
|
||||
import GenericEventListSummary from "../../views/elements/GenericEventListSummary";
|
||||
import { DateSeparatorViewModel } from "../../../viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
// Wrap initial room creation events into a GenericEventListSummary
|
||||
// Grouping only events sent by the same user that sent the `m.room.create` and only until
|
||||
@@ -28,7 +29,10 @@ import { DateSeparatorViewModel } from "../../../viewmodels/room/timeline/DateSe
|
||||
* Creates and auto-disposes the DateSeparatorViewModel for creation-group rendering.
|
||||
*/
|
||||
function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): ReactNode {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts }));
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() => new DateSeparatorViewModel({ roomId, ts, roomViewStore: sdkContext.roomViewStore }),
|
||||
);
|
||||
return <DateSeparatorView vm={vm} className="mx_TimelineSeparator" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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, { type ReactNode } from "react";
|
||||
import React, { type ReactNode, useContext } from "react";
|
||||
import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { DateSeparatorView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import HistoryTile from "../../views/rooms/HistoryTile";
|
||||
import EventListSummary from "../../views/elements/EventListSummary";
|
||||
import { DateSeparatorViewModel } from "../../../viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
const groupedStateEvents = [
|
||||
EventType.RoomMember,
|
||||
@@ -30,7 +31,10 @@ const groupedStateEvents = [
|
||||
* Creates and auto-disposes the DateSeparatorViewModel for grouped timeline rendering.
|
||||
*/
|
||||
function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): ReactNode {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts }));
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() => new DateSeparatorViewModel({ roomId, ts, roomViewStore: sdkContext.roomViewStore }),
|
||||
);
|
||||
return <DateSeparatorView vm={vm} className="mx_TimelineSeparator" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import ScrollPanel from "../../structures/ScrollPanel";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import EditHistoryMessage from "../messages/EditHistoryMessage";
|
||||
import { DateSeparatorViewModel } from "../../../viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass.ts";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
@@ -54,7 +55,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
|
||||
const key = `${roomId}-${ts}`;
|
||||
let vm = this.dateSeparatorVms.get(key);
|
||||
if (!vm) {
|
||||
vm = new DateSeparatorViewModel({ roomId, ts });
|
||||
vm = new DateSeparatorViewModel({ roomId, ts, roomViewStore: SDKContextClass.instance.roomViewStore });
|
||||
this.dateSeparatorVms.set(key, vm);
|
||||
}
|
||||
return vm;
|
||||
|
||||
@@ -41,9 +41,8 @@ import { type ICompletion } from "../../../autocomplete/Autocompleter";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
// matches emoticons which follow the start of a line or whitespace
|
||||
const REGEX_EMOTICON_WHITESPACE = new RegExp("(?:^|\\s)(" + EMOTICON_REGEX.source + ")\\s|:^$");
|
||||
@@ -115,6 +114,9 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class BasicMessageEditor extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public readonly editorRef = createRef<HTMLDivElement>();
|
||||
private autocompleteRef = createRef<Autocomplete>();
|
||||
private formatBarRef = createRef<MessageComposerFormatBar>();
|
||||
@@ -245,17 +247,13 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||
const { cmd } = parseCommandString(this.props.model.parts[0].text);
|
||||
const command = CommandMap.get(cmd!);
|
||||
if (
|
||||
!command?.isEnabled(MatrixClientPeg.get(), this.props.room.roomId) ||
|
||||
!command?.isEnabled(this.context.client!, this.props.room.roomId) ||
|
||||
command.category !== CommandCategories.messages
|
||||
) {
|
||||
isTyping = false;
|
||||
}
|
||||
}
|
||||
SDKContextClass.instance.typingStore.setSelfTyping(
|
||||
this.props.room.roomId,
|
||||
this.props.threadId ?? null,
|
||||
isTyping,
|
||||
);
|
||||
this.context.typingStore.setSelfTyping(this.props.room.roomId, this.props.threadId ?? null, isTyping);
|
||||
|
||||
this.props.onChange?.(selection, inputType, diff);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, type JSX, type ReactNode } from "react";
|
||||
import React, { useCallback, type JSX, type ReactNode, useContext } from "react";
|
||||
import {
|
||||
RoomListView as SharedRoomListView,
|
||||
useCreateAutoDisposedViewModel,
|
||||
@@ -13,21 +13,28 @@ import {
|
||||
} from "@element-hq/web-shared-components";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||
import { RoomAvatarView } from "../../avatars/RoomAvatarView";
|
||||
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
|
||||
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
|
||||
import { RoomListViewModel } from "../../../../viewmodels/room-list/RoomListViewModel";
|
||||
import { SDKContext } from "../../../../contexts/SDKContext.ts";
|
||||
|
||||
/**
|
||||
* RoomListView component using shared components with proper MVVM pattern.
|
||||
*/
|
||||
export function RoomListView(): JSX.Element {
|
||||
const matrixClient = useMatrixClientContext();
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
// Create and auto-dispose ViewModel instance
|
||||
const vm = useCreateAutoDisposedViewModel(() => new RoomListViewModel({ client: matrixClient }));
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() =>
|
||||
new RoomListViewModel({
|
||||
client: sdkContext.client!,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
}),
|
||||
);
|
||||
|
||||
// Render avatar for each room - memoized to prevent re-renders
|
||||
const renderAvatar = useCallback((room: SharedRoom): ReactNode => {
|
||||
|
||||
@@ -7,7 +7,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, { type JSX } from "react";
|
||||
import React, { type JSX, useContext } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { DateSeparatorView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||
|
||||
@@ -22,6 +22,7 @@ import { buildLegacyCallEventGroupers } from "../../structures/LegacyCallEventGr
|
||||
import { haveRendererForEvent } from "../../../events/EventTileFactory";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { DateSeparatorViewModel } from "../../../viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
// a list of strings to be highlighted in the results
|
||||
@@ -39,7 +40,10 @@ interface IProps {
|
||||
* Creates and auto-disposes the DateSeparatorViewModel for search result rendering.
|
||||
*/
|
||||
function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): JSX.Element {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts }));
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() => new DateSeparatorViewModel({ roomId, ts, roomViewStore: sdkContext.roomViewStore }),
|
||||
);
|
||||
return <DateSeparatorView vm={vm} className="mx_TimelineSeparator" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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, { type JSX } from "react";
|
||||
import React, { type JSX, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { SettingsSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { IconButton, Text, Tooltip } from "@vector-im/compound-web";
|
||||
@@ -20,14 +20,15 @@ import { Action } from "../../../dispatcher/actions";
|
||||
import QuickThemeSwitcher from "./QuickThemeSwitcher";
|
||||
import Modal from "../../../Modal";
|
||||
import DevtoolsDialog from "../dialogs/DevtoolsDialog";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
const QuickSettingsButton: React.FC<{
|
||||
isPanelCollapsed: boolean;
|
||||
}> = ({ isPanelCollapsed = false }) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLButtonElement>();
|
||||
|
||||
const currentRoomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const currentRoomId = sdkContext.roomViewStore.getRoomId();
|
||||
const developerModeEnabled = useSettingValue("developerMode");
|
||||
|
||||
let contextMenu: JSX.Element | undefined;
|
||||
|
||||
@@ -10,12 +10,11 @@ import React, { type FC, useContext, useEffect, type AriaRole, useCallback } fro
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { type Call, CallEvent } from "../../../models/Call";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import AppTile from "../elements/AppTile";
|
||||
import { CallStore } from "../../../stores/CallStore";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass";
|
||||
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import { useCall } from "../../../hooks/useCall";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface JoinCallViewProps {
|
||||
room: Room;
|
||||
@@ -26,7 +25,7 @@ interface JoinCallViewProps {
|
||||
}
|
||||
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, role, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
useTypedEventEmitter(call, CallEvent.Close, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -38,17 +37,17 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, role, onClo
|
||||
// The stickyPromise has to resolve before the widget actually becomes sticky.
|
||||
// We only let the widget become sticky after disconnecting all other active calls.
|
||||
const calls = [...CallStore.instance.connectedCalls].filter(
|
||||
(call) => SDKContextClass.instance.roomViewStore.getRoomId() !== call.roomId,
|
||||
(call) => sdkContext.roomViewStore.getRoomId() !== call.roomId,
|
||||
);
|
||||
await Promise.all(calls.map(async (call) => await call.disconnect()));
|
||||
}, []);
|
||||
}, [sdkContext.roomViewStore]);
|
||||
|
||||
return (
|
||||
<div className="mx_CallView" role={role}>
|
||||
<AppTile
|
||||
app={call.widget}
|
||||
room={room}
|
||||
userId={cli.credentials.userId!}
|
||||
userId={sdkContext.client?.credentials.userId ?? undefined}
|
||||
creatorUserId={call.widget.creatorUserId}
|
||||
waitForIframeLoad={call.widget.waitForIframeLoad}
|
||||
showMenubar={false}
|
||||
|
||||
@@ -162,7 +162,6 @@ export class SDKContextClass {
|
||||
public get typingStore(): TypingStore {
|
||||
if (!this._TypingStore) {
|
||||
this._TypingStore = new TypingStore(this);
|
||||
window.mxTypingStore = this._TypingStore;
|
||||
}
|
||||
return this._TypingStore;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { type ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import type React from "react";
|
||||
@@ -37,8 +37,8 @@ import { type InteractionName } from "../../PosthogTrackers";
|
||||
import { ElementCallMemberEventType } from "../../call-types";
|
||||
import { LocalRoom, LocalRoomState } from "../../models/LocalRoom";
|
||||
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext";
|
||||
import { SDKContext } from "../../contexts/SDKContext.ts";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass";
|
||||
|
||||
const logger = rootLogger.getChild("useRoomCall");
|
||||
|
||||
@@ -107,6 +107,7 @@ export const useRoomCall = (
|
||||
showVideoCallButton: boolean;
|
||||
showVoiceCallButton: boolean;
|
||||
} => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const roomViewStore = useScopedRoomContext("roomViewStore").roomViewStore;
|
||||
// settings
|
||||
const widgetsFeatureEnabled = useSettingValue(UIFeature.Widgets);
|
||||
@@ -133,9 +134,9 @@ export const useRoomCall = (
|
||||
}, [useElementCallExclusively, serverIsConfiguredForElementCall]);
|
||||
|
||||
const hasLegacyCall = useEventEmitterState(
|
||||
SDKContextClass.instance.legacyCallHandler,
|
||||
sdkContext.legacyCallHandler,
|
||||
LegacyCallHandlerEvent.CallsChanged,
|
||||
() => SDKContextClass.instance.legacyCallHandler.getCallForRoom(room.roomId) !== null,
|
||||
() => sdkContext.legacyCallHandler.getCallForRoom(room.roomId) !== null,
|
||||
);
|
||||
// settings
|
||||
const widgets = useWidgets(room);
|
||||
|
||||
@@ -69,7 +69,10 @@ describe("useRoomCall", () => {
|
||||
});
|
||||
|
||||
function render() {
|
||||
return renderHook(() => useRoomCall(room), withContexts({ matrixClient: client, roomContext }));
|
||||
return renderHook(
|
||||
() => useRoomCall(room),
|
||||
withContexts({ matrixClient: client, roomContext, sdkContext: SDKContextClass.instance }),
|
||||
);
|
||||
}
|
||||
|
||||
describe("Element Call focus detection", () => {
|
||||
|
||||
@@ -202,5 +202,3 @@ export default class WidgetStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
this.emit(UPDATE_EVENT, roomId);
|
||||
}
|
||||
}
|
||||
|
||||
window.mxWidgetStore = WidgetStore.instance;
|
||||
|
||||
@@ -454,5 +454,3 @@ export default class RightPanelStore extends ReadyWatchingStore {
|
||||
return this.internalInstance;
|
||||
}
|
||||
}
|
||||
|
||||
window.mxRightPanelStore = RightPanelStore.instance;
|
||||
|
||||
@@ -1370,5 +1370,3 @@ export default class SpaceStore {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
window.mxSpaceStore = SpaceStore.instance;
|
||||
|
||||
@@ -514,5 +514,3 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||
this.recalculateRoom(room); // call to try local echo on changes (the catch above undoes any errors)
|
||||
}
|
||||
}
|
||||
|
||||
window.mxWidgetLayoutStore = WidgetLayoutStore.instance;
|
||||
|
||||
@@ -262,6 +262,7 @@ export default class HTMLExporter extends Exporter {
|
||||
roomId: event.getRoomId()!,
|
||||
ts,
|
||||
forExport: true,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
try {
|
||||
const dateSeparator = (
|
||||
|
||||
@@ -22,7 +22,7 @@ import dispatcher from "../../dispatcher/dispatcher";
|
||||
import { type ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
|
||||
import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { type RoomListSectionsCollapseStateChangedPayload } from "../../dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload";
|
||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||
import { type SpaceStoreClass } from "../../stores/spaces/SpaceStore";
|
||||
import RoomListStoreV3, {
|
||||
RoomListStoreV3Event,
|
||||
type RoomsResult,
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
UPDATE_STATUS_INDICATOR,
|
||||
} from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { RoomListItemViewModel } from "./RoomListItemViewModel";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass";
|
||||
import { hasCreateRoomRights } from "./utils";
|
||||
import { keepIfSame } from "../../utils/keepIfSame";
|
||||
import { DefaultTagID } from "../../stores/room-list-v3/skip-list/tag";
|
||||
@@ -43,6 +42,7 @@ import { getCustomSectionData, isCustomSectionTag, CHATS_TAG } from "../../store
|
||||
import { tagRoom } from "../../utils/room/tagRoom";
|
||||
import { getSectionTagForRoom } from "../../utils/room/getSectionTagForRoom";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { type RoomViewStore } from "../../stores/RoomViewStore.tsx";
|
||||
|
||||
/**
|
||||
* Tracks the position of the active room within a specific section.
|
||||
@@ -58,6 +58,8 @@ interface StickyRoomPosition {
|
||||
|
||||
interface RoomListViewModelProps {
|
||||
client: MatrixClient;
|
||||
roomViewStore: RoomViewStore;
|
||||
spaceStore: SpaceStoreClass;
|
||||
}
|
||||
|
||||
const filterKeyToIdMap: Map<FilterEnum, FilterId> = new Map([
|
||||
@@ -158,7 +160,7 @@ export class RoomListViewModel
|
||||
private scrollToIndex?: (index: number) => void;
|
||||
|
||||
public constructor(props: RoomListViewModelProps) {
|
||||
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
||||
const activeSpace = props.spaceStore.activeSpaceRoom;
|
||||
|
||||
// Get initial rooms
|
||||
const roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(undefined);
|
||||
@@ -575,7 +577,7 @@ export class RoomListViewModel
|
||||
* Migrated from useRoomListNavigation hook.
|
||||
*/
|
||||
private handleViewRoomDelta(payload: ViewRoomDeltaPayload): void {
|
||||
const currentRoomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const currentRoomId = this.props.roomViewStore.getRoomId();
|
||||
if (!currentRoomId) return;
|
||||
|
||||
const { delta, unread } = payload;
|
||||
@@ -646,7 +648,7 @@ export class RoomListViewModel
|
||||
}
|
||||
|
||||
// Space changed - get the last selected room for the new space to prevent flicker
|
||||
const lastSelectedRoom = SpaceStore.instance.getLastSelectedRoomIdForSpace(newSpaceId);
|
||||
const lastSelectedRoom = this.props.spaceStore.getLastSelectedRoomIdForSpace(newSpaceId);
|
||||
|
||||
this.updateRoomListData(true, lastSelectedRoom);
|
||||
return;
|
||||
@@ -753,7 +755,7 @@ export class RoomListViewModel
|
||||
): Promise<void> {
|
||||
// Determine the room ID to use for calculations
|
||||
// Use override if provided (e.g., during space changes), otherwise fall back to RoomViewStore
|
||||
const roomId = roomIdOverride ?? SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const roomId = roomIdOverride ?? this.props.roomViewStore.getRoomId();
|
||||
|
||||
// Apply sticky room logic to keep selected room at same position within its section
|
||||
const stickySections = this.applyStickyRoom(isRoomChange, roomId);
|
||||
@@ -858,7 +860,7 @@ export class RoomListViewModel
|
||||
};
|
||||
|
||||
public createRoom = (): void => {
|
||||
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
||||
const activeSpace = this.props.spaceStore.activeSpaceRoom;
|
||||
if (activeSpace) {
|
||||
dispatcher.dispatch({
|
||||
action: Action.CreateRoom,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { UIFeature } from "../../../settings/UIFeature";
|
||||
import ErrorDialog from "../../../components/views/dialogs/ErrorDialog";
|
||||
import BugReportDialog from "../../../components/views/dialogs/BugReportDialog";
|
||||
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass";
|
||||
import { type RoomViewStore } from "../../../stores/RoomViewStore.tsx";
|
||||
|
||||
export interface DateSeparatorViewModelProps {
|
||||
/**
|
||||
@@ -43,6 +43,10 @@ export interface DateSeparatorViewModelProps {
|
||||
* Export mode disables relative date labels and jump-to-date menu UI.
|
||||
*/
|
||||
forExport?: boolean;
|
||||
/**
|
||||
* The room view store instance to use
|
||||
*/
|
||||
roomViewStore: RoomViewStore;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +163,7 @@ export class DateSeparatorViewModel
|
||||
// Only try to navigate to the room if the user is still viewing the same
|
||||
// room. We don't want to jump someone back to a room after a slow request
|
||||
// if they've already navigated away to another room.
|
||||
const currentRoomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const currentRoomId = this.props.roomViewStore.getRoomId();
|
||||
if (currentRoomId === roomIdForJumpRequest) {
|
||||
dispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
@@ -185,7 +189,7 @@ export class DateSeparatorViewModel
|
||||
// don't want to worry someone about an error in a room they no longer care
|
||||
// about after a slow request if they've already navigated away to another
|
||||
// room.
|
||||
const currentRoomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const currentRoomId = this.props.roomViewStore.getRoomId();
|
||||
if (currentRoomId === roomIdForJumpRequest) {
|
||||
let friendlyErrorMessage = "An error occured while trying to find and jump to the given date.";
|
||||
let submitDebugLogsContent: React.ReactElement = <></>;
|
||||
|
||||
@@ -188,6 +188,7 @@ export const mockClientMethodsCrypto = (): Partial<
|
||||
});
|
||||
|
||||
export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
|
||||
getVisibleRooms: vi.fn().mockReturnValue(rooms),
|
||||
getRooms: vi.fn().mockReturnValue(rooms),
|
||||
getRoom: vi.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
|
||||
isRoomEncrypted: vi.fn(),
|
||||
|
||||
@@ -37,6 +37,7 @@ import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomCo
|
||||
import { untilDispatch } from "../../../test-utils/utilities.ts";
|
||||
import { TimelineRenderingType } from "../../../../src/contexts/RoomContext.ts";
|
||||
import { type ComposerInsertPayload, ComposerType } from "../../../../src/dispatcher/payloads/ComposerInsertPayload.ts";
|
||||
import { SDKContext } from "../../../../src/contexts/SDKContext.ts";
|
||||
|
||||
describe("ThreadView", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
@@ -73,7 +74,11 @@ describe("ThreadView", () => {
|
||||
}
|
||||
|
||||
async function getComponent(initialEvent?: MatrixEvent): Promise<RenderResult> {
|
||||
const renderResult = render(<TestThreadView initialEvent={initialEvent} />);
|
||||
const renderResult = render(<TestThreadView initialEvent={initialEvent} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(() => getByTestId(renderResult.container, "spinner")).toThrow();
|
||||
|
||||
@@ -24,7 +24,6 @@ import { clientAndSDKContextRenderOptions, stubClient } from "../../../../test-u
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
|
||||
@@ -194,13 +193,11 @@ describe("AppTile", () => {
|
||||
);
|
||||
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
room={r2}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
<RightPanel
|
||||
room={r2}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(renderResult.queryByText("Example 1")).not.toBeInTheDocument();
|
||||
@@ -278,13 +275,11 @@ describe("AppTile", () => {
|
||||
}),
|
||||
);
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
room={r2}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
<RightPanel
|
||||
room={r2}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
+8
@@ -283,6 +283,14 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
class="_link_k9ljz_8"
|
||||
data-kind="primary"
|
||||
data-size="md"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Set layout for everyone
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { CommandPartCreator } from "../../../../../src/editor/parts";
|
||||
import DocumentOffset from "../../../../../src/editor/offset";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
|
||||
describe("BasicMessageComposer", () => {
|
||||
const renderer = createRenderer();
|
||||
@@ -35,7 +36,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should allow a user to paste a URL without it being mangled", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const testUrl = "https://element.io";
|
||||
const mockDataTransfer = generateMockDataTransferForString(testUrl);
|
||||
await userEvent.paste(mockDataTransfer);
|
||||
@@ -51,7 +56,11 @@ describe("BasicMessageComposer", () => {
|
||||
});
|
||||
userEvent.setup();
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
const tranformations = [
|
||||
{ before: "4:3 video", after: "4:3 video" },
|
||||
@@ -81,7 +90,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should not mangle shift-enter when the autocomplete is open", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
@@ -93,7 +106,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should escape single quote in placeholder", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
const composer = render(<BasicMessageComposer placeholder="Don't" model={model} room={room} />);
|
||||
const composer = render(<BasicMessageComposer placeholder="Don't" model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const input = composer.queryAllByRole("textbox");
|
||||
const placeholder = input[0].style.getPropertyValue("--placeholder");
|
||||
expect(placeholder).toMatch("'Don\\'t'");
|
||||
@@ -101,7 +118,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should escape backslash in placeholder", async () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
const composer = render(<BasicMessageComposer placeholder={"w\\e"} model={model} room={room} />);
|
||||
const composer = render(<BasicMessageComposer placeholder={"w\\e"} model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const input = composer.queryAllByRole("textbox");
|
||||
const placeholder = input[0].style.getPropertyValue("--placeholder");
|
||||
expect(placeholder).toMatch("'w\\\\e'");
|
||||
@@ -116,7 +137,11 @@ describe("BasicMessageComposer", () => {
|
||||
// spy on typingStore.setSelfTyping
|
||||
const spy = jest.spyOn(SDKContextClass.instance.typingStore, "setSelfTyping");
|
||||
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
// simulate typing by updating the model - this will call the component's update callback
|
||||
await model.update(commandPart.text, "insertText", new DocumentOffset(commandPart.text.length, true));
|
||||
@@ -128,7 +153,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should ignore keydown events during IME composition", () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Start IME composition
|
||||
@@ -167,7 +196,11 @@ describe("BasicMessageComposer", () => {
|
||||
|
||||
it("should handle keydown events normally when not composing", () => {
|
||||
const model = new EditorModel([], pc, renderer);
|
||||
render(<BasicMessageComposer model={model} room={room} />);
|
||||
render(<BasicMessageComposer model={model} room={room} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Simulate Tab key when NOT composing
|
||||
|
||||
@@ -33,6 +33,8 @@ import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
describe("<EditMessageComposer/>", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -78,9 +80,11 @@ describe("<EditMessageComposer/>", () => {
|
||||
const getComponent = (editState: EditorStateTransfer, roomContext: RoomContextType = defaultRoomContext) =>
|
||||
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import PlatformPeg from "../../../../../src/PlatformPeg";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
|
||||
function getTile(container: HTMLElement): HTMLElement {
|
||||
const tile = container.querySelector(".mx_EventTile");
|
||||
@@ -1652,7 +1654,11 @@ describe("EventTile", () => {
|
||||
return Element.prototype.matches.call(this, selector);
|
||||
});
|
||||
|
||||
const { container, rerender } = render(<WrappedEventTiles events={events} editEvent={firstEvent} />);
|
||||
const { container, rerender } = render(<WrappedEventTiles events={events} editEvent={firstEvent} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
const editingTile = container.querySelector(".mx_EventTile_isEditing");
|
||||
|
||||
expect(editingTile).not.toBeNull();
|
||||
|
||||
@@ -37,6 +37,8 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import { TimelineRenderingType, type RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
const openStickerPicker = async (): Promise<void> => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
@@ -469,13 +471,15 @@ function wrapAndRender(
|
||||
};
|
||||
|
||||
const getRawComponent = (props = {}, context = roomContext, client = mockClient) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<ScopedRoomContextProvider {...context}>
|
||||
<RoomUploadContextProvider>
|
||||
<MessageComposer {...defaultProps} {...props} />
|
||||
</RoomUploadContextProvider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<ScopedRoomContextProvider {...context}>
|
||||
<RoomUploadContextProvider>
|
||||
<MessageComposer {...defaultProps} {...props} />
|
||||
</RoomUploadContextProvider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
);
|
||||
return {
|
||||
rawComponent: getRawComponent(props, roomContext, mockClient),
|
||||
|
||||
@@ -59,6 +59,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
|
||||
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
||||
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
|
||||
import { SDKContext } from "../../../../../../src/contexts/SDKContext";
|
||||
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
jest.mock("../../../../../../src/utils/ShieldUtils");
|
||||
@@ -93,9 +94,11 @@ describe("RoomHeader", () => {
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import React from "react";
|
||||
import { MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { render, type RenderResult } from "jest-matrix-react";
|
||||
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import { clientAndSDKContextRenderOptions, stubClient } from "../../../../test-utils";
|
||||
import SearchResultTile from "../../../../../src/components/views/rooms/SearchResultTile";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
const ROOM_ID = "!qPewotXpIctQySfjSy:localhost";
|
||||
|
||||
@@ -30,7 +31,7 @@ describe("SearchResultTile", () => {
|
||||
function renderComponent(props: Partial<Props>): RenderResult {
|
||||
return render(
|
||||
<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />,
|
||||
withClientContextRenderOptions(MatrixClientPeg.safeGet()),
|
||||
clientAndSDKContextRenderOptions(MatrixClientPeg.safeGet(), SDKContextClass.instance),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import { addTextToComposer } from "../../../../test-utils/composer";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import { RoomUploadContextProvider } from "../../../../../src/viewmodels/room/RoomUploadViewModel.tsx";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
|
||||
jest.mock("../../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
@@ -199,7 +200,11 @@ describe("<SendMessageComposer/>", () => {
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
|
||||
return render(getRawComponent(props, roomContext, client));
|
||||
return render(getRawComponent(props, roomContext, client), {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
it("renders text and placeholder correctly", () => {
|
||||
@@ -447,6 +452,11 @@ describe("<SendMessageComposer/>", () => {
|
||||
</RoomUploadContextProvider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const composer = container.querySelector<HTMLDivElement>(".mx_BasicMessageComposer_input")!;
|
||||
|
||||
@@ -14,12 +14,17 @@ import { mocked } from "jest-mock";
|
||||
import QuickSettingsButton from "../../../../../src/components/views/spaces/QuickSettingsButton";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext.ts";
|
||||
|
||||
describe("QuickSettingsButton", () => {
|
||||
const roomId = "!room:example.com";
|
||||
|
||||
const renderQuickSettingsButton = () => {
|
||||
render(<QuickSettingsButton isPanelCollapsed={true} />);
|
||||
render(<QuickSettingsButton isPanelCollapsed={true} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const getQuickSettingsButton = () => {
|
||||
@@ -37,7 +42,11 @@ describe("QuickSettingsButton", () => {
|
||||
});
|
||||
|
||||
it("should render the quick settings button in expanded mode", () => {
|
||||
const { asFragment } = render(<QuickSettingsButton isPanelCollapsed={false} />);
|
||||
const { asFragment } = render(<QuickSettingsButton isPanelCollapsed={false} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<SDKContext.Provider value={SDKContextClass.instance}>{children}</SDKContext.Provider>
|
||||
),
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,14 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
|
||||
import { createTestClient, flushPromises, flushPromisesWithFakeTimers, mkStubRoom, stubClient } from "../../test-utils";
|
||||
import {
|
||||
createTestClient,
|
||||
flushPromises,
|
||||
flushPromisesWithFakeTimers,
|
||||
mkStubRoom,
|
||||
stubClient,
|
||||
TestSDKContext,
|
||||
} from "../../test-utils";
|
||||
import RoomListStoreV3, { RoomListStoreV3Event } from "../../../src/stores/room-list-v3/RoomListStoreV3";
|
||||
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
|
||||
import { FilterEnum } from "../../../src/stores/room-list-v3/skip-list/filters";
|
||||
@@ -44,6 +51,7 @@ jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
||||
|
||||
describe("RoomListViewModel", () => {
|
||||
let matrixClient: MatrixClient;
|
||||
let sdkContext: TestSDKContext;
|
||||
let room1: Room;
|
||||
let room2: Room;
|
||||
let room3: Room;
|
||||
@@ -51,6 +59,8 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
matrixClient = createTestClient();
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = matrixClient;
|
||||
room1 = mkStubRoom("!room1:server", "Room 1", matrixClient);
|
||||
room2 = mkStubRoom("!room2:server", "Room 2", matrixClient);
|
||||
room3 = mkStubRoom("!room3:server", "Room 3", matrixClient);
|
||||
@@ -80,7 +90,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Initialization", () => {
|
||||
it("should initialize with correct snapshot", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]);
|
||||
@@ -97,7 +111,11 @@ describe("RoomListViewModel", () => {
|
||||
sections: [{ tag: CHATS_TAG, rooms: [] }],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().sections).toEqual([]);
|
||||
expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true);
|
||||
@@ -106,7 +124,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("should set canCreateRoom based on user rights", () => {
|
||||
mocked(hasCreateRoomRights).mockReturnValue(true);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().canCreateRoom).toBe(true);
|
||||
});
|
||||
@@ -114,7 +136,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Room list updates", () => {
|
||||
it("should update room list when ListsUpdate event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient);
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
@@ -134,7 +160,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("should update loading state when ListsLoaded event fires", () => {
|
||||
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().isLoadingRooms).toBe(true);
|
||||
|
||||
@@ -145,7 +175,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
// This test ensures that the room list item vms are preserved when the room list is changing
|
||||
it("should keep existing view model when ListsUpdate event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Create view model for room1
|
||||
const room1VM = viewModel.getRoomItemViewModel("!room1:server");
|
||||
@@ -160,7 +194,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Space switching", () => {
|
||||
it("should update room list when space changes", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const spaceRoomList = [room1, room2];
|
||||
|
||||
@@ -178,7 +216,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should clear view models when space changes", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Get view models for visible rooms
|
||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
|
||||
@@ -200,7 +242,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should clear roomsMap when space changes and repopulate with new rooms", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const newSpaceRoom = mkStubRoom("!spaceroom:server", "Space Room", matrixClient);
|
||||
|
||||
@@ -221,7 +267,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Active room tracking", () => {
|
||||
it("should update active room index when room is selected", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||
|
||||
@@ -237,7 +287,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should return undefined active room index when no room is selected", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
||||
|
||||
@@ -255,7 +309,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Sticky room behavior", () => {
|
||||
it("should keep selected room at same index when room list updates", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Select room at index 1
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||
@@ -281,7 +339,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should not apply sticky behavior when user changes rooms", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Select room at index 1
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||
@@ -307,7 +369,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Filters", () => {
|
||||
it("should toggle filter on", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().activeFilterId).toBeUndefined();
|
||||
|
||||
@@ -324,7 +390,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should toggle filter off", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Turn filter on
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
@@ -363,7 +433,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("hides the Favourites and Low Priority filters when sections are enabled", () => {
|
||||
mockShowSections(true);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
|
||||
const { filterIds } = viewModel.getSnapshot();
|
||||
expect(filterIds).not.toContain("favourite");
|
||||
@@ -372,7 +446,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("shows the Favourites and Low Priority filters when sections are disabled", () => {
|
||||
mockShowSections(false);
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
|
||||
const { filterIds } = viewModel.getSnapshot();
|
||||
expect(filterIds).toContain("favourite");
|
||||
@@ -393,7 +471,11 @@ describe("RoomListViewModel", () => {
|
||||
return "watcher-id";
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
expect(viewModel.getSnapshot().filterIds).toContain("favourite");
|
||||
|
||||
// Activate the Favourites filter
|
||||
@@ -423,7 +505,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Room item view models", () => {
|
||||
it("should create room item view model on demand", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const itemViewModel = viewModel.getRoomItemViewModel("!room1:server");
|
||||
|
||||
@@ -432,7 +518,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should reuse existing room item view model", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server");
|
||||
const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server");
|
||||
@@ -441,13 +531,21 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should return undefined for non-existent room", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getRoomItemViewModel("!nonexistent:server")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not throw when requesting view model for a room removed from the list but still in roomsMap", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Normal list update removes room2 from the list
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
@@ -461,7 +559,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should return undefined for a room from old space after space change", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const spaceRoom = mkStubRoom("!newroom:server", "New Room", matrixClient);
|
||||
|
||||
@@ -478,7 +580,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should recover when roomsMap is stale but roomsResult has the room", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Manually clear roomsMap to simulate stale cache, but keep roomsResult intact
|
||||
(viewModel as any).roomsMap.clear();
|
||||
@@ -488,7 +594,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should dispose view models for rooms no longer visible", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
|
||||
const vm2 = viewModel.getRoomItemViewModel("!room2:server")!;
|
||||
@@ -511,7 +621,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Room creation", () => {
|
||||
it("should dispatch CreateChat action when createChatRoom is called", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const dispatchSpy = jest.spyOn(dispatcher, "fire");
|
||||
|
||||
@@ -521,7 +635,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should dispatch CreateRoom action without parent space", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
|
||||
@@ -536,7 +654,11 @@ describe("RoomListViewModel", () => {
|
||||
const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient);
|
||||
jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom);
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
|
||||
@@ -556,7 +678,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should navigate to next room when delta is 1", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
||||
|
||||
@@ -579,7 +705,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should navigate to previous room when delta is -1", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||
|
||||
@@ -602,7 +732,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should wrap around to last room when navigating backwards from first room", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
||||
|
||||
@@ -625,7 +759,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should not navigate when current room is not found", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server");
|
||||
|
||||
@@ -649,7 +787,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should not navigate when no room is selected", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
||||
|
||||
@@ -682,7 +824,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should dispose all room item view models on dispose", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server")!;
|
||||
const vm2 = viewModel.getRoomItemViewModel("!room2:server")!;
|
||||
@@ -698,19 +844,31 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Toast", () => {
|
||||
it("should show toast when SectionCreated event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
});
|
||||
|
||||
it("should show toast when RoomTagged event fires", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.RoomTagged);
|
||||
expect(viewModel.getSnapshot().toast).toBe("chat_moved");
|
||||
});
|
||||
|
||||
it("should clear toast when closeToast is called", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
@@ -720,7 +878,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should auto-close toast after 15 seconds", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
expect(viewModel.getSnapshot().toast).toBe("section_created");
|
||||
@@ -730,7 +892,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should reset the auto-close timer when a new section is created", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.SectionCreated);
|
||||
jest.advanceTimersByTime(10 * 1000);
|
||||
@@ -755,7 +921,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("should show the unread-activity toast when an unread room is below the fold", () => {
|
||||
mockRoom3Unread();
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// room1/room2 visible, room3 (unread) scrolled below the fold.
|
||||
viewModel.updateVisibleFold(1);
|
||||
@@ -765,7 +935,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("should prefer the event toast over the unread-activity toast, restoring it on auto-close", () => {
|
||||
mockRoom3Unread();
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
viewModel.updateVisibleFold(1);
|
||||
expect(viewModel.getSnapshot().toast).toBe("unread_activity");
|
||||
|
||||
@@ -804,7 +978,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should initialize with multiple sections", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.sections).toHaveLength(3);
|
||||
@@ -817,7 +995,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should not be a flat list when multiple sections exist", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().isFlatList).toBe(false);
|
||||
});
|
||||
@@ -832,7 +1014,11 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().isFlatList).toBe(true);
|
||||
expect(viewModel.getSnapshot().sections).toHaveLength(1);
|
||||
@@ -849,14 +1035,22 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().isFlatList).toBe(true);
|
||||
expect(viewModel.getSnapshot().sections).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should exclude favourite and low_priority from filter list", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.filterIds).not.toContain("favourite");
|
||||
@@ -876,7 +1070,11 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const snapshot = viewModel.getSnapshot();
|
||||
expect(snapshot.sections).toHaveLength(1);
|
||||
@@ -884,7 +1082,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should create section header view models on demand", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const headerVM = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
expect(headerVM).toBeDefined();
|
||||
@@ -893,7 +1095,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should reuse section header view models", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const headerVM1 = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
const headerVM2 = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -901,7 +1107,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should hide room IDs when a section is collapsed", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse the favourite section
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -920,7 +1130,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should compute activeRoomIndex relative to visible rooms when a section is collapsed", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse the favourite section (which has 2 rooms: fav1, fav2)
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -943,7 +1157,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should restore room IDs when a section is re-expanded", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
|
||||
@@ -958,7 +1176,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should update sections when room list changes", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const newFav = mkStubRoom("!fav3:server", "Fav 3", matrixClient);
|
||||
|
||||
@@ -978,7 +1200,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should preserve section collapse state across list updates", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse favourites
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -993,7 +1219,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should track section collapse state per space", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse favourites in the home space
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -1048,7 +1278,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should apply filters across all sections", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Only favRoom1 is unread
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||
@@ -1102,7 +1336,11 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(true);
|
||||
});
|
||||
@@ -1116,7 +1354,11 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(false);
|
||||
});
|
||||
@@ -1130,7 +1372,11 @@ describe("RoomListViewModel", () => {
|
||||
],
|
||||
});
|
||||
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
expect(viewModel.getSnapshot().sections.some((s) => s.id === customTag)).toBe(true);
|
||||
});
|
||||
@@ -1138,7 +1384,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Collapse/expand all sections", () => {
|
||||
it("should collapse all sections when Action.RoomListCollapseAllSections is dispatched", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
const chatsHeader = viewModel.getSectionHeaderViewModel(CHATS_TAG);
|
||||
@@ -1156,7 +1406,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should expand all sections when Action.RoomListExpandAllSections is dispatched", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse first
|
||||
const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
@@ -1177,7 +1431,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("notifyCollapseState", () => {
|
||||
it("should dispatch collapseSections=expand when all sections are expanded (default)", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate);
|
||||
@@ -1189,7 +1447,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should dispatch collapseSection=collapse when all sections are collapsed", () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Collapse all sections
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded = false;
|
||||
@@ -1217,7 +1479,11 @@ describe("RoomListViewModel", () => {
|
||||
{ tag: DefaultTagID.LowPriority, rooms: [] },
|
||||
],
|
||||
});
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate);
|
||||
@@ -1231,7 +1497,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
it("should apply sticky room within the correct section", async () => {
|
||||
stubClient();
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
|
||||
// Select favRoom1 (index 0 globally, index 0 in favourites section)
|
||||
jest.spyOn(SDKContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!fav1:server");
|
||||
@@ -1263,7 +1533,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("Drag and drop", () => {
|
||||
beforeEach(() => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
// Ensure section header VMs are created before tests that interact with them
|
||||
viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite);
|
||||
viewModel.getSectionHeaderViewModel(CHATS_TAG);
|
||||
@@ -1367,7 +1641,11 @@ describe("RoomListViewModel", () => {
|
||||
|
||||
describe("changeRoomSection", () => {
|
||||
beforeEach(() => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
spaceStore: SpaceStore.instance,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
mocked(tagRoom).mockClear();
|
||||
});
|
||||
|
||||
@@ -1406,7 +1684,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should scroll a room into view in a flat list", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
const scrollSpy = jest.fn();
|
||||
viewModel.setScrollToIndex(scrollSpy);
|
||||
|
||||
@@ -1432,7 +1714,11 @@ describe("RoomListViewModel", () => {
|
||||
{ tag: CHATS_TAG, rooms: [regularRoom1] },
|
||||
],
|
||||
});
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
const scrollSpy = jest.fn();
|
||||
viewModel.setScrollToIndex(scrollSpy);
|
||||
|
||||
@@ -1448,7 +1734,11 @@ describe("RoomListViewModel", () => {
|
||||
});
|
||||
|
||||
it("should not scroll when the room is not in the current list", async () => {
|
||||
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||
viewModel = new RoomListViewModel({
|
||||
client: matrixClient,
|
||||
roomViewStore: sdkContext.roomViewStore,
|
||||
spaceStore: sdkContext.spaceStore,
|
||||
});
|
||||
const scrollSpy = jest.fn();
|
||||
viewModel.setScrollToIndex(scrollSpy);
|
||||
|
||||
|
||||
@@ -70,7 +70,11 @@ describe("DateSeparatorViewModel", () => {
|
||||
const createViewModel = (
|
||||
props: Partial<typeof defaultProps> & { forExport?: boolean } = {},
|
||||
): DateSeparatorViewModel => {
|
||||
return new DateSeparatorViewModel({ ...defaultProps, ...props });
|
||||
return new DateSeparatorViewModel({
|
||||
...defaultProps,
|
||||
...props,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user