Tidy up snapshot, actions and vm names according to MVVM doc (#32819)

* chore: rename snapshot, actions and vm according to MVVM doc

* doc: add naming conventions

* chore: fix UrlGroupView naming, folders and children

* chore: remove `Example` column

* refactor: rename `UrlPreviewGroupViewPreview` into `UrlPreview`
This commit is contained in:
Florian Duros
2026-03-17 15:20:52 +01:00
committed by GitHub
parent 6339bcda15
commit 136bb78c15
54 changed files with 194 additions and 162 deletions
@@ -10,7 +10,7 @@ import React, { type JSX, createRef, type SyntheticEvent, type MouseEvent, useCa
import { MsgType } from "matrix-js-sdk/src/matrix";
import {
UrlPreviewGroupView,
type UrlPreviewViewSnapshotPreview,
type UrlPreview,
useCreateAutoDisposedViewModel,
EventContentBodyView,
LINKIFIED_DATA_ATTRIBUTE,
@@ -35,14 +35,14 @@ import AccessibleButton from "../elements/AccessibleButton";
import { getParentEventId } from "../../../utils/Reply";
import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
import { type IEventTileOps } from "../rooms/EventTile";
import { UrlPreviewViewModel } from "../../../viewmodels/message-body/UrlPreviewViewModel";
import { UrlPreviewGroupViewModel } from "../../../viewmodels/message-body/UrlPreviewGroupViewModel.ts";
import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";
import ImageView from "../elements/ImageView.tsx";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
const logger = rootLogger.getChild("TextualBody");
type Props = IBodyProps & { urlPreviewViewModel: UrlPreviewViewModel };
type Props = IBodyProps & { urlPreviewViewModel: UrlPreviewGroupViewModel };
class InnerTextualBody extends React.Component<Props> {
private readonly contentRef = createRef<HTMLDivElement>();
@@ -390,7 +390,7 @@ export default function TextualBody(props: IBodyProps): React.ReactElement {
const [mediaVisible] = useMediaVisible(props.mxEvent);
const client = useMatrixClientContext();
const onUrlPreviewImageClicked = useCallback((preview: UrlPreviewViewSnapshotPreview): void => {
const onUrlPreviewImageClicked = useCallback((preview: UrlPreview): void => {
if (!preview.image?.imageFull) {
// Should never get this far, but doesn't hurt to check.
return;
@@ -408,7 +408,7 @@ export default function TextualBody(props: IBodyProps): React.ReactElement {
const vm = useCreateAutoDisposedViewModel(
() =>
new UrlPreviewViewModel({
new UrlPreviewGroupViewModel({
client,
mxEvent: props.mxEvent,
mediaVisible: mediaVisible,
@@ -18,7 +18,7 @@ import { RoomAvatarView } from "../../avatars/RoomAvatarView";
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
import { RoomListViewViewModel } from "../../../../viewmodels/room-list/RoomListViewViewModel";
import { RoomListViewModel } from "../../../../viewmodels/room-list/RoomListViewModel";
/**
* RoomListView component using shared components with proper MVVM pattern.
@@ -27,7 +27,7 @@ export function RoomListView(): JSX.Element {
const matrixClient = useMatrixClientContext();
// Create and auto-dispose ViewModel instance
const vm = useCreateAutoDisposedViewModel(() => new RoomListViewViewModel({ client: matrixClient }));
const vm = useCreateAutoDisposedViewModel(() => new RoomListViewModel({ client: matrixClient }));
// Render avatar for each room - memoized to prevent re-renders
const renderAvatar = useCallback((room: SharedRoom): ReactNode => {
@@ -9,7 +9,7 @@ import {
BaseViewModel,
type UrlPreviewGroupViewSnapshot,
type UrlPreviewGroupViewActions,
type UrlPreviewViewSnapshotPreview,
type UrlPreview,
} from "@element-hq/web-shared-components";
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
import { type IPreviewUrlResponse, type MatrixClient, MatrixError, type MatrixEvent } from "matrix-js-sdk/src/matrix";
@@ -21,14 +21,14 @@ import PlatformPeg from "../../PlatformPeg";
import { thumbHeight } from "../../ImageUtils";
import SettingsStore from "../../settings/SettingsStore";
const logger = rootLogger.getChild("UrlPreviewViewModel");
const logger = rootLogger.getChild("UrlPreviewGroupViewModel");
export interface UrlPreviewViewModelProps {
export interface UrlPreviewGroupViewModelProps {
client: MatrixClient;
mxEvent: MatrixEvent;
mediaVisible: boolean;
visible: boolean;
onImageClicked: (preview: UrlPreviewViewSnapshotPreview) => void;
onImageClicked: (preview: UrlPreview) => void;
}
export const MAX_PREVIEWS_WHEN_LIMITED = 2;
@@ -57,8 +57,8 @@ export enum PreviewVisibility {
/**
* ViewModel for fetching and rendering URL previews for an individual event.
*/
export class UrlPreviewViewModel
extends BaseViewModel<UrlPreviewGroupViewSnapshot, UrlPreviewViewModelProps>
export class UrlPreviewGroupViewModel
extends BaseViewModel<UrlPreviewGroupViewSnapshot, UrlPreviewGroupViewModelProps>
implements UrlPreviewGroupViewActions
{
/**
@@ -89,7 +89,7 @@ export class UrlPreviewViewModel
private static getBaseMetadataFromResponse(
response: IPreviewUrlResponse,
link: string,
): Pick<UrlPreviewViewSnapshotPreview, "title" | "description" | "siteName"> {
): Pick<UrlPreview, "title" | "description" | "siteName"> {
let title =
typeof response["og:title"] === "string" && response["og:title"].trim()
? response["og:title"].trim()
@@ -215,14 +215,14 @@ export class UrlPreviewViewModel
/**
* A cache containing all previously calculated previews.
*/
private readonly previewCache = new Map<string, UrlPreviewViewSnapshotPreview>();
private readonly previewCache = new Map<string, UrlPreview>();
/**
* Called when the user clicks on the preview thumbnail.
*/
public readonly onImageClick: (preview: UrlPreviewViewSnapshotPreview) => void;
public readonly onImageClick: (preview: UrlPreview) => void;
public constructor(props: UrlPreviewViewModelProps) {
public constructor(props: UrlPreviewGroupViewModelProps) {
const storageKey = `hide_preview_${props.mxEvent.getId()}`;
super(props, {
previews: [],
@@ -256,7 +256,7 @@ export class UrlPreviewViewModel
* @returns A Promise that returns the snapshot needed to render the preview, or null
* if the resource could not be previewed.
*/
private async fetchPreview(link: string): Promise<UrlPreviewViewSnapshotPreview | null> {
private async fetchPreview(link: string): Promise<UrlPreview | null> {
const cached = this.previewCache.get(link);
if (cached) {
return cached;
@@ -275,18 +275,18 @@ export class UrlPreviewViewModel
return null;
}
const { title, description, siteName } = UrlPreviewViewModel.getBaseMetadataFromResponse(preview, link);
const { title, description, siteName } = UrlPreviewGroupViewModel.getBaseMetadataFromResponse(preview, link);
const hasImage = preview["og:image"] && typeof preview?.["og:image"] === "string";
// Ensure we have something relevant to render.
// The title must not just be the link, or we must have an image.
if (title === link && !hasImage) {
return null;
}
let image: UrlPreviewViewSnapshotPreview["image"];
let image: UrlPreview["image"];
if (typeof preview["og:image"] === "string" && this.visibility > PreviewVisibility.MediaHidden) {
const media = mediaFromMxc(preview["og:image"], this.client);
const declaredHeight = UrlPreviewViewModel.getNumberFromOpenGraph(preview["og:image:height"]);
const declaredWidth = UrlPreviewViewModel.getNumberFromOpenGraph(preview["og:image:width"]);
const declaredHeight = UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["og:image:height"]);
const declaredWidth = UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["og:image:width"]);
const width = Math.min(declaredWidth ?? PREVIEW_WIDTH, PREVIEW_WIDTH);
const height = thumbHeight(width, declaredHeight, PREVIEW_WIDTH, PREVIEW_WIDTH) ?? PREVIEW_WIDTH;
const thumb = media.getThumbnailOfSourceHttp(PREVIEW_WIDTH, PREVIEW_HEIGHT, "scale");
@@ -297,7 +297,7 @@ export class UrlPreviewViewModel
imageFull: media.srcHttp ?? thumb,
width,
height,
fileSize: UrlPreviewViewModel.getNumberFromOpenGraph(preview["matrix:image:size"]),
fileSize: UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["matrix:image:size"]),
};
}
}
@@ -309,7 +309,7 @@ export class UrlPreviewViewModel
siteName,
showTooltipOnLink: link !== title && PlatformPeg.get()?.needsUrlTooltips(),
image,
} satisfies UrlPreviewViewSnapshotPreview;
} satisfies UrlPreview;
this.previewCache.set(link, result);
return result;
}
@@ -356,7 +356,7 @@ export class UrlPreviewViewModel
* @param eventElement
*/
public async updateEventElement(eventElement: HTMLDivElement): Promise<void> {
const newLinks = UrlPreviewViewModel.findLinks([eventElement]);
const newLinks = UrlPreviewGroupViewModel.findLinks([eventElement]);
// Only recalculate if the set of links has changed.
if (newLinks.some((x) => !this.links.includes(x)) || this.links.some((x) => !newLinks.includes(x))) {
this.links = newLinks;
@@ -11,7 +11,7 @@ import { type Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type IWidget, MatrixCapabilities } from "matrix-widget-api";
import {
BaseViewModel,
type WidgetContextMenuSnapshot,
type WidgetContextMenuViewSnapshot,
WidgetContextMenuView,
type WidgetContextMenuViewModel as WidgetContextMenuViewModelInterface,
} from "@element-hq/web-shared-components";
@@ -56,7 +56,7 @@ const checkRevokeButtonState = (
};
export class WidgetContextMenuViewModel
extends BaseViewModel<WidgetContextMenuSnapshot, WidgetContextMenuViewModelProps>
extends BaseViewModel<WidgetContextMenuViewSnapshot, WidgetContextMenuViewModelProps>
implements WidgetContextMenuViewModelInterface
{
private _app: IWidget;
@@ -96,7 +96,7 @@ export class WidgetContextMenuViewModel
menuDisplayed: boolean,
trigger: ReactNode,
onDeleteClick?: () => void,
): WidgetContextMenuSnapshot => {
): WidgetContextMenuViewSnapshot => {
const showStreamAudioStreamButton = !!getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type);
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, room?.roomId);
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
import {
BaseViewModel,
RoomNotifState,
type RoomListItemSnapshot,
type RoomListItemActions,
type RoomListItemViewSnapshot,
type RoomListItemViewActions,
} from "@element-hq/web-shared-components";
import { RoomEvent } from "matrix-js-sdk/src/matrix";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
@@ -46,11 +46,11 @@ interface RoomItemProps {
/**
* View model for an individual room list item.
* Manages per-room subscriptions and updates only when this specific room's data changes.
* Implements RoomListItemActions to provide interaction callbacks.
* Implements RoomListItemViewActions to provide interaction callbacks.
*/
export class RoomListItemViewModel
extends BaseViewModel<RoomListItemSnapshot, RoomItemProps>
implements RoomListItemActions
extends BaseViewModel<RoomListItemViewSnapshot, RoomItemProps>
implements RoomListItemViewActions
{
private notifState: RoomNotificationState;
/**
@@ -205,7 +205,7 @@ export class RoomListItemViewModel
room: Room,
client: MatrixClient,
notifState: RoomNotificationState,
): RoomListItemSnapshot {
): RoomListItemViewSnapshot {
// Get room tags for menu state
const roomTags = room.tags;
const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import {
BaseViewModel,
type RoomListSnapshot,
type RoomListViewSnapshot,
type FilterId,
type RoomListViewActions,
type RoomListViewState,
@@ -27,7 +27,7 @@ import { SdkContextClass } from "../../contexts/SDKContext";
import { hasCreateRoomRights } from "./utils";
import { keepIfSame } from "../../utils/keepIfSame";
interface RoomListViewViewModelProps {
interface RoomListViewModelProps {
client: MatrixClient;
}
@@ -41,8 +41,8 @@ const filterKeyToIdMap: Map<FilterKey, FilterId> = new Map([
[FilterKey.LowPriorityFilter, "low_priority"],
]);
export class RoomListViewViewModel
extends BaseViewModel<RoomListSnapshot, RoomListViewViewModelProps>
export class RoomListViewModel
extends BaseViewModel<RoomListViewSnapshot, RoomListViewModelProps>
implements RoomListViewActions
{
// State tracking
@@ -54,7 +54,7 @@ export class RoomListViewViewModel
private roomItemViewModels = new Map<string, RoomListItemViewModel>();
private roomsMap = new Map<string, Room>();
public constructor(props: RoomListViewViewModelProps) {
public constructor(props: RoomListViewModelProps) {
const activeSpace = SpaceStore.instance.activeSpaceRoom;
// Get initial rooms
@@ -9,8 +9,8 @@ import { expect } from "@jest/globals";
import type { MockedObject } from "jest-mock";
import type { MatrixClient, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
import { UrlPreviewViewModel } from "../../../src/viewmodels/message-body/UrlPreviewViewModel";
import type { UrlPreviewViewSnapshotPreview } from "@element-hq/web-shared-components";
import { UrlPreviewGroupViewModel } from "../../../src/viewmodels/message-body/UrlPreviewGroupViewModel";
import type { UrlPreview } from "@element-hq/web-shared-components";
import { getMockClientWithEventEmitter, mkEvent } from "../../test-utils";
const IMAGE_MXC = "mxc://example.org/abc";
@@ -23,16 +23,16 @@ const BASIC_PREVIEW_OGDATA = {
};
function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible: true }): {
vm: UrlPreviewViewModel;
vm: UrlPreviewGroupViewModel;
client: MockedObject<MatrixClient>;
onImageClicked: jest.Mock<void, [UrlPreviewViewSnapshotPreview]>;
onImageClicked: jest.Mock<void, [UrlPreview]>;
} {
const client = getMockClientWithEventEmitter({
getUrlPreview: jest.fn(),
mxcUrlToHttp: jest.fn(),
});
const onImageClicked = jest.fn<void, [UrlPreviewViewSnapshotPreview]>();
const vm = new UrlPreviewViewModel({
const onImageClicked = jest.fn<void, [UrlPreview]>();
const vm = new UrlPreviewGroupViewModel({
client,
mediaVisible,
visible,
@@ -48,7 +48,7 @@ function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible:
return { vm, client, onImageClicked };
}
describe("UrlPreviewViewModel", () => {
describe("UrlPreviewGroupViewModel", () => {
it("should return no previews by default", () => {
expect(getViewModel().vm.getSnapshot()).toMatchSnapshot();
});
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:description': 'A description',\\n 'og:title': ''\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:description': 'A description',\\n 'og:title': ''\\n} 1`] = `
{
"description": undefined,
"image": undefined,
@@ -11,7 +11,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
}
`;
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:site_name': 'Site name',\\n 'og:title': ''\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:site_name': 'Site name',\\n 'og:title': ''\\n} 1`] = `
{
"description": undefined,
"image": undefined,
@@ -22,7 +22,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
}
`;
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Basic title'\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Basic title'\\n} 1`] = `
{
"description": undefined,
"image": undefined,
@@ -33,7 +33,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
}
`;
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Cool blog',\\n 'og:site_name': 'Cool site'\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Cool blog',\\n 'og:site_name': 'Cool site'\\n} 1`] = `
{
"description": undefined,
"image": undefined,
@@ -44,7 +44,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
}
`;
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 1024,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 1024,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
{
"description": undefined,
"image": {
@@ -61,7 +61,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
}
`;
exports[`UrlPreviewViewModel should deduplicate multiple versions of the same URL 1`] = `
exports[`UrlPreviewGroupViewModel should deduplicate multiple versions of the same URL 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -80,7 +80,7 @@ exports[`UrlPreviewViewModel should deduplicate multiple versions of the same UR
}
`;
exports[`UrlPreviewViewModel should handle being hidden and shown by the user 1`] = `
exports[`UrlPreviewGroupViewModel should handle being hidden and shown by the user 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -90,7 +90,7 @@ exports[`UrlPreviewViewModel should handle being hidden and shown by the user 1`
}
`;
exports[`UrlPreviewViewModel should handle being hidden and shown by the user 2`] = `
exports[`UrlPreviewGroupViewModel should handle being hidden and shown by the user 2`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -109,7 +109,7 @@ exports[`UrlPreviewViewModel should handle being hidden and shown by the user 2`
}
`;
exports[`UrlPreviewViewModel should hide preview when invisible 1`] = `
exports[`UrlPreviewGroupViewModel should hide preview when invisible 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -119,7 +119,7 @@ exports[`UrlPreviewViewModel should hide preview when invisible 1`] = `
}
`;
exports[`UrlPreviewViewModel should ignore failed previews 1`] = `
exports[`UrlPreviewGroupViewModel should ignore failed previews 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -129,7 +129,7 @@ exports[`UrlPreviewViewModel should ignore failed previews 1`] = `
}
`;
exports[`UrlPreviewViewModel should ignore media when mediaVisible is false 1`] = `
exports[`UrlPreviewGroupViewModel should ignore media when mediaVisible is false 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -148,7 +148,7 @@ exports[`UrlPreviewViewModel should ignore media when mediaVisible is false 1`]
}
`;
exports[`UrlPreviewViewModel should preview a URL with media 1`] = `
exports[`UrlPreviewGroupViewModel should preview a URL with media 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -173,7 +173,7 @@ exports[`UrlPreviewViewModel should preview a URL with media 1`] = `
}
`;
exports[`UrlPreviewViewModel should preview a single valid URL 1`] = `
exports[`UrlPreviewGroupViewModel should preview a single valid URL 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -192,7 +192,7 @@ exports[`UrlPreviewViewModel should preview a single valid URL 1`] = `
}
`;
exports[`UrlPreviewViewModel should return no previews by default 1`] = `
exports[`UrlPreviewGroupViewModel should return no previews by default 1`] = `
{
"compactLayout": false,
"overPreviewLimit": false,
@@ -16,7 +16,7 @@ import dispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import { SdkContextClass } from "../../../src/contexts/SDKContext";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { RoomListViewViewModel } from "../../../src/viewmodels/room-list/RoomListViewViewModel";
import { RoomListViewModel } from "../../../src/viewmodels/room-list/RoomListViewModel";
import { hasCreateRoomRights } from "../../../src/viewmodels/room-list/utils";
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
@@ -25,12 +25,12 @@ jest.mock("../../../src/viewmodels/room-list/utils", () => ({
hasAccessToNotificationMenu: jest.fn().mockReturnValue(true),
}));
describe("RoomListViewViewModel", () => {
describe("RoomListViewModel", () => {
let matrixClient: MatrixClient;
let room1: Room;
let room2: Room;
let room3: Room;
let viewModel: RoomListViewViewModel;
let viewModel: RoomListViewModel;
beforeEach(() => {
matrixClient = createTestClient();
@@ -63,7 +63,7 @@ describe("RoomListViewViewModel", () => {
describe("Initialization", () => {
it("should initialize with correct snapshot", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const snapshot = viewModel.getSnapshot();
expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]);
@@ -80,7 +80,7 @@ describe("RoomListViewViewModel", () => {
rooms: [],
});
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([]);
expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true);
@@ -88,7 +88,7 @@ describe("RoomListViewViewModel", () => {
it("should set canCreateRoom based on user rights", () => {
mocked(hasCreateRoomRights).mockReturnValue(true);
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
expect(viewModel.getSnapshot().canCreateRoom).toBe(true);
});
@@ -96,7 +96,7 @@ describe("RoomListViewViewModel", () => {
describe("Room list updates", () => {
it("should update room list when ListsUpdate event fires", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient);
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -116,7 +116,7 @@ describe("RoomListViewViewModel", () => {
it("should update loading state when ListsLoaded event fires", () => {
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
expect(viewModel.getSnapshot().isLoadingRooms).toBe(true);
@@ -127,7 +127,7 @@ describe("RoomListViewViewModel", () => {
// 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 RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
// Create view model for room1
const room1VM = viewModel.getRoomItemViewModel("!room1:server");
@@ -142,7 +142,7 @@ describe("RoomListViewViewModel", () => {
describe("Space switching", () => {
it("should update room list when space changes", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const spaceRoomList = [room1, room2];
@@ -160,7 +160,7 @@ describe("RoomListViewViewModel", () => {
});
it("should clear view models when space changes", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
// Get view models for visible rooms
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
@@ -184,7 +184,7 @@ describe("RoomListViewViewModel", () => {
describe("Active room tracking", () => {
it("should update active room index when room is selected", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -200,7 +200,7 @@ describe("RoomListViewViewModel", () => {
});
it("should return undefined active room index when no room is selected", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
@@ -218,7 +218,7 @@ describe("RoomListViewViewModel", () => {
describe("Sticky room behavior", () => {
it("should keep selected room at same index when room list updates", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
// Select room at index 1
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -244,7 +244,7 @@ describe("RoomListViewViewModel", () => {
});
it("should not apply sticky behavior when user changes rooms", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
// Select room at index 1
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -270,7 +270,7 @@ describe("RoomListViewViewModel", () => {
describe("Filters", () => {
it("should toggle filter on", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
expect(viewModel.getSnapshot().activeFilterId).toBeUndefined();
@@ -287,7 +287,7 @@ describe("RoomListViewViewModel", () => {
});
it("should toggle filter off", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
// Turn filter on
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
@@ -317,7 +317,7 @@ describe("RoomListViewViewModel", () => {
describe("Room item view models", () => {
it("should create room item view model on demand", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const itemViewModel = viewModel.getRoomItemViewModel("!room1:server");
@@ -326,7 +326,7 @@ describe("RoomListViewViewModel", () => {
});
it("should reuse existing room item view model", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server");
const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server");
@@ -335,7 +335,7 @@ describe("RoomListViewViewModel", () => {
});
it("should throw error when requesting view model for non-existent room", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
expect(() => {
viewModel.getRoomItemViewModel("!nonexistent:server");
@@ -343,7 +343,7 @@ describe("RoomListViewViewModel", () => {
});
it("should dispose view models for rooms no longer visible", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
const vm2 = viewModel.getRoomItemViewModel("!room2:server");
@@ -366,7 +366,7 @@ describe("RoomListViewViewModel", () => {
describe("Room creation", () => {
it("should dispatch CreateChat action when createChatRoom is called", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const dispatchSpy = jest.spyOn(dispatcher, "fire");
@@ -376,7 +376,7 @@ describe("RoomListViewViewModel", () => {
});
it("should dispatch CreateRoom action without parent space", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
@@ -391,7 +391,7 @@ describe("RoomListViewViewModel", () => {
const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient);
jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom);
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
@@ -411,7 +411,7 @@ describe("RoomListViewViewModel", () => {
});
it("should navigate to next room when delta is 1", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
@@ -434,7 +434,7 @@ describe("RoomListViewViewModel", () => {
});
it("should navigate to previous room when delta is -1", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
@@ -457,7 +457,7 @@ describe("RoomListViewViewModel", () => {
});
it("should wrap around to last room when navigating backwards from first room", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
@@ -480,7 +480,7 @@ describe("RoomListViewViewModel", () => {
});
it("should not navigate when current room is not found", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server");
@@ -504,7 +504,7 @@ describe("RoomListViewViewModel", () => {
});
it("should not navigate when no room is selected", async () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
@@ -529,7 +529,7 @@ describe("RoomListViewViewModel", () => {
describe("Cleanup", () => {
it("should dispose all room item view models on dispose", () => {
viewModel = new RoomListViewViewModel({ client: matrixClient });
viewModel = new RoomListViewModel({ client: matrixClient });
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
const vm2 = viewModel.getRoomItemViewModel("!room2:server");