Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 168ffc5

Browse files
committed
Test voice rooms
Signed-off-by: Robin Townsend <[email protected]>
1 parent e83a685 commit 168ffc5

File tree

3 files changed

+273
-1
lines changed

3 files changed

+273
-1
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { mount } from "enzyme";
19+
import { act } from "react-dom/test-utils";
20+
import { MatrixWidgetType } from "matrix-widget-api";
21+
22+
import "../../../skinned-sdk";
23+
import { stubClient, mkStubRoom } from "../../../test-utils";
24+
import PlatformPeg from "../../../../src/PlatformPeg";
25+
import RoomTile from "../../../../src/components/views/rooms/RoomTile";
26+
import SettingsStore from "../../../../src/settings/SettingsStore";
27+
import WidgetStore from "../../../../src/stores/WidgetStore";
28+
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
29+
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
30+
import VoiceChannelStore, { VoiceChannelEvent } from "../../../../src/stores/VoiceChannelStore";
31+
import { DefaultTagID } from "../../../../src/stores/room-list/models";
32+
import DMRoomMap from "../../../../src/utils/DMRoomMap";
33+
import { VOICE_CHANNEL_ID } from "../../../../src/utils/VoiceChannelUtils";
34+
35+
describe("RoomTile", () => {
36+
PlatformPeg.get = () => ({ overrideBrowserShortcuts: () => false });
37+
SettingsStore.getValue = setting => setting === "feature_voice_rooms";
38+
beforeEach(() => {
39+
stubClient();
40+
DMRoomMap.makeShared();
41+
});
42+
43+
describe("voice rooms", () => {
44+
const room = mkStubRoom("!1:example.org");
45+
room.isCallRoom.mockReturnValue(true);
46+
47+
// Set up mocks to simulate the remote end of the widget API
48+
let messageSent;
49+
let messageSendMock;
50+
let onceMock;
51+
beforeEach(() => {
52+
let resolveMessageSent;
53+
messageSent = new Promise(resolve => resolveMessageSent = resolve);
54+
messageSendMock = jest.fn().mockImplementation(() => resolveMessageSent());
55+
onceMock = jest.fn();
56+
57+
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([{
58+
id: VOICE_CHANNEL_ID,
59+
eventId: "$1:example.org",
60+
roomId: "!1:example.org",
61+
type: MatrixWidgetType.JitsiMeet,
62+
url: "",
63+
name: "Voice channel",
64+
creatorUserId: "@alice:example.org",
65+
avatar_url: null,
66+
}]);
67+
jest.spyOn(WidgetMessagingStore.instance, "getMessagingForUid").mockReturnValue({
68+
on: () => {},
69+
off: () => {},
70+
once: onceMock,
71+
transport: {
72+
send: messageSendMock,
73+
reply: () => {},
74+
},
75+
});
76+
});
77+
78+
it("tracks connection state", async () => {
79+
const tile = mount(
80+
<RoomTile
81+
room={room}
82+
showMessagePreview={false}
83+
isMinimized={false}
84+
tag={DefaultTagID.Untagged}
85+
/>,
86+
);
87+
expect(tile.find(".mx_RoomTile_voiceIndicator").text()).toEqual("Voice room");
88+
89+
act(() => { tile.simulate("click"); });
90+
tile.update();
91+
expect(tile.find(".mx_RoomTile_voiceIndicator").text()).toEqual("Connecting…");
92+
93+
// Wait for the VoiceChannelStore to connect to the widget API
94+
await messageSent;
95+
// Then, locate the callback that will confirm the join
96+
const [, join] = onceMock.mock.calls.find(([action]) =>
97+
action === `action:${ElementWidgetActions.JoinCall}`,
98+
);
99+
100+
// Now we confirm the join and wait for the VoiceChannelStore to update
101+
const waitForConnect = new Promise<void>(resolve =>
102+
VoiceChannelStore.instance.once(VoiceChannelEvent.Connect, resolve),
103+
);
104+
join({ detail: {} });
105+
await waitForConnect;
106+
// Wait yet another tick for the room tile to update
107+
await Promise.resolve();
108+
109+
tile.update();
110+
expect(tile.find(".mx_RoomTile_voiceIndicator").text()).toEqual("Connected");
111+
112+
// Locate the callback that will perform the hangup
113+
const [, hangup] = onceMock.mock.calls.find(([action]) =>
114+
action === `action:${ElementWidgetActions.HangupCall}`,
115+
);
116+
117+
// Hangup and wait for the VoiceChannelStore, once again
118+
const waitForHangup = new Promise<void>(resolve =>
119+
VoiceChannelStore.instance.once(VoiceChannelEvent.Disconnect, resolve),
120+
);
121+
hangup({ detail: {} });
122+
await waitForHangup;
123+
// Wait yet another tick for the room tile to update
124+
await Promise.resolve();
125+
126+
tile.update();
127+
expect(tile.find(".mx_RoomTile_voiceIndicator").text()).toEqual("Voice room");
128+
});
129+
});
130+
});
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { EventEmitter } from "events";
18+
import React from "react";
19+
import { mount } from "enzyme";
20+
import { act } from "react-dom/test-utils";
21+
22+
import "../../../skinned-sdk";
23+
import { stubClient, mkStubRoom, wrapInMatrixClientContext } from "../../../test-utils";
24+
import _VoiceChannelRadio from "../../../../src/components/views/voip/VoiceChannelRadio";
25+
import VoiceChannelStore, { VoiceChannelEvent } from "../../../../src/stores/VoiceChannelStore";
26+
import DMRoomMap from "../../../../src/utils/DMRoomMap";
27+
28+
const VoiceChannelRadio = wrapInMatrixClientContext(_VoiceChannelRadio);
29+
30+
export default class StubVoiceChannelStore extends EventEmitter {
31+
private _roomId: string;
32+
public get roomId(): string { return this._roomId; }
33+
private _audioMuted: boolean;
34+
public get audioMuted(): boolean { return this._audioMuted; }
35+
private _videoMuted: boolean;
36+
public get videoMuted(): boolean { return this._videoMuted; }
37+
38+
public connect = jest.fn().mockImplementation(async (roomId: string) => {
39+
this._roomId = roomId;
40+
this._audioMuted = true;
41+
this._videoMuted = true;
42+
this.emit(VoiceChannelEvent.Connect);
43+
});
44+
public disconnect = jest.fn().mockImplementation(async () => {
45+
this._roomId = null;
46+
this.emit(VoiceChannelEvent.Disconnect);
47+
});
48+
public muteAudio = jest.fn().mockImplementation(async () => {
49+
this._audioMuted = true;
50+
this.emit(VoiceChannelEvent.MuteAudio);
51+
});
52+
public unmuteAudio = jest.fn().mockImplementation(async () => {
53+
this._audioMuted = false;
54+
this.emit(VoiceChannelEvent.UnmuteAudio);
55+
});
56+
public muteVideo = jest.fn().mockImplementation(async () => {
57+
this._videoMuted = true;
58+
this.emit(VoiceChannelEvent.MuteVideo);
59+
});
60+
public unmuteVideo = jest.fn().mockImplementation(async () => {
61+
this._videoMuted = false;
62+
this.emit(VoiceChannelEvent.UnmuteVideo);
63+
});
64+
}
65+
66+
describe("VoiceChannelRadio", () => {
67+
const room = mkStubRoom("!1:example.org");
68+
room.isCallRoom.mockReturnValue(true);
69+
70+
beforeEach(() => {
71+
stubClient();
72+
DMRoomMap.makeShared();
73+
// Stub out the VoiceChannelStore
74+
jest.spyOn(VoiceChannelStore, "instance", "get").mockReturnValue(new StubVoiceChannelStore());
75+
});
76+
77+
it("shows when connecting voice", async () => {
78+
const radio = mount(<VoiceChannelRadio />);
79+
expect(radio.children().children().exists()).toEqual(false);
80+
81+
act(() => { VoiceChannelStore.instance.connect("!1:example.org"); });
82+
radio.update();
83+
expect(radio.children().children().exists()).toEqual(true);
84+
});
85+
86+
it("hides when disconnecting voice", () => {
87+
VoiceChannelStore.instance.connect("!1:example.org");
88+
const radio = mount(<VoiceChannelRadio />);
89+
expect(radio.children().children().exists()).toEqual(true);
90+
91+
act(() => { VoiceChannelStore.instance.disconnect(); });
92+
radio.update();
93+
expect(radio.children().children().exists()).toEqual(false);
94+
});
95+
96+
describe("disconnect button", () => {
97+
it("works", () => {
98+
VoiceChannelStore.instance.connect("!1:example.org");
99+
const radio = mount(<VoiceChannelRadio />);
100+
101+
act(() => {
102+
radio.find("AccessibleButton.mx_VoiceChannelRadio_disconnectButton").simulate("click");
103+
});
104+
expect(VoiceChannelStore.instance.disconnect).toHaveBeenCalled();
105+
});
106+
});
107+
108+
describe("video button", () => {
109+
it("works", () => {
110+
VoiceChannelStore.instance.connect("!1:example.org");
111+
const radio = mount(<VoiceChannelRadio />);
112+
113+
act(() => {
114+
radio.find("AccessibleButton.mx_VoiceChannelRadio_videoButton").simulate("click");
115+
});
116+
expect(VoiceChannelStore.instance.unmuteVideo).toHaveBeenCalled();
117+
118+
act(() => {
119+
radio.find("AccessibleButton.mx_VoiceChannelRadio_videoButton").simulate("click");
120+
});
121+
expect(VoiceChannelStore.instance.muteVideo).toHaveBeenCalled();
122+
});
123+
});
124+
125+
describe("audio button", () => {
126+
it("works", () => {
127+
VoiceChannelStore.instance.connect("!1:example.org");
128+
const radio = mount(<VoiceChannelRadio />);
129+
130+
act(() => {
131+
radio.find("AccessibleButton.mx_VoiceChannelRadio_audioButton").simulate("click");
132+
});
133+
expect(VoiceChannelStore.instance.unmuteAudio).toHaveBeenCalled();
134+
135+
act(() => {
136+
radio.find("AccessibleButton.mx_VoiceChannelRadio_audioButton").simulate("click");
137+
});
138+
expect(VoiceChannelStore.instance.muteAudio).toHaveBeenCalled();
139+
});
140+
});
141+
});

test/test-utils/test-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
351351
name,
352352
getAvatarUrl: () => 'mxc://avatar.url/room.png',
353353
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
354-
isSpaceRoom: jest.fn(() => false),
354+
isSpaceRoom: jest.fn().mockReturnValue(false),
355+
isCallRoom: jest.fn().mockReturnValue(false),
355356
getUnreadNotificationCount: jest.fn(() => 0),
356357
getEventReadUpTo: jest.fn(() => null),
357358
getCanonicalAlias: jest.fn(),

0 commit comments

Comments
 (0)