Merge pull request #3547 from element-hq/valere/fix_blank_widget_auto_leave
fix: Send close widget action on auto-leave
This commit is contained in:
@@ -29,6 +29,7 @@ import userEvent from "@testing-library/user-event";
|
|||||||
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container";
|
import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
|
import { type ITransport } from "matrix-widget-api";
|
||||||
|
|
||||||
import { prefetchSounds } from "../soundUtils";
|
import { prefetchSounds } from "../soundUtils";
|
||||||
import { useAudioContext } from "../useAudioContext";
|
import { useAudioContext } from "../useAudioContext";
|
||||||
@@ -43,7 +44,7 @@ import {
|
|||||||
MockRTCSession,
|
MockRTCSession,
|
||||||
} from "../utils/test";
|
} from "../utils/test";
|
||||||
import { GroupCallView } from "./GroupCallView";
|
import { GroupCallView } from "./GroupCallView";
|
||||||
import { type WidgetHelpers } from "../widget";
|
import { ElementWidgetActions, type WidgetHelpers } from "../widget";
|
||||||
import { LazyEventEmitter } from "../LazyEventEmitter";
|
import { LazyEventEmitter } from "../LazyEventEmitter";
|
||||||
import { MatrixRTCTransportMissingError } from "../utils/errors";
|
import { MatrixRTCTransportMissingError } from "../utils/errors";
|
||||||
import { ProcessorProvider } from "../livekit/TrackProcessorContext";
|
import { ProcessorProvider } from "../livekit/TrackProcessorContext";
|
||||||
@@ -112,6 +113,10 @@ beforeEach(() => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => onLeave("user")}>Leave</button>
|
<button onClick={() => onLeave("user")}>Leave</button>
|
||||||
|
<button onClick={() => onLeave("allOthersLeft")}>
|
||||||
|
SimulateOtherLeft
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onLeave("error")}>SimulateErrorLeft</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -243,6 +248,112 @@ test.skip("GroupCallView plays a leave sound synchronously in widget mode", asyn
|
|||||||
expect(leaveRTCSession).toHaveBeenCalledOnce();
|
expect(leaveRTCSession).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.skip("Should close widget when all other left and have time to play a sound", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const widgetClosedCalled = Promise.withResolvers<void>();
|
||||||
|
const widgetSendMock = vi.fn().mockImplementation((action: string) => {
|
||||||
|
if (action === ElementWidgetActions.Close) {
|
||||||
|
widgetClosedCalled.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const widgetStopMock = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const widget = {
|
||||||
|
api: {
|
||||||
|
setAlwaysOnScreen: vi.fn().mockResolvedValue(true),
|
||||||
|
transport: {
|
||||||
|
send: widgetSendMock,
|
||||||
|
reply: vi.fn().mockResolvedValue(undefined),
|
||||||
|
stop: widgetStopMock,
|
||||||
|
} as unknown as ITransport,
|
||||||
|
} as Partial<WidgetHelpers["api"]>,
|
||||||
|
lazyActions: new LazyEventEmitter(),
|
||||||
|
};
|
||||||
|
const resolvePlaySound = Promise.withResolvers<void>();
|
||||||
|
playSound = vi.fn().mockReturnValue(resolvePlaySound);
|
||||||
|
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||||
|
playSound,
|
||||||
|
playSoundLooping: vitest.fn(),
|
||||||
|
soundDuration: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByText } = createGroupCallView(widget as WidgetHelpers);
|
||||||
|
const leaveButton = getByText("SimulateOtherLeft");
|
||||||
|
await user.click(leaveButton);
|
||||||
|
await flushPromises();
|
||||||
|
expect(widgetSendMock).not.toHaveBeenCalled();
|
||||||
|
resolvePlaySound.resolve();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(playSound).toHaveBeenCalledWith("left");
|
||||||
|
|
||||||
|
await widgetClosedCalled.promise;
|
||||||
|
await flushPromises();
|
||||||
|
expect(widgetStopMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should close widget when all other left", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const widgetClosedCalled = Promise.withResolvers<void>();
|
||||||
|
const widgetSendMock = vi.fn().mockImplementation((action: string) => {
|
||||||
|
if (action === ElementWidgetActions.Close) {
|
||||||
|
widgetClosedCalled.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const widgetStopMock = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const widget = {
|
||||||
|
api: {
|
||||||
|
setAlwaysOnScreen: vi.fn().mockResolvedValue(true),
|
||||||
|
transport: {
|
||||||
|
send: widgetSendMock,
|
||||||
|
reply: vi.fn().mockResolvedValue(undefined),
|
||||||
|
stop: widgetStopMock,
|
||||||
|
} as unknown as ITransport,
|
||||||
|
} as Partial<WidgetHelpers["api"]>,
|
||||||
|
lazyActions: new LazyEventEmitter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByText } = createGroupCallView(widget as WidgetHelpers);
|
||||||
|
const leaveButton = getByText("SimulateOtherLeft");
|
||||||
|
await user.click(leaveButton);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
await widgetClosedCalled.promise;
|
||||||
|
await flushPromises();
|
||||||
|
expect(widgetStopMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should not close widget when auto leave due to error", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
const widgetStopMock = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const widgetSendMock = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const widget = {
|
||||||
|
api: {
|
||||||
|
setAlwaysOnScreen: vi.fn().mockResolvedValue(true),
|
||||||
|
transport: {
|
||||||
|
send: widgetSendMock,
|
||||||
|
reply: vi.fn().mockResolvedValue(undefined),
|
||||||
|
stop: widgetStopMock,
|
||||||
|
} as unknown as ITransport,
|
||||||
|
} as Partial<WidgetHelpers["api"]>,
|
||||||
|
lazyActions: new LazyEventEmitter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const alwaysOnScreenSpy = vi.spyOn(widget.api, "setAlwaysOnScreen");
|
||||||
|
|
||||||
|
const { getByText } = createGroupCallView(widget as WidgetHelpers);
|
||||||
|
const leaveButton = getByText("SimulateErrorLeft");
|
||||||
|
await user.click(leaveButton);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// When onLeft is called, we first set always on screen to false
|
||||||
|
await waitFor(() => expect(alwaysOnScreenSpy).toHaveBeenCalledWith(false));
|
||||||
|
await flushPromises();
|
||||||
|
// But then we do not close the widget automatically
|
||||||
|
expect(widgetStopMock).not.toHaveBeenCalledOnce();
|
||||||
|
expect(widgetSendMock).not.toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
test.skip("GroupCallView leaves the session when an error occurs", async () => {
|
test.skip("GroupCallView leaves the session when an error occurs", async () => {
|
||||||
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(() => {
|
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(() => {
|
||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|||||||
@@ -313,7 +313,9 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onLeft = useCallback(
|
const onLeft = useCallback(
|
||||||
(reason: "timeout" | "user" | "allOthersLeft" | "decline"): void => {
|
(
|
||||||
|
reason: "timeout" | "user" | "allOthersLeft" | "decline" | "error",
|
||||||
|
): void => {
|
||||||
let playSound: CallEventSounds = "left";
|
let playSound: CallEventSounds = "left";
|
||||||
if (reason === "timeout" || reason === "decline") playSound = reason;
|
if (reason === "timeout" || reason === "decline") playSound = reason;
|
||||||
|
|
||||||
@@ -366,7 +368,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
// On a normal user hangup we can shut down and close the widget. But if an
|
// On a normal user hangup we can shut down and close the widget. But if an
|
||||||
// error occurs we should keep the widget open until the user reads it.
|
// error occurs we should keep the widget open until the user reads it.
|
||||||
if (reason === "user" && !getUrlParams().returnToLobby) {
|
if (reason != "error" && !getUrlParams().returnToLobby) {
|
||||||
try {
|
try {
|
||||||
await widget.api.transport.send(ElementWidgetActions.Close, {});
|
await widget.api.transport.send(ElementWidgetActions.Close, {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -518,8 +520,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
onError={
|
onError={
|
||||||
(/**error*/) => {
|
(/**error*/) => {
|
||||||
// TODO this should not be "user". It needs a new case
|
if (rtcSession.isJoined()) onLeft("error");
|
||||||
if (rtcSession.isJoined()) onLeft("user");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -115,7 +115,9 @@ export interface ActiveCallProps
|
|||||||
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
||||||
e2eeSystem: EncryptionSystem;
|
e2eeSystem: EncryptionSystem;
|
||||||
// TODO refactor those reasons into an enum
|
// TODO refactor those reasons into an enum
|
||||||
onLeft: (reason: "user" | "timeout" | "decline" | "allOthersLeft") => void;
|
onLeft: (
|
||||||
|
reason: "user" | "timeout" | "decline" | "allOthersLeft" | "error",
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user