From aa821a5b6fbe291fafaf232849dd23e57a39a1e7 Mon Sep 17 00:00:00 2001
From: David Langley
Date: Wed, 16 Apr 2025 09:36:34 +0100
Subject: [PATCH 1/8] Remove virtual rooms (#29635)
* Remove virtual rooms from the timelinePanel and RoomView
* Remove VoipUserMapper
* Remove some unneeded imports
* Remove tovirtual slash command test
* Remove getSupportsVirtualRooms and virtualLookup
* lint
* Remove PROTOCOL_SIP_NATIVE
* Remove native/virtual looks fields and fix tests
* Remove unused lookup fields
---
src/@types/global.d.ts | 2 -
src/LegacyCallHandler.tsx | 104 +-----
src/Notifier.ts | 11 +-
src/SlashCommands.tsx | 23 --
src/VoipUserMapper.ts | 150 ---------
src/call-types.ts | 4 -
src/components/structures/RoomView.tsx | 12 -
src/components/structures/TimelinePanel.tsx | 257 +++------------
src/createRoom.ts | 31 --
src/i18n/strings/en_EN.json | 2 -
src/stores/room-list/RoomListStore.ts | 10 -
.../room-list/filters/VisibilityProvider.ts | 13 -
test/unit-tests/LegacyCallHandler-test.ts | 76 +----
test/unit-tests/SlashCommands-test.tsx | 41 ---
.../components/structures/RoomView-test.tsx | 23 --
.../structures/TimelinePanel-test.tsx | 309 ------------------
.../__snapshots__/RoomView-test.tsx.snap | 4 +-
.../filters/VisibilityProvider-test.ts | 43 ---
18 files changed, 68 insertions(+), 1047 deletions(-)
delete mode 100644 src/VoipUserMapper.ts
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 3bbeda067b..344059fee4 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -29,7 +29,6 @@ import type LegacyCallHandler from "../LegacyCallHandler";
import type UserActivity from "../UserActivity";
import { type ModalWidgetStore } from "../stores/ModalWidgetStore";
import { type WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
-import type VoipUserMapper from "../VoipUserMapper";
import { type SpaceStoreClass } from "../stores/spaces/SpaceStore";
import type TypingStore from "../stores/TypingStore";
import { type EventIndexPeg } from "../indexing/EventIndexPeg";
@@ -113,7 +112,6 @@ declare global {
mxLegacyCallHandler: LegacyCallHandler;
mxUserActivity: UserActivity;
mxModalWidgetStore: ModalWidgetStore;
- mxVoipUserMapper: VoipUserMapper;
mxSpaceStore: SpaceStoreClass;
mxVoiceRecordingStore: VoiceRecordingStore;
mxTypingStore: TypingStore;
diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx
index 608d396dda..97cb478512 100644
--- a/src/LegacyCallHandler.tsx
+++ b/src/LegacyCallHandler.tsx
@@ -39,14 +39,12 @@ import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
import { UIFeature } from "./settings/UIFeature";
import { Action } from "./dispatcher/actions";
-import VoipUserMapper from "./VoipUserMapper";
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "./widgets/ManagedHybrid";
import SdkConfig from "./SdkConfig";
import { ensureDMExists } from "./createRoom";
import { Container, WidgetLayoutStore } from "./stores/widgets/WidgetLayoutStore";
import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from "./toasts/IncomingLegacyCallToast";
import ToastStore from "./stores/ToastStore";
-import Resend from "./Resend";
import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { InviteKind } from "./components/views/dialogs/InviteDialogTypes";
import { type OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
@@ -59,8 +57,6 @@ import { Jitsi } from "./widgets/Jitsi.ts";
export const PROTOCOL_PSTN = "m.protocol.pstn";
export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn";
-export const PROTOCOL_SIP_NATIVE = "im.vector.protocol.sip_native";
-export const PROTOCOL_SIP_VIRTUAL = "im.vector.protocol.sip_virtual";
const CHECK_PROTOCOLS_ATTEMPTS = 3;
@@ -107,27 +103,9 @@ const debuglog = (...args: any[]): void => {
}
};
-interface ThirdpartyLookupResponseFields {
- /* eslint-disable camelcase */
-
- // im.vector.sip_native
- virtual_mxid?: string;
- is_virtual?: boolean;
-
- // im.vector.sip_virtual
- native_mxid?: string;
- is_native?: boolean;
-
- // common
- lookup_success?: boolean;
-
- /* eslint-enable camelcase */
-}
-
interface ThirdpartyLookupResponse {
userid: string;
protocol: string;
- fields: ThirdpartyLookupResponseFields;
}
export enum LegacyCallHandlerEvent {
@@ -158,7 +136,6 @@ export default class LegacyCallHandler extends TypedEventEmitter(); // callId (target) -> call (transferee)
private supportsPstnProtocol: boolean | null = null;
private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
- private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
// Map of the asserted identity users after we've looked them up using the API.
// We need to be be able to determine the mapped room synchronously, so we
@@ -179,8 +156,7 @@ export default class LegacyCallHandler extends TypedEventEmitter {
try {
return await MatrixClientPeg.safeGet().getThirdpartyUser(
@@ -323,28 +289,6 @@ export default class LegacyCallHandler extends TypedEventEmitter {
- try {
- return await MatrixClientPeg.safeGet().getThirdpartyUser(PROTOCOL_SIP_VIRTUAL, {
- native_mxid: nativeMxid,
- });
- } catch (e) {
- logger.warn("Failed to query SIP identity for user", e);
- return Promise.resolve([]);
- }
- }
-
- public async sipNativeLookup(virtualMxid: string): Promise {
- try {
- return await MatrixClientPeg.safeGet().getThirdpartyUser(PROTOCOL_SIP_NATIVE, {
- virtual_mxid: virtualMxid,
- });
- } catch (e) {
- logger.warn("Failed to query identity for SIP user", e);
- return Promise.resolve([]);
- }
- }
-
private onCallIncoming = (call: MatrixCall): void => {
// if the runtime env doesn't do VoIP, stop here.
if (!MatrixClientPeg.get()?.supportsVoip()) {
@@ -537,24 +481,16 @@ export default class LegacyCallHandler extends TypedEventEmitter {
const cli = MatrixClientPeg.safeGet();
- const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
- logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
-
- // If we're using a virtual room nd there are any events pending, try to resend them,
- // otherwise the call will fail and because its a virtual room, the user won't be able
- // to see it to either retry or clear the pending events. There will only be call events
- // in this queue, and since we're about to place a new call, they can only be events from
- // previous calls that are probably stale by now, so just cancel them.
- if (mappedRoomId !== roomId) {
- const mappedRoom = cli.getRoom(mappedRoomId);
- if (mappedRoom?.getPendingEvents().length) {
- Resend.cancelUnsentEvents(mappedRoom);
- }
- }
const timeUntilTurnCresExpire = cli.getTurnServersExpiry() - Date.now();
logger.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
- const call = cli.createCall(mappedRoomId)!;
+ const call = cli.createCall(roomId)!;
try {
this.addCallForRoom(roomId, call);
@@ -978,19 +900,7 @@ export default class LegacyCallHandler extends TypedEventEmitter 0 && nativeLookupResults[0].fields.lookup_success;
- nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId;
- logger.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId);
- } else {
- nativeUserId = userId;
- }
-
- const roomId = await ensureDMExists(MatrixClientPeg.safeGet(), nativeUserId);
+ const roomId = await ensureDMExists(MatrixClientPeg.safeGet(), userId);
if (!roomId) {
throw new Error("Failed to ensure DM exists for dialing number");
}
diff --git a/src/Notifier.ts b/src/Notifier.ts
index b24e146fbc..7dce26d6bd 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -43,8 +43,6 @@ import { isPushNotifyDisabled } from "./settings/controllers/NotificationControl
import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
-import LegacyCallHandler from "./LegacyCallHandler";
-import VoipUserMapper from "./VoipUserMapper";
import { SdkContextClass } from "./contexts/SDKContext";
import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded } from "./utils/notifications";
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
@@ -447,14 +445,7 @@ class NotifierClass extends TypedEventEmitter {
- return success(
- (async (): Promise => {
- const room = await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(roomId);
- if (!room) throw new UserFriendlyError("slash_command|tovirtual_not_found");
- dis.dispatch({
- action: Action.ViewRoom,
- room_id: room.roomId,
- metricsTrigger: "SlashCommand",
- metricsViaKeyboard: true,
- });
- })(),
- );
- },
- }),
new Command({
command: "query",
description: _td("slash_command|query"),
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
deleted file mode 100644
index c2a7810d96..0000000000
--- a/src/VoipUserMapper.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2021 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { type Room, EventType } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
-import { logger } from "matrix-js-sdk/src/logger";
-
-import { ensureVirtualRoomExists } from "./createRoom";
-import { MatrixClientPeg } from "./MatrixClientPeg";
-import DMRoomMap from "./utils/DMRoomMap";
-import LegacyCallHandler from "./LegacyCallHandler";
-import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
-import { findDMForUser } from "./utils/dm/findDMForUser";
-
-// Functions for mapping virtual users & rooms. Currently the only lookup
-// is sip virtual: there could be others in the future.
-
-export default class VoipUserMapper {
- // We store mappings of virtual -> native room IDs here until the local echo for the
- // account data arrives.
- private virtualToNativeRoomIdCache = new Map();
-
- public static sharedInstance(): VoipUserMapper {
- if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
- return window.mxVoipUserMapper;
- }
-
- private async userToVirtualUser(userId: string): Promise {
- const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
- if (results.length === 0 || !results[0].fields.lookup_success) return null;
- return results[0].userid;
- }
-
- private async getVirtualUserForRoom(roomId: string): Promise {
- const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
- if (!userId) return null;
-
- const virtualUser = await this.userToVirtualUser(userId);
- if (!virtualUser) return null;
-
- return virtualUser;
- }
-
- public async getOrCreateVirtualRoomForRoom(roomId: string): Promise {
- const virtualUser = await this.getVirtualUserForRoom(roomId);
- if (!virtualUser) return null;
-
- const cli = MatrixClientPeg.safeGet();
- const virtualRoomId = await ensureVirtualRoomExists(cli, virtualUser, roomId);
- cli.setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
- native_room: roomId,
- });
-
- this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
-
- return virtualRoomId;
- }
-
- /**
- * Gets the ID of the virtual room for a room, or null if the room has no
- * virtual room
- */
- public async getVirtualRoomForRoom(roomId: string): Promise {
- const virtualUser = await this.getVirtualUserForRoom(roomId);
- if (!virtualUser) return undefined;
-
- return findDMForUser(MatrixClientPeg.safeGet(), virtualUser);
- }
-
- public nativeRoomForVirtualRoom(roomId: string): string | null {
- const cachedNativeRoomId = this.virtualToNativeRoomIdCache.get(roomId);
- if (cachedNativeRoomId) {
- logger.log(
- "Returning native room ID " + cachedNativeRoomId + " for virtual room ID " + roomId + " from cache",
- );
- return cachedNativeRoomId;
- }
-
- const cli = MatrixClientPeg.safeGet();
- const virtualRoom = cli.getRoom(roomId);
- if (!virtualRoom) return null;
- const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
- if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null;
- const nativeRoomID = virtualRoomEvent.getContent()["native_room"];
- const nativeRoom = cli.getRoom(nativeRoomID);
- if (!nativeRoom || nativeRoom.getMyMembership() !== KnownMembership.Join) return null;
-
- return nativeRoomID;
- }
-
- public isVirtualRoom(room: Room): boolean {
- if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
-
- if (this.virtualToNativeRoomIdCache.has(room.roomId)) return true;
-
- // also look in the create event for the claimed native room ID, which is the only
- // way we can recognise a virtual room we've created when it first arrives down
- // our stream. We don't trust this in general though, as it could be faked by an
- // inviter: our main source of truth is the DM state.
- const roomCreateEvent = room.currentState.getStateEvents(EventType.RoomCreate, "");
- if (!roomCreateEvent || !roomCreateEvent.getContent()) return false;
- // we only look at this for rooms we created (so inviters can't just cause rooms
- // to be invisible)
- if (roomCreateEvent.getSender() !== MatrixClientPeg.safeGet().getUserId()) return false;
- const claimedNativeRoomId = roomCreateEvent.getContent()[VIRTUAL_ROOM_EVENT_TYPE];
- return Boolean(claimedNativeRoomId);
- }
-
- public async onNewInvitedRoom(invitedRoom: Room): Promise {
- if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
-
- const inviterId = invitedRoom.getDMInviter();
- if (!inviterId) {
- logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
- }
-
- logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
- const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
- if (result.length === 0) {
- return;
- }
-
- if (result[0].fields.is_virtual) {
- const cli = MatrixClientPeg.safeGet();
- const nativeUser = result[0].userid;
- const nativeRoom = findDMForUser(cli, nativeUser);
- if (nativeRoom) {
- // It's a virtual room with a matching native room, so set the room account data. This
- // will make sure we know where how to map calls and also allow us know not to display
- // it in the future.
- cli.setRoomAccountData(invitedRoom.roomId, VIRTUAL_ROOM_EVENT_TYPE, {
- native_room: nativeRoom.roomId,
- });
- // also auto-join the virtual room if we have a matching native room
- // (possibly we should only join if we've also joined the native room, then we'd also have
- // to make sure we joined virtual rooms on joining a native one)
- cli.joinRoom(invitedRoom.roomId);
-
- // also put this room in the virtual room ID cache so isVirtualRoom return the right answer
- // in however long it takes for the echo of setAccountData to come down the sync
- this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
- }
- }
- }
-}
diff --git a/src/call-types.ts b/src/call-types.ts
index 2a263174ef..6586bcf3b9 100644
--- a/src/call-types.ts
+++ b/src/call-types.ts
@@ -6,10 +6,6 @@ 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.
*/
-// Event type for room account data and room creation content used to mark rooms as virtual rooms
-// (and store the ID of their native room)
-export const VIRTUAL_ROOM_EVENT_TYPE = "im.vector.is_virtual_room";
-
export const JitsiCallMemberEventType = "io.element.video.member";
export interface JitsiCallMemberContent {
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 7ad4abe15d..0dbc6aaf3f 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -120,8 +120,6 @@ import { isVideoRoom } from "../../utils/video-rooms";
import { SDKContext } from "../../contexts/SDKContext";
import { RoomSearchView } from "./RoomSearchView";
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
-import VoipUserMapper from "../../VoipUserMapper";
-import { isCallEvent } from "./LegacyCallEventGrouper";
import { WidgetType } from "../../widgets/WidgetType";
import WidgetUtils from "../../utils/WidgetUtils";
import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
@@ -165,7 +163,6 @@ export { MainSplitContentType };
export interface IRoomState {
room?: Room;
- virtualRoom?: Room;
roomId?: string;
roomAlias?: string;
roomLoading: boolean;
@@ -1344,12 +1341,6 @@ export class RoomView extends React.Component {
return this.messagePanel.canResetTimeline();
};
- private loadVirtualRoom = async (room?: Room): Promise => {
- const virtualRoom = room?.roomId && (await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(room?.roomId));
-
- this.setState({ virtualRoom: virtualRoom || undefined });
- };
-
// called when state.room is first initialised (either at initial load,
// after a successful peek, or after we join the room).
private onRoomLoaded = (room: Room): void => {
@@ -1362,7 +1353,6 @@ export class RoomView extends React.Component {
this.calculateRecommendedVersion(room);
this.updatePermissions(room);
this.checkWidgets(room);
- this.loadVirtualRoom(room);
this.updateRoomEncrypted(room);
if (
@@ -2444,8 +2434,6 @@ export class RoomView extends React.Component {
{
}
};
-const overlaysBefore = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
- overlayEvent.localTimestamp < mainEvent.localTimestamp;
-
-const overlaysAfter = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
- overlayEvent.localTimestamp >= mainEvent.localTimestamp;
-
interface IProps {
// The js-sdk EventTimelineSet object for the timeline sequence we are
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
// that room.
timelineSet: EventTimelineSet;
- // overlay events from a second timelineset on the main timeline
- // added to support virtual rooms
- // events from the overlay timeline set will be added by localTimestamp
- // into the main timeline
- overlayTimelineSet?: EventTimelineSet;
- // filter events from overlay timeline
- overlayTimelineSetFilter?: (event: MatrixEvent) => boolean;
showReadReceipts?: boolean;
// Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts?: boolean;
@@ -251,7 +238,6 @@ class TimelinePanel extends React.Component {
private readonly messagePanel = createRef();
private dispatcherRef?: string;
private timelineWindow?: TimelineWindow;
- private overlayTimelineWindow?: TimelineWindow;
private unmounted = false;
private readReceiptActivityTimer: Timer | null = null;
private readMarkerActivityTimer: Timer | null = null;
@@ -349,16 +335,12 @@ class TimelinePanel extends React.Component {
const differentEventId = prevProps.eventId != this.props.eventId;
const differentHighlightedEventId = prevProps.highlightedEventId != this.props.highlightedEventId;
const differentAvoidJump = prevProps.eventScrollIntoView && !this.props.eventScrollIntoView;
- const differentOverlayTimeline = prevProps.overlayTimelineSet !== this.props.overlayTimelineSet;
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
logger.log(
`TimelinePanel switching to eventId ${this.props.eventId} (was ${prevProps.eventId}), ` +
`scrollIntoView: ${this.props.eventScrollIntoView} (was ${prevProps.eventScrollIntoView})`,
);
this.initTimeline(this.props);
- } else if (differentOverlayTimeline) {
- logger.log(`TimelinePanel updating overlay timeline.`);
- this.initTimeline(this.props);
}
}
@@ -509,24 +491,9 @@ class TimelinePanel extends React.Component {
// this particular event should be the first or last to be unpaginated.
const eventId = scrollToken;
- // The event in question could belong to either the main timeline or
- // overlay timeline; let's check both
const mainEvents = this.timelineWindow!.getEvents();
- const overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
- let marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
- let overlayMarker: number;
- if (marker === -1) {
- // The event must be from the overlay timeline instead
- overlayMarker = overlayEvents.findIndex((ev) => ev.getId() === eventId);
- marker = backwards
- ? findLastIndex(mainEvents, (ev) => overlaysAfter(overlayEvents[overlayMarker], ev))
- : mainEvents.findIndex((ev) => overlaysBefore(overlayEvents[overlayMarker], ev));
- } else {
- overlayMarker = backwards
- ? findLastIndex(overlayEvents, (ev) => overlaysBefore(ev, mainEvents[marker]))
- : overlayEvents.findIndex((ev) => overlaysAfter(ev, mainEvents[marker]));
- }
+ const marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
// The number of events to unpaginate from the main timeline
let count: number;
@@ -536,24 +503,11 @@ class TimelinePanel extends React.Component {
count = backwards ? marker + 1 : mainEvents.length - marker;
}
- // The number of events to unpaginate from the overlay timeline
- let overlayCount: number;
- if (overlayMarker === -1) {
- overlayCount = 0;
- } else {
- overlayCount = backwards ? overlayMarker + 1 : overlayEvents.length - overlayMarker;
- }
-
if (count > 0) {
debuglog("Unpaginating", count, "in direction", dir);
this.timelineWindow!.unpaginate(count, backwards);
}
- if (overlayCount > 0) {
- debuglog("Unpaginating", count, "from overlay timeline in direction", dir);
- this.overlayTimelineWindow!.unpaginate(overlayCount, backwards);
- }
-
const { events, liveEvents } = this.getEvents();
this.buildLegacyCallEventGroupers(events);
this.setState({
@@ -610,10 +564,6 @@ class TimelinePanel extends React.Component {
return false;
}
- if (this.overlayTimelineWindow) {
- await this.extendOverlayWindowToCoverMainWindow();
- }
-
debuglog("paginate complete backwards:" + backwards + "; success:" + r);
const { events, liveEvents } = this.getEvents();
@@ -705,10 +655,7 @@ class TimelinePanel extends React.Component {
data: IRoomTimelineData,
): void => {
// ignore events for other timeline sets
- if (
- data.timeline.getTimelineSet() !== this.props.timelineSet &&
- data.timeline.getTimelineSet() !== this.props.overlayTimelineSet
- ) {
+ if (data.timeline.getTimelineSet() !== this.props.timelineSet) {
return;
}
@@ -748,69 +695,60 @@ class TimelinePanel extends React.Component {
// timeline window.
//
// see https://github.com/vector-im/vector-web/issues/1035
- this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false)
- .then(() => {
- if (this.overlayTimelineWindow) {
- return this.overlayTimelineWindow.paginate(EventTimeline.FORWARDS, 1, false);
+ this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
+ if (this.unmounted) {
+ return;
+ }
+
+ const { events, liveEvents } = this.getEvents();
+ this.buildLegacyCallEventGroupers(events);
+ const lastLiveEvent = liveEvents[liveEvents.length - 1];
+
+ const updatedState: Partial = {
+ events,
+ liveEvents,
+ };
+
+ let callRMUpdated = false;
+ if (this.props.manageReadMarkers) {
+ // when a new event arrives when the user is not watching the
+ // window, but the window is in its auto-scroll mode, make sure the
+ // read marker is visible.
+ //
+ // We ignore events we have sent ourselves; we don't want to see the
+ // read-marker when a remote echo of an event we have just sent takes
+ // more than the timeout on userActiveRecently.
+ //
+ const myUserId = MatrixClientPeg.safeGet().credentials.userId;
+ callRMUpdated = false;
+ if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
+ updatedState.readMarkerVisible = true;
+ } else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
+ // we know we're stuckAtBottom, so we can advance the RM
+ // immediately, to save a later render cycle
+
+ this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
+ updatedState.readMarkerVisible = false;
+ updatedState.readMarkerEventId = lastLiveEvent.getId();
+ callRMUpdated = true;
}
- })
- .then(() => {
- if (this.unmounted) {
- return;
+ }
+
+ this.setState(updatedState as IState, () => {
+ this.messagePanel.current?.updateTimelineMinHeight();
+ if (callRMUpdated) {
+ this.props.onReadMarkerUpdated?.();
}
-
- const { events, liveEvents } = this.getEvents();
- this.buildLegacyCallEventGroupers(events);
- const lastLiveEvent = liveEvents[liveEvents.length - 1];
-
- const updatedState: Partial = {
- events,
- liveEvents,
- };
-
- let callRMUpdated = false;
- if (this.props.manageReadMarkers) {
- // when a new event arrives when the user is not watching the
- // window, but the window is in its auto-scroll mode, make sure the
- // read marker is visible.
- //
- // We ignore events we have sent ourselves; we don't want to see the
- // read-marker when a remote echo of an event we have just sent takes
- // more than the timeout on userActiveRecently.
- //
- const myUserId = MatrixClientPeg.safeGet().credentials.userId;
- callRMUpdated = false;
- if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
- updatedState.readMarkerVisible = true;
- } else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
- // we know we're stuckAtBottom, so we can advance the RM
- // immediately, to save a later render cycle
-
- this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
- updatedState.readMarkerVisible = false;
- updatedState.readMarkerEventId = lastLiveEvent.getId();
- callRMUpdated = true;
- }
- }
-
- this.setState(updatedState as IState, () => {
- this.messagePanel.current?.updateTimelineMinHeight();
- if (callRMUpdated) {
- this.props.onReadMarkerUpdated?.();
- }
- });
});
+ });
};
private hasTimelineSetFor(roomId: string | undefined): boolean {
- return (
- (roomId !== undefined && roomId === this.props.timelineSet.room?.roomId) ||
- roomId === this.props.overlayTimelineSet?.room?.roomId
- );
+ return roomId !== undefined && roomId === this.props.timelineSet.room?.roomId;
}
private onRoomTimelineReset = (room: Room | undefined, timelineSet: EventTimelineSet): void => {
- if (timelineSet !== this.props.timelineSet && timelineSet !== this.props.overlayTimelineSet) return;
+ if (timelineSet !== this.props.timelineSet) return;
if (this.canResetTimeline()) {
this.loadTimeline();
@@ -1475,48 +1413,6 @@ class TimelinePanel extends React.Component {
});
}
- private async extendOverlayWindowToCoverMainWindow(): Promise {
- const mainWindow = this.timelineWindow!;
- const overlayWindow = this.overlayTimelineWindow!;
- const mainEvents = mainWindow.getEvents();
-
- if (mainEvents.length > 0) {
- let paginationRequests: Promise[];
-
- // Keep paginating until the main window is covered
- do {
- paginationRequests = [];
- const overlayEvents = overlayWindow.getEvents();
-
- if (
- overlayWindow.canPaginate(EventTimeline.BACKWARDS) &&
- (overlayEvents.length === 0 ||
- overlaysAfter(overlayEvents[0], mainEvents[0]) ||
- !mainWindow.canPaginate(EventTimeline.BACKWARDS))
- ) {
- // Paginating backwards could reveal more events to be overlaid in the main window
- paginationRequests.push(
- this.onPaginationRequest(overlayWindow, EventTimeline.BACKWARDS, PAGINATE_SIZE),
- );
- }
-
- if (
- overlayWindow.canPaginate(EventTimeline.FORWARDS) &&
- (overlayEvents.length === 0 ||
- overlaysBefore(overlayEvents.at(-1)!, mainEvents.at(-1)!) ||
- !mainWindow.canPaginate(EventTimeline.FORWARDS))
- ) {
- // Paginating forwards could reveal more events to be overlaid in the main window
- paginationRequests.push(
- this.onPaginationRequest(overlayWindow, EventTimeline.FORWARDS, PAGINATE_SIZE),
- );
- }
-
- await Promise.all(paginationRequests);
- } while (paginationRequests.length > 0);
- }
- }
-
/**
* (re)-load the event timeline, and initialise the scroll state, centered
* around the given event.
@@ -1536,9 +1432,6 @@ class TimelinePanel extends React.Component {
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number, scrollIntoView = true): void {
const cli = MatrixClientPeg.safeGet();
this.timelineWindow = new TimelineWindow(cli, this.props.timelineSet, { windowLimit: this.props.timelineCap });
- this.overlayTimelineWindow = this.props.overlayTimelineSet
- ? new TimelineWindow(cli, this.props.overlayTimelineSet, { windowLimit: this.props.timelineCap })
- : undefined;
const onLoaded = (): void => {
if (this.unmounted) return;
@@ -1554,14 +1447,8 @@ class TimelinePanel extends React.Component {
this.setState(
{
- canBackPaginate:
- (this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ||
- this.overlayTimelineWindow?.canPaginate(EventTimeline.BACKWARDS)) ??
- false,
- canForwardPaginate:
- (this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ||
- this.overlayTimelineWindow?.canPaginate(EventTimeline.FORWARDS)) ??
- false,
+ canBackPaginate: this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ?? false,
+ canForwardPaginate: this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ?? false,
timelineLoading: false,
},
() => {
@@ -1636,7 +1523,7 @@ class TimelinePanel extends React.Component {
// This is a hot-path optimization by skipping a promise tick
// by repeating a no-op sync branch in
// TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
- if (this.props.timelineSet.getTimelineForEvent(eventId) && !this.overlayTimelineWindow) {
+ if (this.props.timelineSet.getTimelineForEvent(eventId)) {
// if we've got an eventId, and the timeline exists, we can skip
// the promise tick.
this.timelineWindow.load(eventId, INITIAL_SIZE);
@@ -1645,14 +1532,7 @@ class TimelinePanel extends React.Component {
return;
}
- const prom = this.timelineWindow.load(eventId, INITIAL_SIZE).then(async (): Promise => {
- if (this.overlayTimelineWindow) {
- // TODO: use timestampToEvent to load the overlay timeline
- // with more correct position when main TL eventId is truthy
- await this.overlayTimelineWindow.load(undefined, INITIAL_SIZE);
- await this.extendOverlayWindowToCoverMainWindow();
- }
- });
+ const prom = this.timelineWindow.load(eventId, INITIAL_SIZE);
this.buildLegacyCallEventGroupers();
this.setState({
events: [],
@@ -1683,38 +1563,9 @@ class TimelinePanel extends React.Component {
this.reloadEvents();
}
- // get the list of events from the timeline windows and the pending event list
+ // get the list of events from the timeline window and the pending event list
private getEvents(): Pick {
- const mainEvents = this.timelineWindow!.getEvents();
- let overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
- if (this.props.overlayTimelineSetFilter !== undefined) {
- overlayEvents = overlayEvents.filter(this.props.overlayTimelineSetFilter);
- }
-
- // maintain the main timeline event order as returned from the HS
- // merge overlay events at approximately the right position based on local timestamp
- const events = overlayEvents.reduce(
- (acc: MatrixEvent[], overlayEvent: MatrixEvent) => {
- // find the first main tl event with a later timestamp
- const index = acc.findIndex((event) => overlaysBefore(overlayEvent, event));
- // insert overlay event into timeline at approximately the right place
- // if it's beyond the edge of the main window, hide it so that expanding
- // the main window doesn't cause new events to pop in and change its position
- if (index === -1) {
- if (!this.timelineWindow!.canPaginate(EventTimeline.FORWARDS)) {
- acc.push(overlayEvent);
- }
- } else if (index === 0) {
- if (!this.timelineWindow!.canPaginate(EventTimeline.BACKWARDS)) {
- acc.unshift(overlayEvent);
- }
- } else {
- acc.splice(index, 0, overlayEvent);
- }
- return acc;
- },
- [...mainEvents],
- );
+ const events = this.timelineWindow!.getEvents();
// We want the last event to be decrypted first
const client = MatrixClientPeg.safeGet();
diff --git a/src/createRoom.ts b/src/createRoom.ts
index f226f68011..df0f90b91f 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -28,7 +28,6 @@ import { _t, UserFriendlyError } from "./languageHandler";
import dis from "./dispatcher/dispatcher";
import * as Rooms from "./Rooms";
import { getAddressType } from "./UserAddress";
-import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
import SpaceStore from "./stores/spaces/SpaceStore";
import { makeSpaceParentEvent } from "./utils/space";
import { JitsiCall, ElementCall } from "./models/Call";
@@ -423,36 +422,6 @@ export async function canEncryptToAllUsers(client: MatrixClient, userIds: string
return true;
}
-// Similar to ensureDMExists but also adds creation content
-// without polluting ensureDMExists with unrelated stuff (also
-// they're never encrypted).
-export async function ensureVirtualRoomExists(
- client: MatrixClient,
- userId: string,
- nativeRoomId: string,
-): Promise {
- const existingDMRoom = findDMForUser(client, userId);
- let roomId: string | null;
- if (existingDMRoom) {
- roomId = existingDMRoom.roomId;
- } else {
- roomId = await createRoom(client, {
- dmUserId: userId,
- spinner: false,
- andView: false,
- createOpts: {
- creation_content: {
- // This allows us to recognise that the room is a virtual room
- // when it comes down our sync stream (we also put the ID of the
- // respective native room in there because why not?)
- [VIRTUAL_ROOM_EVENT_TYPE]: nativeRoomId,
- },
- },
- });
- }
- return roomId;
-}
-
export async function ensureDMExists(client: MatrixClient, userId: string): Promise {
const existingDMRoom = findDMForUser(client, userId);
let roomId: string | null;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 37a3a0718e..1fb2294eb6 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -3093,8 +3093,6 @@
"topic": "Gets or sets the room topic",
"topic_none": "This room has no topic.",
"topic_room_error": "Failed to get room topic: Unable to find room (%(roomId)s",
- "tovirtual": "Switches to this room's virtual room, if it has one",
- "tovirtual_not_found": "No virtual room for this room",
"unban": "Unbans user with given ID",
"unflip": "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message",
"unholdcall": "Takes the call in the current room off hold",
diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts
index 6985e007bd..2fc396b8b3 100644
--- a/src/stores/room-list/RoomListStore.ts
+++ b/src/stores/room-list/RoomListStore.ts
@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { type MatrixClient, type Room, type RoomState, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import SettingsStore from "../../settings/SettingsStore";
@@ -349,15 +348,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient implem
}
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise {
- if (cause === RoomUpdateCause.NewRoom && room.getMyMembership() === KnownMembership.Invite) {
- // Let the visibility provider know that there is a new invited room. It would be nice
- // if this could just be an event that things listen for but the point of this is that
- // we delay doing anything about this room until the VoipUserMapper had had a chance
- // to do the things it needs to do to decide if we should show this room or not, so
- // an even wouldn't et us do that.
- await VisibilityProvider.instance.onNewInvitedRoom(room);
- }
-
if (!VisibilityProvider.instance.isRoomVisible(room)) {
return; // don't do anything on rooms that aren't visible
}
diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts
index f6b0acb030..178a1ed553 100644
--- a/src/stores/room-list/filters/VisibilityProvider.ts
+++ b/src/stores/room-list/filters/VisibilityProvider.ts
@@ -8,10 +8,8 @@
import { type Room } from "matrix-js-sdk/src/matrix";
-import LegacyCallHandler from "../../../LegacyCallHandler";
import { RoomListCustomisations } from "../../../customisations/RoomList";
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
-import VoipUserMapper from "../../../VoipUserMapper";
export class VisibilityProvider {
private static internalInstance: VisibilityProvider;
@@ -25,22 +23,11 @@ export class VisibilityProvider {
return VisibilityProvider.internalInstance;
}
- public async onNewInvitedRoom(room: Room): Promise {
- await VoipUserMapper.sharedInstance().onNewInvitedRoom(room);
- }
-
public isRoomVisible(room?: Room): boolean {
if (!room) {
return false;
}
- if (
- LegacyCallHandler.instance.getSupportsVirtualRooms() &&
- VoipUserMapper.sharedInstance().isVirtualRoom(room)
- ) {
- return false;
- }
-
// hide space rooms as they'll be shown in the SpacePanel
if (room.isSpaceRoom()) {
return false;
diff --git a/test/unit-tests/LegacyCallHandler-test.ts b/test/unit-tests/LegacyCallHandler-test.ts
index 664e091393..972f17b7e0 100644
--- a/test/unit-tests/LegacyCallHandler-test.ts
+++ b/test/unit-tests/LegacyCallHandler-test.ts
@@ -29,8 +29,6 @@ import LegacyCallHandler, {
LegacyCallHandlerEvent,
PROTOCOL_PSTN,
PROTOCOL_PSTN_PREFIXED,
- PROTOCOL_SIP_NATIVE,
- PROTOCOL_SIP_VIRTUAL,
} from "../../src/LegacyCallHandler";
import { mkStubRoom, stubClient, untilDispatch } from "../test-utils";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
@@ -75,9 +73,6 @@ const NATIVE_ALICE = "@alice:example.org";
const NATIVE_BOB = "@bob:example.org";
const NATIVE_CHARLIE = "@charlie:example.org";
-// Virtual user for Bob
-const VIRTUAL_BOB = "@virtual_bob:example.org";
-
//const REAL_ROOM_ID = "$room1:example.org";
// The rooms the user sees when they're communicating with these users
const NATIVE_ROOM_ALICE = "$alice_room:example.org";
@@ -86,10 +81,6 @@ const NATIVE_ROOM_CHARLIE = "$charlie_room:example.org";
const FUNCTIONAL_USER = "@bot:example.com";
-// The room we use to talk to virtual Bob (but that the user does not see)
-// Bob has a virtual room, but Alice doesn't
-const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
-
// Bob's phone number
const BOB_PHONE_NUMBER = "01818118181";
@@ -146,14 +137,6 @@ class FakeCall extends EventEmitter {
}
}
-function untilCallHandlerEvent(callHandler: LegacyCallHandler, event: LegacyCallHandlerEvent): Promise {
- return new Promise((resolve) => {
- callHandler.addListener(event, () => {
- resolve();
- });
- });
-}
-
describe("LegacyCallHandler", () => {
let dmRoomMap;
let callHandler: LegacyCallHandler;
@@ -162,7 +145,6 @@ describe("LegacyCallHandler", () => {
// what addresses the app has looked up via pstn and native lookup
let pstnLookup: string | null;
- let nativeLookup: string | null;
const deviceId = "my-device";
beforeEach(async () => {
@@ -180,8 +162,6 @@ describe("LegacyCallHandler", () => {
MatrixClientPeg.safeGet().getThirdpartyProtocols = () => {
return Promise.resolve({
"m.id.phone": {} as IProtocol,
- "im.vector.protocol.sip_native": {} as IProtocol,
- "im.vector.protocol.sip_virtual": {} as IProtocol,
});
};
@@ -193,7 +173,6 @@ describe("LegacyCallHandler", () => {
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
const nativeRoomBob = mkStubDM(NATIVE_ROOM_BOB, NATIVE_BOB);
const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
- const virtualBobRoom = mkStubDM(VIRTUAL_ROOM_BOB, VIRTUAL_BOB);
MatrixClientPeg.safeGet().getRoom = (roomId: string): Room | null => {
switch (roomId) {
@@ -203,8 +182,6 @@ describe("LegacyCallHandler", () => {
return nativeRoomBob;
case NATIVE_ROOM_CHARLIE:
return nativeRoomCharie;
- case VIRTUAL_ROOM_BOB:
- return virtualBobRoom;
}
return null;
@@ -218,8 +195,6 @@ describe("LegacyCallHandler", () => {
return NATIVE_BOB;
} else if (roomId === NATIVE_ROOM_CHARLIE) {
return NATIVE_CHARLIE;
- } else if (roomId === VIRTUAL_ROOM_BOB) {
- return VIRTUAL_BOB;
} else {
return null;
}
@@ -231,8 +206,6 @@ describe("LegacyCallHandler", () => {
return [NATIVE_ROOM_BOB];
} else if (userId === NATIVE_CHARLIE) {
return [NATIVE_ROOM_CHARLIE];
- } else if (userId === VIRTUAL_BOB) {
- return [VIRTUAL_ROOM_BOB];
} else {
return [];
}
@@ -241,52 +214,18 @@ describe("LegacyCallHandler", () => {
DMRoomMap.setShared(dmRoomMap);
pstnLookup = null;
- nativeLookup = null;
MatrixClientPeg.safeGet().getThirdpartyUser = (proto: string, params: any) => {
if ([PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED].includes(proto)) {
pstnLookup = params["m.id.phone"];
return Promise.resolve([
{
- userid: VIRTUAL_BOB,
+ userid: NATIVE_BOB,
protocol: "m.id.phone",
- fields: {
- is_native: true,
- lookup_success: true,
- },
+ fields: {},
},
]);
- } else if (proto === PROTOCOL_SIP_NATIVE) {
- nativeLookup = params["virtual_mxid"];
- if (params["virtual_mxid"] === VIRTUAL_BOB) {
- return Promise.resolve([
- {
- userid: NATIVE_BOB,
- protocol: "im.vector.protocol.sip_native",
- fields: {
- is_native: true,
- lookup_success: true,
- },
- },
- ]);
- }
- return Promise.resolve([]);
- } else if (proto === PROTOCOL_SIP_VIRTUAL) {
- if (params["native_mxid"] === NATIVE_BOB) {
- return Promise.resolve([
- {
- userid: VIRTUAL_BOB,
- protocol: "im.vector.protocol.sip_virtual",
- fields: {
- is_virtual: true,
- lookup_success: true,
- },
- },
- ]);
- }
- return Promise.resolve([]);
}
-
return Promise.resolve([]);
};
@@ -312,16 +251,14 @@ describe("LegacyCallHandler", () => {
await callHandler.dialNumber(BOB_PHONE_NUMBER);
expect(pstnLookup).toEqual(BOB_PHONE_NUMBER);
- expect(nativeLookup).toEqual(VIRTUAL_BOB);
// we should have switched to the native room for Bob
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
// Check that a call was started: its room on the protocol level
- // should be the virtual room
expect(fakeCall).not.toBeNull();
- expect(fakeCall?.roomId).toEqual(VIRTUAL_ROOM_BOB);
+ expect(fakeCall?.roomId).toEqual(NATIVE_ROOM_BOB);
// but it should appear to the user to be in thw native room for Bob
expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
@@ -338,7 +275,7 @@ describe("LegacyCallHandler", () => {
expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
expect(fakeCall).not.toBeNull();
- expect(fakeCall!.roomId).toEqual(VIRTUAL_ROOM_BOB);
+ expect(fakeCall!.roomId).toEqual(NATIVE_ROOM_BOB);
expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
});
@@ -346,8 +283,6 @@ describe("LegacyCallHandler", () => {
it("should move calls between rooms when remote asserted identity changes", async () => {
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
- await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
-
// We placed the call in Alice's room so it should start off there
expect(callHandler.getCallForRoom(NATIVE_ROOM_ALICE)).toBe(fakeCall);
@@ -517,10 +452,7 @@ describe("LegacyCallHandler without third party protocols", () => {
it("should still start a native call", async () => {
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
- await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
-
// Check that a call was started: its room on the protocol level
- // should be the virtual room
expect(fakeCall).not.toBeNull();
expect(fakeCall!.roomId).toEqual(NATIVE_ROOM_ALICE);
diff --git a/test/unit-tests/SlashCommands-test.tsx b/test/unit-tests/SlashCommands-test.tsx
index 60bc145892..b30bc69176 100644
--- a/test/unit-tests/SlashCommands-test.tsx
+++ b/test/unit-tests/SlashCommands-test.tsx
@@ -14,7 +14,6 @@ import { type Command, Commands, getCommand } from "../../src/SlashCommands";
import { createTestClient } from "../test-utils";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom";
import SettingsStore from "../../src/settings/SettingsStore";
-import LegacyCallHandler from "../../src/LegacyCallHandler";
import { SdkContextClass } from "../../src/contexts/SDKContext";
import Modal from "../../src/Modal";
import WidgetUtils from "../../src/utils/WidgetUtils";
@@ -196,46 +195,6 @@ describe("SlashCommands", () => {
});
});
- describe("/tovirtual", () => {
- beforeEach(() => {
- command = findCommand("tovirtual")!;
- });
-
- describe("isEnabled", () => {
- describe("when virtual rooms are supported", () => {
- beforeEach(() => {
- jest.spyOn(LegacyCallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(true);
- });
-
- it("should return true for Room", () => {
- setCurrentRoom();
- expect(command.isEnabled(client)).toBe(true);
- });
-
- it("should return false for LocalRoom", () => {
- setCurrentLocalRoom();
- expect(command.isEnabled(client)).toBe(false);
- });
- });
-
- describe("when virtual rooms are not supported", () => {
- beforeEach(() => {
- jest.spyOn(LegacyCallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(false);
- });
-
- it("should return false for Room", () => {
- setCurrentRoom();
- expect(command.isEnabled(client)).toBe(false);
- });
-
- it("should return false for LocalRoom", () => {
- setCurrentLocalRoom();
- expect(command.isEnabled(client)).toBe(false);
- });
- });
- });
- });
-
describe("/part", () => {
it("should part room matching alias if found", async () => {
const room1 = new Room("room-id", client, client.getSafeUserId());
diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx
index 696df284fe..d4010700a7 100644
--- a/test/unit-tests/components/structures/RoomView-test.tsx
+++ b/test/unit-tests/components/structures/RoomView-test.tsx
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React, { createRef, type RefObject } from "react";
import { mocked, type MockedObject } from "jest-mock";
import {
- ClientEvent,
EventTimeline,
EventType,
type IEvent,
@@ -68,7 +67,6 @@ import { DirectoryMember } from "../../../../src/utils/direct-messages";
import { createDmLocalRoom } from "../../../../src/utils/dm/createDmLocalRoom";
import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore";
import { SDKContext, SdkContextClass } from "../../../../src/contexts/SDKContext";
-import VoipUserMapper from "../../../../src/VoipUserMapper";
import WidgetUtils from "../../../../src/utils/WidgetUtils";
import { WidgetType } from "../../../../src/widgets/WidgetType";
import WidgetStore from "../../../../src/stores/WidgetStore";
@@ -119,7 +117,6 @@ describe("RoomView", () => {
stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli);
- jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
crypto = cli.getCrypto()!;
jest.spyOn(cli, "getCrypto").mockReturnValue(undefined);
});
@@ -417,26 +414,6 @@ describe("RoomView", () => {
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_verified")).toBeInTheDocument());
});
- describe("with virtual rooms", () => {
- it("checks for a virtual room on initial load", async () => {
- const { container } = await renderRoomView();
- expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
-
- // quick check that rendered without error
- expect(container.querySelector(".mx_ErrorBoundary")).toBeFalsy();
- });
-
- it("checks for a virtual room on room event", async () => {
- await renderRoomView();
- expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
-
- act(() => cli.emit(ClientEvent.Room, room));
-
- // called again after room event
- expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledTimes(2);
- });
- });
-
describe("video rooms", () => {
beforeEach(async () => {
await setupAsyncStoreWithClient(CallStore.instance, MatrixClientPeg.safeGet());
diff --git a/test/unit-tests/components/structures/TimelinePanel-test.tsx b/test/unit-tests/components/structures/TimelinePanel-test.tsx
index 87c788d9f9..354036e282 100644
--- a/test/unit-tests/components/structures/TimelinePanel-test.tsx
+++ b/test/unit-tests/components/structures/TimelinePanel-test.tsx
@@ -35,7 +35,6 @@ import { forEachRight } from "lodash";
import TimelinePanel from "../../../../src/components/structures/TimelinePanel";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
-import { isCallEvent } from "../../../../src/components/structures/LegacyCallEventGrouper";
import {
filterConsole,
flushPromises,
@@ -115,22 +114,6 @@ const setupTestData = (): [MatrixClient, Room, MatrixEvent[]] => {
return [client, room, events];
};
-const setupOverlayTestData = (client: MatrixClient, mainEvents: MatrixEvent[]): [Room, MatrixEvent[]] => {
- const virtualRoom = mkRoom(client, "virtualRoomId");
- const overlayEvents = mockEvents(virtualRoom, 5);
-
- // Set the event order that we'll be looking for in the timeline
- overlayEvents[0].localTimestamp = 1000;
- mainEvents[0].localTimestamp = 2000;
- overlayEvents[1].localTimestamp = 3000;
- overlayEvents[2].localTimestamp = 4000;
- overlayEvents[3].localTimestamp = 5000;
- mainEvents[1].localTimestamp = 6000;
- overlayEvents[4].localTimestamp = 7000;
-
- return [virtualRoom, overlayEvents];
-};
-
const expectEvents = (container: HTMLElement, events: MatrixEvent[]): void => {
const eventTiles = container.querySelectorAll(".mx_EventTile");
const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id"));
@@ -518,298 +501,6 @@ describe("TimelinePanel", () => {
expect(paginateSpy).toHaveBeenCalledWith(EventTimeline.FORWARDS, 1, false);
});
-
- it("advances the overlay timeline window", async () => {
- const [client, room, events] = setupTestData();
-
- const virtualRoom = mkRoom(client, "virtualRoomId");
- const virtualEvents = mockEvents(virtualRoom);
- const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
-
- const props = {
- ...getProps(room, events),
- overlayTimelineSet,
- };
-
- const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
-
- render();
-
- await flushPromises();
-
- const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 });
- const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true };
- client.emit(RoomEvent.Timeline, event, room, false, false, data);
-
- await flushPromises();
-
- expect(paginateSpy).toHaveBeenCalledTimes(2);
- });
- });
-
- describe("with overlayTimeline", () => {
- it("renders merged timeline", async () => {
- const [client, room, events] = setupTestData();
- const virtualRoom = mkRoom(client, "virtualRoomId");
- const virtualCallInvite = new MatrixEvent({
- type: "m.call.invite",
- room_id: virtualRoom.roomId,
- event_id: `virtualCallEvent1`,
- origin_server_ts: 0,
- });
- virtualCallInvite.localTimestamp = 2;
- const virtualCallMetaEvent = new MatrixEvent({
- type: "org.matrix.call.sdp_stream_metadata_changed",
- room_id: virtualRoom.roomId,
- event_id: `virtualCallEvent2`,
- origin_server_ts: 0,
- });
- virtualCallMetaEvent.localTimestamp = 2;
- const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent];
- const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
-
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
- await waitFor(() =>
- expectEvents(container, [
- // main timeline events are included
- events[0],
- events[1],
- // virtual timeline call event is included
- virtualCallInvite,
- // virtual call event has no tile renderer => not rendered
- ]),
- );
- });
-
- it.each([
- ["when it starts with no overlay events", true],
- ["to get enough overlay events", false],
- ])("expands the initial window %s", async (_s, startWithEmptyOverlayWindow) => {
- const [client, room, events] = setupTestData();
- const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
- let overlayEventsPage1: MatrixEvent[];
- let overlayEventsPage2: MatrixEvent[];
- let overlayEventsPage3: MatrixEvent[];
- if (startWithEmptyOverlayWindow) {
- overlayEventsPage1 = overlayEvents.slice(0, 3);
- overlayEventsPage2 = [];
- overlayEventsPage3 = overlayEvents.slice(3, 5);
- } else {
- overlayEventsPage1 = overlayEvents.slice(0, 2);
- overlayEventsPage2 = overlayEvents.slice(2, 3);
- overlayEventsPage3 = overlayEvents.slice(3, 5);
- }
-
- // Start with only page 2 of the overlay events in the window
- const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage2);
- setupPagination(client, overlayTimeline, overlayEventsPage1, overlayEventsPage3);
-
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
-
- await waitFor(() =>
- expectEvents(container, [
- overlayEvents[0],
- events[0],
- overlayEvents[1],
- overlayEvents[2],
- overlayEvents[3],
- events[1],
- overlayEvents[4],
- ]),
- );
- });
-
- it("extends overlay window beyond main window at the start of the timeline", async () => {
- const [client, room, events] = setupTestData();
- const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
- // Delete event 0 so the TimelinePanel will still leave some stuff
- // unloaded for us to test with
- events.shift();
-
- const overlayEventsPage1 = overlayEvents.slice(0, 2);
- const overlayEventsPage2 = overlayEvents.slice(2, 5);
-
- // Start with only page 2 of the overlay events in the window
- const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage2);
- setupPagination(client, overlayTimeline, overlayEventsPage1, null);
-
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
-
- await waitFor(() =>
- expectEvents(container, [
- // These first two are the newly loaded events
- overlayEvents[0],
- overlayEvents[1],
- overlayEvents[2],
- overlayEvents[3],
- events[0],
- overlayEvents[4],
- ]),
- );
- });
-
- it("extends overlay window beyond main window at the end of the timeline", async () => {
- const [client, room, events] = setupTestData();
- const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
- // Delete event 1 so the TimelinePanel will still leave some stuff
- // unloaded for us to test with
- events.pop();
-
- const overlayEventsPage1 = overlayEvents.slice(0, 2);
- const overlayEventsPage2 = overlayEvents.slice(2, 5);
-
- // Start with only page 1 of the overlay events in the window
- const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage1);
- setupPagination(client, overlayTimeline, null, overlayEventsPage2);
-
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
-
- await waitFor(() =>
- expectEvents(container, [
- overlayEvents[0],
- events[0],
- overlayEvents[1],
- // These are the newly loaded events
- overlayEvents[2],
- overlayEvents[3],
- overlayEvents[4],
- ]),
- );
- });
-
- it("paginates", async () => {
- const [client, room, events] = setupTestData();
- const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
- const eventsPage1 = events.slice(0, 1);
- const eventsPage2 = events.slice(1, 2);
-
- // Start with only page 1 of the main events in the window
- const [timeline, timelineSet] = mkTimeline(room, eventsPage1);
- const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents);
- setupPagination(client, timeline, null, eventsPage2);
-
- await withScrollPanelMountSpy(async (mountSpy) => {
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
-
- await waitFor(() => expectEvents(container, [overlayEvents[0], events[0]]));
-
- // ScrollPanel has no chance of working in jsdom, so we've no choice
- // but to do some shady stuff to trigger the fill callback by hand
- const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel;
- scrollPanel.props.onFillRequest!(false);
-
- await waitFor(() =>
- expectEvents(container, [
- overlayEvents[0],
- events[0],
- overlayEvents[1],
- overlayEvents[2],
- overlayEvents[3],
- events[1],
- overlayEvents[4],
- ]),
- );
- });
- });
-
- it.each([
- ["down", "main", true, false],
- ["down", "overlay", true, true],
- ["up", "main", false, false],
- ["up", "overlay", false, true],
- ])("unpaginates %s to an event from the %s timeline", async (_s1, _s2, backwards, fromOverlay) => {
- const [client, room, events] = setupTestData();
- const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
- let marker: MatrixEvent;
- let expectedEvents: MatrixEvent[];
- if (backwards) {
- if (fromOverlay) {
- marker = overlayEvents[1];
- // Overlay events 0−1 and event 0 should be unpaginated
- // Overlay events 2−3 should be hidden since they're at the edge of the window
- expectedEvents = [events[1], overlayEvents[4]];
- } else {
- marker = events[0];
- // Overlay event 0 and event 0 should be unpaginated
- // Overlay events 1−3 should be hidden since they're at the edge of the window
- expectedEvents = [events[1], overlayEvents[4]];
- }
- } else {
- if (fromOverlay) {
- marker = overlayEvents[4];
- // Only the last overlay event should be unpaginated
- expectedEvents = [
- overlayEvents[0],
- events[0],
- overlayEvents[1],
- overlayEvents[2],
- overlayEvents[3],
- events[1],
- ];
- } else {
- // Get rid of overlay event 4 so we can test the case where no overlay events get unpaginated
- overlayEvents.pop();
- marker = events[1];
- // Only event 1 should be unpaginated
- // Overlay events 1−2 should be hidden since they're at the edge of the window
- expectedEvents = [overlayEvents[0], events[0]];
- }
- }
-
- const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents);
-
- await withScrollPanelMountSpy(async (mountSpy) => {
- const { container } = render(
- ,
- withClientContextRenderOptions(MatrixClientPeg.safeGet()),
- );
-
- await waitFor(() =>
- expectEvents(container, [
- overlayEvents[0],
- events[0],
- overlayEvents[1],
- overlayEvents[2],
- overlayEvents[3],
- events[1],
- ...(!backwards && !fromOverlay ? [] : [overlayEvents[4]]),
- ]),
- );
-
- // ScrollPanel has no chance of working in jsdom, so we've no choice
- // but to do some shady stuff to trigger the unfill callback by hand
- const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel;
- scrollPanel.props.onUnfillRequest!(backwards, marker.getId()!);
-
- await waitFor(() => expectEvents(container, expectedEvents));
- });
- });
});
describe("when a thread updates", () => {
diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
index 0e85152567..1e7ceea447 100644
--- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
@@ -1952,7 +1952,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
aria-label="Open room settings"
aria-live="off"
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
- data-color="5"
+ data-color="3"
data-testid="avatar-img"
data-type="round"
role="button"
@@ -1979,7 +1979,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
diff --git a/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts b/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
index 3ba1fb3de6..95e0a3e103 100644
--- a/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
+++ b/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
@@ -10,22 +10,10 @@ import { mocked } from "jest-mock";
import { type Room, RoomType } from "matrix-js-sdk/src/matrix";
import { VisibilityProvider } from "../../../../../src/stores/room-list/filters/VisibilityProvider";
-import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
-import VoipUserMapper from "../../../../../src/VoipUserMapper";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../../src/models/LocalRoom";
import { RoomListCustomisations } from "../../../../../src/customisations/RoomList";
import { createTestClient } from "../../../../test-utils";
-jest.mock("../../../../../src/VoipUserMapper", () => ({
- sharedInstance: jest.fn(),
-}));
-
-jest.mock("../../../../../src/LegacyCallHandler", () => ({
- instance: {
- getSupportsVirtualRooms: jest.fn(),
- },
-}));
-
jest.mock("../../../../../src/customisations/RoomList", () => ({
RoomListCustomisations: {
isRoomVisible: jest.fn(),
@@ -46,16 +34,6 @@ const createLocalRoom = (): LocalRoom => {
};
describe("VisibilityProvider", () => {
- let mockVoipUserMapper: VoipUserMapper;
-
- beforeEach(() => {
- mockVoipUserMapper = {
- onNewInvitedRoom: jest.fn(),
- isVirtualRoom: jest.fn(),
- } as unknown as VoipUserMapper;
- mocked(VoipUserMapper.sharedInstance).mockReturnValue(mockVoipUserMapper);
- });
-
describe("instance", () => {
it("should return an instance", () => {
const visibilityProvider = VisibilityProvider.instance;
@@ -64,28 +42,7 @@ describe("VisibilityProvider", () => {
});
});
- describe("onNewInvitedRoom", () => {
- it("should call onNewInvitedRoom on VoipUserMapper.sharedInstance", async () => {
- const room = {} as unknown as Room;
- await VisibilityProvider.instance.onNewInvitedRoom(room);
- expect(mockVoipUserMapper.onNewInvitedRoom).toHaveBeenCalledWith(room);
- });
- });
-
describe("isRoomVisible", () => {
- describe("for a virtual room", () => {
- beforeEach(() => {
- mocked(LegacyCallHandler.instance.getSupportsVirtualRooms).mockReturnValue(true);
- mocked(mockVoipUserMapper.isVirtualRoom).mockReturnValue(true);
- });
-
- it("should return return false", () => {
- const room = createRoom();
- expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
- expect(mockVoipUserMapper.isVirtualRoom).toHaveBeenCalledWith(room);
- });
- });
-
it("should return false without room", () => {
expect(VisibilityProvider.instance.isRoomVisible()).toBe(false);
});
From 427e61309bbb6366c568f5bf856373e672e9f06f Mon Sep 17 00:00:00 2001
From: David Langley
Date: Wed, 16 Apr 2025 09:38:00 +0100
Subject: [PATCH 2/8] Update team members in triage-assigned.yml (#29751)
---
.github/workflows/triage-assigned.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/triage-assigned.yml b/.github/workflows/triage-assigned.yml
index e43eb94618..f190122a1c 100644
--- a/.github/workflows/triage-assigned.yml
+++ b/.github/workflows/triage-assigned.yml
@@ -11,7 +11,8 @@ jobs:
runs-on: ubuntu-24.04
if: |
contains(github.event.issue.assignees.*.login, 't3chguy') ||
- contains(github.event.issue.assignees.*.login, 'andybalaam') ||
+ contains(github.event.issue.assignees.*.login, 'florianduros') ||
+ contains(github.event.issue.assignees.*.login, 'dbkr') ||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
steps:
- uses: actions/add-to-project@main
From b511bf064db5e7d9f85bc69429f7989881b4cdc2 Mon Sep 17 00:00:00 2001
From: Florian Duros
Date: Wed, 16 Apr 2025 11:23:23 +0200
Subject: [PATCH 3/8] New room list: new visual for invitation (#29773)
* feat: rework invitation styling in room list item
* test: update notification decoration test
* test: add test for vm
* test(e2e): update to new invitation styling
---
.../room-list-item-invited-linux.png | Bin 2945 -> 2841 bytes
.../RoomListPanel/_RoomListItemView.pcss | 4 ---
.../roomlist/RoomListItemViewModel.tsx | 10 ++++++--
.../views/rooms/NotificationDecoration.tsx | 3 ++-
.../roomlist/RoomListItemViewModel-test.tsx | 23 +++++++++++++++++-
.../NotificationDecoration-test.tsx.snap | 14 ++++++++---
6 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png
index aa35ea711815c11e556bf749fff65826a18cee5c..4d9fd3ccd2dbc44b101ab0af211c20edae160e36 100644
GIT binary patch
delta 2821
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 room.tags);
const isArchived = Boolean(roomTags[DefaultTagID.Archived]);
- const showHoverMenu =
- hasAccessToOptionsMenu(room) || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived);
const notificationState = useMemo(() => RoomNotificationStateStore.instance.getRoomState(room), [room]);
+ const invited = notificationState.invited;
const a11yLabel = getA11yLabel(room, notificationState);
const isBold = notificationState.hasAnyNotificationOrActivity;
+ // We don't want to show the hover menu if
+ // - there is an invitation for this room
+ // - the user doesn't have access to both notification and more options menus
+ const showHoverMenu =
+ !invited &&
+ (hasAccessToOptionsMenu(room) || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived));
+
// Video room
const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom();
// EC video call or video room
diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx
index cfb82c461c..9cc1bee738 100644
--- a/src/components/views/rooms/NotificationDecoration.tsx
+++ b/src/components/views/rooms/NotificationDecoration.tsx
@@ -10,6 +10,7 @@ import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/ment
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
+import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
import { UnreadCounter, Unread } from "@vector-im/compound-web";
import { Flex } from "../../utils/Flex";
@@ -56,7 +57,7 @@ export function NotificationDecoration({
>
{isUnsetMessage && }
{hasVideoCall && }
- {invited && }
+ {invited && }
{isMention && }
{(isMention || isNotification) && }
{isActivityNotification && }
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
index 867a909f2e..be309b36ed 100644
--- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
@@ -33,6 +33,10 @@ describe("RoomListItemViewModel", () => {
room = mkStubRoom("roomId", "roomName", matrixClient);
});
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
it("should dispatch view room action on openRoom", async () => {
const { result: vm } = renderHook(
() => useRoomListItemViewModel(room),
@@ -68,6 +72,20 @@ describe("RoomListItemViewModel", () => {
expect(vm.current.showHoverMenu).toBe(true);
});
+ it("should not show hover menu if user has an invitation notification", async () => {
+ mocked(hasAccessToOptionsMenu).mockReturnValue(true);
+
+ const notificationState = new RoomNotificationState(room, false);
+ jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState);
+ jest.spyOn(notificationState, "invited", "get").mockReturnValue(false);
+
+ const { result: vm } = renderHook(
+ () => useRoomListItemViewModel(room),
+ withClientContextRenderOptions(room.client),
+ );
+ expect(vm.current.showHoverMenu).toBe(true);
+ });
+
describe("a11yLabel", () => {
let notificationState: RoomNotificationState;
beforeEach(() => {
@@ -108,7 +126,10 @@ describe("RoomListItemViewModel", () => {
},
])("should return the $label label", ({ mock, expected }) => {
mock?.();
- const { result: vm } = renderHook(() => useRoomListItemViewModel(room));
+ const { result: vm } = renderHook(
+ () => useRoomListItemViewModel(room),
+ withClientContextRenderOptions(room.client),
+ );
expect(vm.current.a11yLabel).toBe(expected);
});
});
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap
index be81664eb8..33ee60102b 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap
@@ -23,11 +23,17 @@ exports[` should render the invitation decoration 1`]
data-testid="notification-decoration"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1-5x); --mx-flex-wrap: nowrap;"
>
-
- 1
-
+
+
`;
From db9428de874fe14bc8f141ec63d8b24aee503c36 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 16 Apr 2025 11:34:01 +0200
Subject: [PATCH 4/8] Update react monorepo (#29765)
* Update react monorepo
* Update snapshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
---
package.json | 10 +--
.../__snapshots__/FilePanel-test.tsx.snap | 2 +-
.../__snapshots__/RoomView-test.tsx.snap | 72 +++++++++---------
.../SpaceHierarchy-test.tsx.snap | 32 ++++----
.../__snapshots__/ThreadPanel-test.tsx.snap | 4 +-
.../DecoratedRoomAvatar-test.tsx.snap | 4 +-
.../WithPresenceIndicator-test.tsx.snap | 6 +-
.../BeaconListItem-test.tsx.snap | 2 +-
.../__snapshots__/DialogSidebar-test.tsx.snap | 2 +-
.../ShareLatestLocation-test.tsx.snap | 2 +-
.../ConfirmRejectInviteDialog-test.tsx.snap | 16 ++--
.../DevtoolsDialog-test.tsx.snap | 4 +-
...nageRestrictedJoinRuleDialog-test.tsx.snap | 4 +-
.../ReportRoomDialog-test.tsx.snap | 6 +-
.../ServerPickerDialog-test.tsx.snap | 2 +-
.../__snapshots__/AppTile-test.tsx.snap | 6 +-
.../__snapshots__/FacePile-test.tsx.snap | 2 +-
.../__snapshots__/InfoTooltip-test.tsx.snap | 2 +-
.../LabelledCheckbox-test.tsx.snap | 4 +-
.../__snapshots__/RoomFacePile-test.tsx.snap | 4 +-
.../__snapshots__/SettingsField-test.tsx.snap | 4 +-
.../LocationShareMenu-test.tsx.snap | 4 +-
.../__snapshots__/MLocationBody-test.tsx.snap | 4 +-
.../__snapshots__/PollHistory-test.tsx.snap | 4 +-
.../__snapshots__/PollListItem-test.tsx.snap | 2 +-
.../PollListItemEnded-test.tsx.snap | 2 +-
.../__snapshots__/BaseCard-test.tsx.snap | 2 +-
.../ExtensionsCard-test.tsx.snap | 4 +-
.../PinnedMessagesCard-test.tsx.snap | 22 +++---
.../RoomSummaryCard-test.tsx.snap | 12 +--
.../__snapshots__/UserInfo-test.tsx.snap | 4 +-
.../__snapshots__/RoomHeader-test.tsx.snap | 8 +-
.../VideoRoomChatButton-test.tsx.snap | 2 +-
.../RoomListHeaderView-test.tsx.snap | 12 +--
.../RoomListItemMenuView-test.tsx.snap | 16 ++--
.../__snapshots__/RoomListPanel-test.tsx.snap | 2 +-
.../PinnedEventTile-test.tsx.snap | 16 ++--
.../ReadReceiptGroup-test.tsx.snap | 8 +-
.../ThirdPartyMemberInfo-test.tsx.snap | 4 +-
.../MemberTileView-test.tsx.snap | 4 +-
.../LayoutSwitcher-test.tsx.snap | 20 ++---
.../__snapshots__/Notifications-test.tsx.snap | 8 +-
.../__snapshots__/SetIdServer-test.tsx.snap | 4 +-
.../ThemeChoicePanel-test.tsx.snap | 76 +++++++++----------
.../FilteredDeviceListHeader-test.tsx.snap | 4 +-
.../__snapshots__/AdvancedPanel-test.tsx.snap | 8 +-
.../ChangeRecoveryKey-test.tsx.snap | 12 +--
.../Notifications2-test.tsx.snap | 40 +++++-----
.../AppearanceUserSettingsTab-test.tsx.snap | 32 ++++----
.../SecurityUserSettingsTab-test.tsx.snap | 4 +-
.../SessionManagerTab-test.tsx.snap | 2 +-
.../SidebarUserSettingsTab-test.tsx.snap | 48 ++++++------
.../__snapshots__/SpacePanel-test.tsx.snap | 8 +-
.../SpaceSettingsVisibilityTab-test.tsx.snap | 6 +-
.../ThreadsActivityCentre-test.tsx.snap | 64 ++++++++--------
test/unit-tests/hooks/useProfileInfo-test.tsx | 7 +-
.../hooks/usePublicRoomDirectory-test.tsx | 3 +-
.../hooks/useUserDirectory-test.tsx | 6 +-
.../__snapshots__/link-tooltip-test.tsx.snap | 2 +-
yarn.lock | 46 +++++------
60 files changed, 362 insertions(+), 360 deletions(-)
diff --git a/package.json b/package.json
index 159ca2879b..4773ee3da9 100644
--- a/package.json
+++ b/package.json
@@ -68,10 +68,10 @@
"postinstall": "patch-package"
},
"resolutions": {
- "**/pretty-format/react-is": "19.0.0",
+ "**/pretty-format/react-is": "19.1.0",
"@playwright/test": "1.51.1",
- "@types/react": "19.0.10",
- "@types/react-dom": "19.0.4",
+ "@types/react": "19.1.1",
+ "@types/react-dom": "19.1.2",
"oidc-client-ts": "3.2.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001713",
@@ -211,9 +211,9 @@
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5",
- "@types/react": "19.0.10",
+ "@types/react": "19.1.1",
"@types/react-beautiful-dnd": "^13.0.0",
- "@types/react-dom": "19.0.4",
+ "@types/react-dom": "19.1.2",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.15.0",
"@types/semver": "^7.5.8",
diff --git a/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
index cc900cdf5a..f311521960 100644
--- a/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
@@ -19,7 +19,7 @@ exports[`FilePanel renders empty state 1`] = `