mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-03 06:55:44 +00:00
Show error screens in group calls (#29254)
* Avoid destroying calls until they are hidden from the UI We often want calls to exist even when no more participants are left in the MatrixRTC session. So, we should avoid destroying calls as long as they're being presented in the UI; this means that the user has an intent to either join the call or continue looking at an error screen, and we shouldn't interrupt that interaction. The RoomViewStore is now what takes care of creating and destroying calls, rather than the CallView. In general it seems kinda impossible to safely create and destroy model objects from React lifecycle hooks, so moving this responsibility to a store seemed appropriate and resolves existing issues with calls in React strict mode. * Wait for a close action before closing a call This creates a distinction between the user hanging up and the widget being ready to close, which is useful for allowing Element Call to show error screens when disconnected from the call, for example. * Don't expect a 'close' action in video rooms These use the returnToLobby option and are expected to remain visible when the user leaves the call.
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
} from "jest-matrix-react";
|
||||
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { filterConsole, stubClient } from "../../../../../test-utils";
|
||||
import RoomHeader from "../../../../../../src/components/views/rooms/RoomHeader/RoomHeader";
|
||||
@@ -106,13 +107,15 @@ describe("RoomHeader", () => {
|
||||
});
|
||||
|
||||
it("opens the room summary", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
fireEvent.click(getByText(container, ROOM_ID));
|
||||
await user.click(getByText(container, ROOM_ID));
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomSummary });
|
||||
});
|
||||
|
||||
it("shows a face pile for rooms", async () => {
|
||||
const user = userEvent.setup();
|
||||
const members = [
|
||||
{
|
||||
userId: "@me:example.org",
|
||||
@@ -161,33 +164,36 @@ describe("RoomHeader", () => {
|
||||
const facePile = getByLabelText(document.body, "4 members");
|
||||
expect(facePile).toHaveTextContent("4");
|
||||
|
||||
fireEvent.click(facePile);
|
||||
await user.click(facePile);
|
||||
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
|
||||
});
|
||||
|
||||
it("has room info icon that opens the room info panel", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { getAllByRole } = render(<RoomHeader room={room} />, getWrapper());
|
||||
const infoButton = getAllByRole("button", { name: "Room info" })[1];
|
||||
fireEvent.click(infoButton);
|
||||
await user.click(infoButton);
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomSummary });
|
||||
});
|
||||
|
||||
it("opens the thread panel", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
fireEvent.click(getByLabelText(document.body, "Threads"));
|
||||
await user.click(getByLabelText(document.body, "Threads"));
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel });
|
||||
});
|
||||
|
||||
it("opens the notifications panel", async () => {
|
||||
const user = userEvent.setup();
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "feature_notifications") return true;
|
||||
});
|
||||
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
fireEvent.click(getByLabelText(document.body, "Notifications"));
|
||||
await user.click(getByLabelText(document.body, "Notifications"));
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.NotificationPanel });
|
||||
});
|
||||
|
||||
@@ -274,6 +280,7 @@ describe("RoomHeader", () => {
|
||||
});
|
||||
|
||||
it("you can call when you're two in the room", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 2);
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
@@ -284,10 +291,10 @@ describe("RoomHeader", () => {
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
|
||||
fireEvent.click(voiceButton);
|
||||
await user.click(voiceButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
|
||||
|
||||
fireEvent.click(videoButton);
|
||||
await user.click(videoButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
|
||||
});
|
||||
|
||||
@@ -332,6 +339,7 @@ describe("RoomHeader", () => {
|
||||
});
|
||||
|
||||
it("renders only the video call element", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 3);
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({ use_exclusively: true });
|
||||
// allow element calls
|
||||
@@ -344,9 +352,9 @@ describe("RoomHeader", () => {
|
||||
const videoCallButton = screen.getByRole("button", { name: "Video call" });
|
||||
expect(videoCallButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
const dispatcherSpy = jest.spyOn(dispatcher, "dispatch").mockImplementation();
|
||||
|
||||
fireEvent.click(videoCallButton);
|
||||
await user.click(videoCallButton);
|
||||
expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ view_call: true }));
|
||||
});
|
||||
|
||||
@@ -366,7 +374,8 @@ describe("RoomHeader", () => {
|
||||
expect(screen.getByRole("button", { name: "Ongoing call" })).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("clicking on ongoing (unpinned) call re-pins it", () => {
|
||||
it("clicking on ongoing (unpinned) call re-pins it", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 3);
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets);
|
||||
// allow calls
|
||||
@@ -386,7 +395,7 @@ describe("RoomHeader", () => {
|
||||
|
||||
const videoButton = screen.getByRole("button", { name: "Video call" });
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(videoButton);
|
||||
await user.click(videoButton);
|
||||
expect(spy).toHaveBeenCalledWith(room, widget, Container.Top);
|
||||
});
|
||||
|
||||
@@ -463,6 +472,7 @@ describe("RoomHeader", () => {
|
||||
});
|
||||
|
||||
it("calls using legacy or jitsi", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 2);
|
||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||
if (key === "im.vector.modular.widgets") return true;
|
||||
@@ -476,14 +486,15 @@ describe("RoomHeader", () => {
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
fireEvent.click(voiceButton);
|
||||
await user.click(voiceButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
|
||||
|
||||
fireEvent.click(videoButton);
|
||||
await user.click(videoButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
|
||||
});
|
||||
|
||||
it("calls using legacy or jitsi for large rooms", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 3);
|
||||
|
||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||
@@ -497,11 +508,12 @@ describe("RoomHeader", () => {
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
fireEvent.click(videoButton);
|
||||
await user.click(videoButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
|
||||
});
|
||||
|
||||
it("calls using element call for large rooms", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockRoomMembers(room, 3);
|
||||
|
||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||
@@ -514,8 +526,8 @@ describe("RoomHeader", () => {
|
||||
const videoButton = screen.getByRole("button", { name: "Video call" });
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
fireEvent.click(videoButton);
|
||||
const dispatcherSpy = jest.spyOn(dispatcher, "dispatch").mockImplementation();
|
||||
await user.click(videoButton);
|
||||
expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ view_call: true }));
|
||||
});
|
||||
|
||||
@@ -750,10 +762,11 @@ describe("RoomHeader", () => {
|
||||
});
|
||||
|
||||
it("should open room settings when clicking the room avatar", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
|
||||
fireEvent.click(getByLabelText(document.body, "Open room settings"));
|
||||
await user.click(getByLabelText(document.body, "Open room settings"));
|
||||
expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ action: "open_room_settings" }));
|
||||
});
|
||||
});
|
||||
|
||||
+8
-6
@@ -43,7 +43,8 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby=":r16c:"
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
class="_icon-button_m2erp_8"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -51,9 +52,10 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_zr2a0_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":r166:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -61,7 +63,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -69,7 +71,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
aria-labelledby=":r16h:"
|
||||
aria-labelledby=":r16b:"
|
||||
class="_icon-button_m2erp_8"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -94,7 +96,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r16m:"
|
||||
aria-labelledby=":r16g:"
|
||||
class="_icon-button_m2erp_8"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -120,7 +122,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r16r:"
|
||||
aria-labelledby=":r16l:"
|
||||
class="_icon-button_m2erp_8"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
||||
Reference in New Issue
Block a user