Refactor element call lobby + skip lobby (#12057)
* Refactor ElementCall to use the widget lobby. - expose skip lobby - use the widget.data to build the widget url Signed-off-by: Timo K <toger5@hotmail.de> * Use shiftKey click to skip the lobby Signed-off-by: Timo K <toger5@hotmail.de> * remove Lobby component Signed-off-by: Timo K <toger5@hotmail.de> * update tests + remove EW lobby related tests Signed-off-by: Timo K <toger5@hotmail.de> * remove lobby device button tests Signed-off-by: Timo K <toger5@hotmail.de> * i18n Signed-off-by: Timo K <toger5@hotmail.de> * use voip participant label Signed-off-by: Timo K <toger5@hotmail.de> * update tests Signed-off-by: Timo K <toger5@hotmail.de> * fix rounded corners in pip Signed-off-by: Timo K <toger5@hotmail.de> * allow joining call in legacy room header (without banner) Signed-off-by: Timo K <toger5@hotmail.de> * Introduce new connection states for calls. And use them for integrated lobby. Signed-off-by: Timo K <toger5@hotmail.de> * New room header call join Fix broken top container element call. Signed-off-by: Timo K <toger5@hotmail.de> * i18n Signed-off-by: Timo K <toger5@hotmail.de> * Fix closing element call in lobby view. (should destroy call if there the user never managed to connect (not clicked join in lobby) Signed-off-by: Timo K <toger5@hotmail.de> * all cases for connection state Signed-off-by: Timo K <toger5@hotmail.de> * add correct LiveContentSummary labels Signed-off-by: Timo K <toger5@hotmail.de> * Theme widget loading (no rounded corner) destroy call when switching room while a call is loading. Signed-off-by: Timo K <toger5@hotmail.de> * temp Signed-off-by: Timo K <toger5@hotmail.de> * usei view room dispatcher instead of emitter Signed-off-by: Timo K <toger5@hotmail.de> * tidy up Signed-off-by: Timo K <toger5@hotmail.de> * returnToLobby + remove StartCallView Signed-off-by: Timo K <toger5@hotmail.de> * comment cleanup Signed-off-by: Timo K <toger5@hotmail.de> * disconnect ongoing calls before making widget sticky. Signed-off-by: Timo K <toger5@hotmail.de> * linter + jitsi as videoChannel Signed-off-by: Timo K <toger5@hotmail.de> * stickyPromise type Signed-off-by: Timo K <toger5@hotmail.de> * fix legacy call (jistsi, cisco, bbb) reopen when clicking call button Signed-off-by: Timo K <toger5@hotmail.de> * fix tests and connect resolves Signed-off-by: Timo K <toger5@hotmail.de> * fix "waits for messaging when connecting" test Signed-off-by: Timo K <toger5@hotmail.de> * Allow to skip awaiting Call session events. This option is used in tests to spare mocking the events emitted when EC updates the room state Signed-off-by: Timo K <toger5@hotmail.de> * add sticky test Signed-off-by: Timo K <toger5@hotmail.de> * add test for looby tile rendering Signed-off-by: Timo K <toger5@hotmail.de> * fix flaky test Signed-off-by: Timo K <toger5@hotmail.de> * add reconnect after disconnect test (video room) Signed-off-by: Timo K <toger5@hotmail.de> * add shift click test to call toast Signed-off-by: Timo K <toger5@hotmail.de> * test for allowVoipWithNoMedia in widget url Signed-off-by: Timo K <toger5@hotmail.de> * fix e2e tests to search for the right element Signed-off-by: Timo K <toger5@hotmail.de> * destroy call after test so next test does not fail Signed-off-by: Timo K <toger5@hotmail.de> * new call test (connection failed) Signed-off-by: Timo K <toger5@hotmail.de> * reset to real timers Signed-off-by: Timo K <toger5@hotmail.de> * dont use skipSessionAwait for tests Signed-off-by: Timo K <toger5@hotmail.de> * code quality (sonar) Signed-off-by: Timo K <toger5@hotmail.de> * refactor call.disconnect tests (dont use skipSessionAwait) Signed-off-by: Timo K <toger5@hotmail.de> * miscellaneous cleanup Signed-off-by: Timo K <toger5@hotmail.de> * only send call notify after the call has been joined (not when just opening the lobby) Signed-off-by: Timo K <toger5@hotmail.de> * update call notify tests to expect notify on connect. Not on widget creation. Signed-off-by: Timo K <toger5@hotmail.de> * Update playwright/e2e/room/room-header.spec.ts Co-authored-by: Robin <robin@robin.town> * Update src/components/views/voip/CallView.tsx Co-authored-by: Robin <robin@robin.town> * review rename connect -> start isVideoRoom not dependant on feature flags rename allOtherCallsDisconnected -> disconnectAllOtherCalls Signed-off-by: Timo K <toger5@hotmail.de> * check for EC widget Signed-off-by: Timo K <toger5@hotmail.de> * dep array Signed-off-by: Timo K <toger5@hotmail.de> * rename in spyOn Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
parent
3f7e21e08d
commit
a370a5cfa4
28 changed files with 693 additions and 767 deletions
|
@ -38,8 +38,6 @@ import { CallView as _CallView } from "../../../../src/components/views/voip/Cal
|
|||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { Call, ConnectionState } from "../../../../src/models/Call";
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
import MediaDeviceHandler from "../../../../src/MediaDeviceHandler";
|
||||
|
||||
const CallView = wrapInMatrixClientContext(_CallView);
|
||||
|
||||
|
@ -75,8 +73,10 @@ describe("CallView", () => {
|
|||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||
});
|
||||
|
||||
const renderView = async (): Promise<void> => {
|
||||
render(<CallView room={room} resizing={false} waitForCall={false} />, { wrapper: TooltipProvider });
|
||||
const renderView = async (skipLobby = false): Promise<void> => {
|
||||
render(<CallView room={room} resizing={false} waitForCall={false} skipLobby={skipLobby} />, {
|
||||
wrapper: TooltipProvider,
|
||||
});
|
||||
await act(() => Promise.resolve()); // Let effects settle
|
||||
};
|
||||
|
||||
|
@ -108,20 +108,6 @@ describe("CallView", () => {
|
|||
expect(cleanSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows lobby and keeps widget loaded when disconnected", async () => {
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: "Join" });
|
||||
screen.getAllByText(/\bwidget\b/i);
|
||||
});
|
||||
|
||||
it("only shows widget when connected", async () => {
|
||||
await renderView();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
await waitFor(() => expect(call.connectionState).toBe(ConnectionState.Connected));
|
||||
expect(screen.queryByRole("button", { name: "Join" })).toBe(null);
|
||||
screen.getAllByText(/\bwidget\b/i);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO: Fix I do not understand this test
|
||||
*/
|
||||
|
@ -166,40 +152,17 @@ describe("CallView", () => {
|
|||
expectAvatars([]);
|
||||
});
|
||||
|
||||
it("connects to the call when the join button is pressed", async () => {
|
||||
await renderView();
|
||||
const connectSpy = jest.spyOn(call, "connect");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
it("automatically connects to the call when skipLobby is true", async () => {
|
||||
const connectSpy = jest.spyOn(call, "start");
|
||||
await renderView(true);
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled(), { interval: 1 });
|
||||
});
|
||||
|
||||
it("disables join button when the participant limit has been exceeded", async () => {
|
||||
const bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||
const carol = mkRoomMember(room.roomId, "@carol:example.org");
|
||||
|
||||
SdkConfig.put({
|
||||
element_call: { participant_limit: 2, url: "", use_exclusively: false, brand: "Element Call" },
|
||||
});
|
||||
call.participants = new Map([
|
||||
[bob, new Set("b")],
|
||||
[carol, new Set("c")],
|
||||
]);
|
||||
|
||||
await renderView();
|
||||
const connectSpy = jest.spyOn(call, "connect");
|
||||
const joinButton = screen.getByRole("button", { name: "Join" });
|
||||
expect(joinButton).toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(joinButton);
|
||||
await waitFor(() => expect(connectSpy).not.toHaveBeenCalled(), { interval: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("without an existing call", () => {
|
||||
it("creates and connects to a new call when the join button is pressed", async () => {
|
||||
await renderView();
|
||||
expect(Call.get(room)).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||
await renderView(true);
|
||||
await waitFor(() => expect(CallStore.instance.getCall(room.roomId)).not.toBeNull());
|
||||
const call = CallStore.instance.getCall(room.roomId)!;
|
||||
|
||||
|
@ -214,117 +177,4 @@ describe("CallView", () => {
|
|||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("device buttons", () => {
|
||||
const fakeVideoInput1: MediaDeviceInfo = {
|
||||
deviceId: "v1",
|
||||
groupId: "v1",
|
||||
label: "Webcam",
|
||||
kind: "videoinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeVideoInput2: MediaDeviceInfo = {
|
||||
deviceId: "v2",
|
||||
groupId: "v2",
|
||||
label: "Othercam",
|
||||
kind: "videoinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeAudioInput1: MediaDeviceInfo = {
|
||||
deviceId: "v1",
|
||||
groupId: "v1",
|
||||
label: "Headphones",
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
const fakeAudioInput2: MediaDeviceInfo = {
|
||||
deviceId: "v2",
|
||||
groupId: "v2",
|
||||
label: "Tailphones",
|
||||
kind: "audioinput",
|
||||
toJSON: () => {},
|
||||
};
|
||||
|
||||
it("hide when no devices are available", async () => {
|
||||
await renderView();
|
||||
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||
});
|
||||
|
||||
it("hide when no access to device list", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockRejectedValue("permission denied");
|
||||
await renderView();
|
||||
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||
});
|
||||
|
||||
it("hide when unknown error with device list", async () => {
|
||||
const originalGetDevices = MediaDeviceHandler.getDevices;
|
||||
MediaDeviceHandler.getDevices = () => Promise.reject("unknown error");
|
||||
await renderView();
|
||||
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||
MediaDeviceHandler.getDevices = originalGetDevices;
|
||||
});
|
||||
|
||||
it("show without dropdown when only one device is available", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /camera/ });
|
||||
expect(screen.queryByRole("button", { name: "Video devices" })).toBe(null);
|
||||
});
|
||||
|
||||
it("show with dropdown when multiple devices are available", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeAudioInput1, fakeAudioInput2]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /microphone/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Audio devices" }));
|
||||
screen.getByRole("menuitem", { name: "Headphones" });
|
||||
screen.getByRole("menuitem", { name: "Tailphones" });
|
||||
});
|
||||
|
||||
it("sets video device when selected", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1, fakeVideoInput2]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /camera/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Video devices" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: fakeVideoInput2.label }));
|
||||
|
||||
expect(client.getMediaHandler().setVideoInput).toHaveBeenCalledWith(fakeVideoInput2.deviceId);
|
||||
});
|
||||
|
||||
it("sets audio device when selected", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeAudioInput1, fakeAudioInput2]);
|
||||
|
||||
await renderView();
|
||||
screen.getByRole("button", { name: /microphone/ });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Audio devices" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: fakeAudioInput2.label }));
|
||||
|
||||
expect(client.getMediaHandler().setAudioInput).toHaveBeenCalledWith(fakeAudioInput2.deviceId);
|
||||
});
|
||||
|
||||
it("set media muted if no access to audio device", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeAudioInput1, fakeAudioInput2]);
|
||||
mocked(navigator.mediaDevices.getUserMedia).mockRejectedValue("permission rejected");
|
||||
await renderView();
|
||||
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||
});
|
||||
|
||||
it("set media muted if no access to video device", async () => {
|
||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1, fakeVideoInput2]);
|
||||
mocked(navigator.mediaDevices.getUserMedia).mockRejectedValue("permission rejected");
|
||||
await renderView();
|
||||
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue