Prepare for repo merge

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-10-15 11:35:21 +01:00
parent 0f670b8dc0
commit b084ff2313
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
807 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,245 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen, cleanup, fireEvent, waitFor } from "jest-matrix-react";
import { mocked, Mocked } from "jest-mock";
import { Room, RoomStateEvent, MatrixEvent, MatrixEventEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import { ICallNotifyContent } from "matrix-js-sdk/src/matrixrtc";
import type { RoomMember } from "matrix-js-sdk/src/matrix";
import {
useMockedCalls,
MockedCall,
stubClient,
mkRoomMember,
setupAsyncStoreWithClient,
resetAsyncStoreWithClient,
} from "../test-utils";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
import { Action } from "../../src/dispatcher/actions";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import { CallStore } from "../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingStore";
import DMRoomMap from "../../src/utils/DMRoomMap";
import ToastStore from "../../src/stores/ToastStore";
import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/IncomingCallToast";
import LegacyCallHandler, { AudioID } from "../../src/LegacyCallHandler";
describe("IncomingCallToast", () => {
useMockedCalls();
let client: Mocked<MatrixClient>;
let room: Room;
let notifyContent: ICallNotifyContent;
let alice: RoomMember;
let bob: RoomMember;
let call: MockedCall;
let widget: Widget;
const dmRoomMap = {
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap;
const toastStore = {
dismissToast: jest.fn(),
} as unknown as ToastStore;
beforeEach(async () => {
stubClient();
client = mocked(MatrixClientPeg.safeGet());
const audio = document.createElement("audio");
audio.id = AudioID.Ring;
document.body.appendChild(audio);
room = new Room("!1:example.org", client, "@alice:example.org");
notifyContent = {
call_id: "",
getRoomId: () => room.roomId,
} as unknown as ICallNotifyContent;
alice = mkRoomMember(room.roomId, "@alice:example.org");
bob = mkRoomMember(room.roomId, "@bob:example.org");
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
MockedCall.create(room, "1");
await Promise.all(
[CallStore.instance, WidgetMessagingStore.instance].map((store) =>
setupAsyncStoreWithClient(store, client),
),
);
const maybeCall = CallStore.instance.getCall(room.roomId);
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
call = maybeCall;
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
jest.spyOn(ToastStore, "sharedInstance").mockReturnValue(toastStore);
});
afterEach(async () => {
cleanup(); // Unmount before we do any cleanup that might update the component
call.destroy();
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(resetAsyncStoreWithClient));
jest.restoreAllMocks();
});
const renderToast = () => {
call.event.getContent = () => notifyContent as any;
render(<IncomingCallToast notifyEvent={call.event} />);
};
it("correctly shows all the information", () => {
call.participants = new Map([
[alice, new Set("a")],
[bob, new Set(["b1", "b2"])],
]);
renderToast();
screen.getByText("Video call started");
screen.getByText("Video");
screen.getByLabelText("3 people joined");
screen.getByRole("button", { name: "Join" });
screen.getByRole("button", { name: "Close" });
});
it("start ringing on ring notify event", () => {
call.event.getContent = () =>
({
...notifyContent,
notify_type: "ring",
}) as any;
const playMock = jest.spyOn(LegacyCallHandler.instance, "play");
render(<IncomingCallToast notifyEvent={call.event} />);
expect(playMock).toHaveBeenCalled();
});
it("correctly renders toast without a call", () => {
call.destroy();
renderToast();
screen.getByText("Video call started");
screen.getByText("Video");
screen.getByRole("button", { name: "Join" });
screen.getByRole("button", { name: "Close" });
});
it("joins the call and closes the toast", async () => {
renderToast();
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Join" }));
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
skipLobby: false,
view_call: true,
}),
);
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
defaultDispatcher.unregister(dispatcherRef);
});
it("Dismiss toast if user starts call and skips lobby when using shift key click", async () => {
renderToast();
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Join" }), { shiftKey: true });
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
skipLobby: true,
view_call: true,
}),
);
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
defaultDispatcher.unregister(dispatcherRef);
});
it("closes the toast", async () => {
renderToast();
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Close" }));
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
defaultDispatcher.unregister(dispatcherRef);
});
it("closes toast when the call lobby is viewed", async () => {
renderToast();
defaultDispatcher.dispatch({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
});
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
});
it("closes toast when the call event is redacted", async () => {
renderToast();
const event = room.currentState.getStateEvents(MockedCall.EVENT_TYPE, "1")!;
event.emit(MatrixEventEvent.BeforeRedaction, event, {} as unknown as MatrixEvent);
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
});
it("closes toast when the matrixRTC session has ended", async () => {
renderToast();
call.destroy();
await waitFor(() =>
expect(toastStore.dismissToast).toHaveBeenCalledWith(
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
),
);
});
});

View file

@ -0,0 +1,72 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { render } from "jest-matrix-react";
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import React from "react";
import LegacyCallHandler from "../../src/LegacyCallHandler";
import IncomingLegacyCallToast from "../../src/toasts/IncomingLegacyCallToast";
import DMRoomMap from "../../src/utils/DMRoomMap";
import { getMockClientWithEventEmitter, mockClientMethodsServer, mockClientMethodsUser } from "../test-utils";
describe("<IncomingLegacyCallToast />", () => {
const userId = "@alice:server.org";
const deviceId = "my-device";
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap);
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getRoom: jest.fn(),
});
const mockRoom = new Room("!room:server.org", mockClient, userId);
mockClient.deviceId = deviceId;
const call = new MatrixCall({ client: mockClient, roomId: mockRoom.roomId });
const defaultProps = {
call,
};
const getComponent = (props = {}) => <IncomingLegacyCallToast {...defaultProps} {...props} />;
beforeEach(() => {
jest.clearAllMocks();
mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getRoom.mockReturnValue(mockRoom);
});
it("renders when silence button when call is not silenced", () => {
const { getByLabelText } = render(getComponent());
expect(getByLabelText("Silence call")).toMatchSnapshot();
});
it("renders sound on button when call is silenced", () => {
LegacyCallHandler.instance.silenceCall(call.callId);
const { getByLabelText } = render(getComponent());
expect(getByLabelText("Sound on")).toMatchSnapshot();
});
it("renders disabled silenced button when call is forced to silent", () => {
// silence local notifications -> force call ringer to silent
mockClient.getAccountData.mockImplementation((eventType) => {
if (eventType.includes(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
return new MatrixEvent({
type: eventType,
content: {
is_silenced: true,
},
});
}
});
const { getByLabelText } = render(getComponent());
expect(getByLabelText("Notifications silenced")).toMatchSnapshot();
});
});

View file

@ -0,0 +1,103 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, RenderResult, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { mocked, Mocked } from "jest-mock";
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import dis from "../../src/dispatcher/dispatcher";
import { showToast } from "../../src/toasts/UnverifiedSessionToast";
import { filterConsole, flushPromises, stubClient } from "../test-utils";
import ToastContainer from "../../src/components/structures/ToastContainer";
import { Action } from "../../src/dispatcher/actions";
import DeviceListener from "../../src/DeviceListener";
describe("UnverifiedSessionToast", () => {
const otherDevice: IMyDevice = {
device_id: "ABC123",
};
const otherDeviceInfo = new DeviceInfo(otherDevice.device_id);
let client: Mocked<MatrixClient>;
let renderResult: RenderResult;
filterConsole("Dismissing unverified sessions: ABC123");
beforeAll(() => {
client = mocked(stubClient());
client.getDevice.mockImplementation(async (deviceId: string) => {
if (deviceId === otherDevice.device_id) {
return otherDevice;
}
throw new Error(`Unknown device ${deviceId}`);
});
client.getStoredDevice.mockImplementation((userId: string, deviceId: string) => {
if (deviceId === otherDevice.device_id) {
return otherDeviceInfo;
}
return null;
});
client.getCrypto.mockReturnValue({
getDeviceVerificationStatus: jest
.fn()
.mockResolvedValue(new DeviceVerificationStatus({ crossSigningVerified: true })),
} as unknown as CryptoApi);
jest.spyOn(dis, "dispatch");
jest.spyOn(DeviceListener.sharedInstance(), "dismissUnverifiedSessions");
});
beforeEach(() => {
renderResult = render(<ToastContainer />);
});
describe("when rendering the toast", () => {
beforeEach(async () => {
showToast(otherDevice.device_id);
await flushPromises();
});
const itShouldDismissTheDevice = () => {
it("should dismiss the device", () => {
expect(DeviceListener.sharedInstance().dismissUnverifiedSessions).toHaveBeenCalledWith([
otherDevice.device_id,
]);
});
};
it("should render as expected", () => {
expect(renderResult.baseElement).toMatchSnapshot();
});
describe("and confirming the login", () => {
beforeEach(async () => {
await userEvent.click(screen.getByRole("button", { name: "Yes, it was me" }));
});
itShouldDismissTheDevice();
});
describe("and dismissing the login", () => {
beforeEach(async () => {
await userEvent.click(screen.getByRole("button", { name: "No" }));
});
itShouldDismissTheDevice();
it("should show the device settings", () => {
expect(dis.dispatch).toHaveBeenCalledWith({
action: Action.ViewUserDeviceSettings,
});
});
});
});
});

View file

@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<IncomingLegacyCallToast /> renders disabled silenced button when call is forced to silent 1`] = `
<div
aria-disabled="true"
aria-label="Notifications silenced"
class="mx_AccessibleButton mx_IncomingLegacyCallToast_iconButton mx_IncomingLegacyCallToast_unSilence mx_AccessibleButton_disabled"
disabled=""
role="button"
tabindex="0"
/>
`;
exports[`<IncomingLegacyCallToast /> renders sound on button when call is silenced 1`] = `
<div
aria-label="Sound on"
class="mx_AccessibleButton mx_IncomingLegacyCallToast_iconButton mx_IncomingLegacyCallToast_unSilence"
role="button"
tabindex="0"
/>
`;
exports[`<IncomingLegacyCallToast /> renders when silence button when call is not silenced 1`] = `
<div
aria-label="Silence call"
class="mx_AccessibleButton mx_IncomingLegacyCallToast_iconButton mx_IncomingLegacyCallToast_silence"
role="button"
tabindex="0"
/>
`;

View file

@ -0,0 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UnverifiedSessionToast when rendering the toast should render as expected 1`] = `
<body>
<div>
<div
class="mx_ToastContainer"
role="alert"
>
<div
class="mx_Toast_toast mx_Toast_hasIcon mx_Toast_icon_verification_warning"
>
<div
class="mx_Toast_title"
>
<h2
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
>
New login. Was this you?
</h2>
<span
class="mx_Toast_title_countIndicator"
/>
</div>
<div
class="mx_Toast_body"
>
<div>
<div
class="mx_Toast_description"
>
<div
class="mx_Toast_detail"
>
<span
data-testid="device-metadata-isVerified"
>
Verified
</span>
·
<span
data-testid="device-metadata-deviceId"
>
ABC123
</span>
</div>
</div>
<div
aria-live="off"
class="mx_Toast_buttons"
>
<button
class="_button_i91xf_17 _destructive_i91xf_116"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
No
</button>
<button
class="_button_i91xf_17"
data-kind="primary"
data-size="sm"
role="button"
tabindex="0"
>
Yes, it was me
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
`;