Room list: listen to call event to check number of participants (#32677)
* feat(call store): add new `CallEvent.Participants` event The room list needs to listen to `CallEvent.Participants` to be able to display the Call icon. This was working before https://github.com/element-hq/element-web/pull/32663 due to an excessive re-renders or relying on the notification events. * chore(room list): listen to `CallEvent.Participants` * test(room list): add test for new listener * test(call store): add tests for `CallEvent.Particpants` * Revert "feat(call store): add new `CallEvent.Participants` event" This reverts commit d2a7a009a4c55325404ad38f23fa662a8103cff4. * Revert "test(call store): add tests for `CallEvent.Particpants`" This reverts commit 4455182fb3aea54ea10cfabb8beb7946cfdf8a6c. * chore(room list): listen to `Call#CallEvent.Participants` insteaf of listening to `CallStore` * test(room list): update added test * fix(room list): clean properly listeners on previous call * test(room list): add missing test * fix(room list): don't use trackListeners to avoid leaking memory when listening to call event * fix(room list): listen to participant change when vm is created * test(room list): add test case when there is an existing call
This commit is contained in:
@@ -5,7 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixClient, type MatrixEvent, Room, RoomEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
type MatrixClient,
|
||||
type MatrixEvent,
|
||||
Room,
|
||||
RoomEvent,
|
||||
PendingEventOrdering,
|
||||
type RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
|
||||
import { createTestClient, flushPromises } from "../../test-utils";
|
||||
@@ -270,6 +277,8 @@ describe("RoomListItemViewModel", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map([[matrixClient.getUserId()!, {}]]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
} as unknown as Call;
|
||||
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall);
|
||||
@@ -285,6 +294,8 @@ describe("RoomListItemViewModel", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Video,
|
||||
participants: new Map([[matrixClient.getUserId()!, {}]]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
} as unknown as Call;
|
||||
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall);
|
||||
@@ -300,6 +311,8 @@ describe("RoomListItemViewModel", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map(),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
} as unknown as Call;
|
||||
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall);
|
||||
@@ -310,6 +323,120 @@ describe("RoomListItemViewModel", () => {
|
||||
|
||||
expect(viewModel.getSnapshot().notification.callType).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should listen to call participant changes", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map(),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall as unknown as Call);
|
||||
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
expect(viewModel.getSnapshot().notification.callType).toBeUndefined();
|
||||
|
||||
// Get the callback registered for call state changes
|
||||
const mockCalls = (CallStore.instance.on as jest.Mock).mock.calls;
|
||||
const callStateCallback = mockCalls[mockCalls.length - 1][1];
|
||||
callStateCallback();
|
||||
|
||||
// Simulate participant joining
|
||||
mockCall.participants.set(matrixClient.getUserId()! as unknown as RoomMember, new Set());
|
||||
|
||||
// Get the callback registered for participant changes
|
||||
const participantsChangeCallback = mockCall.on.mock.calls[0][1];
|
||||
participantsChangeCallback();
|
||||
|
||||
expect(viewModel.getSnapshot().notification.callType).toBe("voice");
|
||||
});
|
||||
|
||||
it("should not update the item when there is already an active call and participants join", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map([[matrixClient.getUserId()! as unknown as RoomMember, new Set<string>()]]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall as unknown as Call);
|
||||
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
// Trigger onCallStateChanged so the call is tracked and the participant listener is registered
|
||||
const mockCalls = (CallStore.instance.on as jest.Mock).mock.calls;
|
||||
const callStateCallback = mockCalls[mockCalls.length - 1][1];
|
||||
callStateCallback();
|
||||
|
||||
expect(viewModel.getSnapshot().notification.callType).toBe("voice");
|
||||
|
||||
// Record the snapshot version before the participant event fires
|
||||
const snapshotBefore = viewModel.getSnapshot();
|
||||
|
||||
// Simulate another participant joining while the call is already active
|
||||
mockCall.participants.set("@other:server" as unknown as RoomMember, new Set<string>());
|
||||
const participantsChangeCallback = mockCall.on.mock.calls[0][1];
|
||||
participantsChangeCallback(mockCall.participants);
|
||||
|
||||
// Snapshot should not have changed
|
||||
expect(viewModel.getSnapshot()).toBe(snapshotBefore);
|
||||
});
|
||||
|
||||
it("should react to participant changes when a call already exists at instantiation time", () => {
|
||||
const mockCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map([]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall as unknown as Call);
|
||||
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
expect(viewModel.getSnapshot().notification.callType).toBeUndefined();
|
||||
|
||||
// Simulate participant joining
|
||||
mockCall.participants.set(matrixClient.getUserId()! as unknown as RoomMember, new Set());
|
||||
|
||||
// Get the callback registered for participant changes
|
||||
const participantsChangeCallback = mockCall.on.mock.calls[0][1];
|
||||
participantsChangeCallback();
|
||||
|
||||
expect(viewModel.getSnapshot().notification.callType).toBe("voice");
|
||||
});
|
||||
|
||||
it("should unsubscribe from old call participants when the call changes", () => {
|
||||
const firstCall = {
|
||||
callType: CallType.Voice,
|
||||
participants: new Map([[matrixClient.getUserId()! as unknown as RoomMember, new Set<string>()]]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
const secondCall = {
|
||||
callType: CallType.Video,
|
||||
participants: new Map([[matrixClient.getUserId()! as unknown as RoomMember, new Set<string>()]]),
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(firstCall as unknown as Call);
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
|
||||
// Trigger onCallStateChanged to register the first call
|
||||
const mockCalls = (CallStore.instance.on as jest.Mock).mock.calls;
|
||||
const callStateCallback = mockCalls[mockCalls.length - 1][1];
|
||||
callStateCallback();
|
||||
|
||||
const participantsCallback = firstCall.on.mock.calls[0][1];
|
||||
expect(firstCall.on).toHaveBeenCalledWith("participants", participantsCallback);
|
||||
|
||||
// Now switch to a different call
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(secondCall as unknown as Call);
|
||||
callStateCallback();
|
||||
|
||||
// The old call's listener must have been removed
|
||||
expect(firstCall.off).toHaveBeenCalledWith("participants", participantsCallback);
|
||||
// The new call must have a listener registered
|
||||
expect(secondCall.on).toHaveBeenCalledWith("participants", expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room name updates", () => {
|
||||
|
||||
Reference in New Issue
Block a user