Fix room list often showing the wrong icons for calls (#32881)
* Give rooms with calls a proper accessible description Besides improving accessibility, this makes it possible to check for the presence of a call indicator in the room list in Playwright tests. * Make room list react to calls in a room, even when not connected to them To use the results of CallStore.getRoom reactively, you need to listen for Call events, not ConnectedCalls events. * Don't assume that every call starts off as a video call If a Call object is created by way of someone starting a voice call, then of course the call's initial type needs to be 'voice'. * Make room list items react to changes in call type The type of a call may change over time; therefore room list items explicitly need to react to the changes. * Update a call's type before notifying listeners of the change If we notify listeners of a change in a call's type before actually making that change, the listeners will be working with glitched state. This would cause the room list to show the wrong call type in certain situations. * Ignore the Vitest attachments directory
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
RoomStateEvent,
|
||||
type IContent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { mocked, type Mocked } from "jest-mock";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
|
||||
|
||||
@@ -52,6 +53,7 @@ export class MockedCall extends Call {
|
||||
waitForIframeLoad: false,
|
||||
},
|
||||
room.client,
|
||||
CallType.Video,
|
||||
);
|
||||
this.groupCall = { creationTs: this.event.getTs() } as unknown as GroupCall;
|
||||
}
|
||||
|
||||
@@ -381,6 +381,7 @@ export function createStubMatrixRTC(): MatrixRTCSessionManager {
|
||||
const session = new EventEmitter() as MatrixRTCSession;
|
||||
session.memberships = [];
|
||||
session.getOldestMembership = () => undefined;
|
||||
session.getConsensusCallIntent = () => "video";
|
||||
return session;
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
MatrixRTCSession,
|
||||
MatrixRTCSessionEvent,
|
||||
} from "matrix-js-sdk/src/matrixrtc";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
|
||||
import type { Mocked } from "jest-mock";
|
||||
import type { ClientWidgetApi } from "matrix-widget-api";
|
||||
@@ -987,6 +988,35 @@ describe("ElementCall", () => {
|
||||
call.off(CallEvent.Participants, onParticipants);
|
||||
});
|
||||
|
||||
it("emits events when call type changes", async () => {
|
||||
const onCallTypeChanged = jest.fn();
|
||||
call.on(CallEvent.CallTypeChanged, onCallTypeChanged);
|
||||
// Should default to video when unknown
|
||||
expect(call.callType).toBe(CallType.Video);
|
||||
|
||||
// Change call type to voice
|
||||
roomSession.memberships = [
|
||||
{ sender: alice.userId, deviceId: "alices_device", callIntent: "audio" } as Mocked<CallMembership>,
|
||||
];
|
||||
roomSession.getConsensusCallIntent.mockReturnValue("audio");
|
||||
roomSession.emit(MatrixRTCSessionEvent.MembershipsChanged, [], []);
|
||||
|
||||
expect(call.callType).toBe(CallType.Voice);
|
||||
expect(onCallTypeChanged.mock.calls).toEqual([[CallType.Voice]]);
|
||||
|
||||
// Change call type back to video
|
||||
roomSession.memberships = [
|
||||
{ sender: alice.userId, deviceId: "alices_device", callIntent: "video" } as Mocked<CallMembership>,
|
||||
];
|
||||
roomSession.getConsensusCallIntent.mockReturnValue("video");
|
||||
roomSession.emit(MatrixRTCSessionEvent.MembershipsChanged, [], []);
|
||||
|
||||
expect(call.callType).toBe(CallType.Video);
|
||||
expect(onCallTypeChanged.mock.calls).toEqual([[CallType.Voice], [CallType.Video]]);
|
||||
|
||||
call.off(CallEvent.CallTypeChanged, onCallTypeChanged);
|
||||
});
|
||||
|
||||
it("ends the call immediately if the session ended", async () => {
|
||||
await connect(call, widgetApi);
|
||||
const onDestroy = jest.fn();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
type MatrixClient,
|
||||
type MatrixEvent,
|
||||
@@ -26,7 +27,7 @@ import { DefaultTagID } from "../../../src/stores/room-list-v3/skip-list/tag";
|
||||
import dispatcher from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import { CallStore } from "../../../src/stores/CallStore";
|
||||
import type { Call } from "../../../src/models/Call";
|
||||
import { CallEvent, type Call } from "../../../src/models/Call";
|
||||
import { RoomListItemViewModel } from "../../../src/viewmodels/room-list/RoomListItemViewModel";
|
||||
|
||||
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
||||
@@ -436,6 +437,28 @@ describe("RoomListItemViewModel", () => {
|
||||
// The new call must have a listener registered
|
||||
expect(secondCall.on).toHaveBeenCalledWith("participants", expect.any(Function));
|
||||
});
|
||||
|
||||
it("should listen to call type changes", async () => {
|
||||
// Start with a voice call
|
||||
let callType = CallType.Voice;
|
||||
const mockCall = new (class extends EventEmitter {
|
||||
get callType() {
|
||||
return callType;
|
||||
}
|
||||
participants = new Map([[matrixClient.getUserId()!, {}]]);
|
||||
})() as unknown as Call;
|
||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall);
|
||||
|
||||
viewModel = new RoomListItemViewModel({ room, client: matrixClient });
|
||||
await flushPromises();
|
||||
|
||||
expect(viewModel.getSnapshot().notification.callType).toBe("voice");
|
||||
|
||||
// Now turn it into a video call
|
||||
callType = CallType.Video;
|
||||
mockCall.emit(CallEvent.CallTypeChanged, callType);
|
||||
expect(viewModel.getSnapshot().notification.callType).toBe("video");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room name updates", () => {
|
||||
|
||||
Reference in New Issue
Block a user