Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
f0ee7f7905
3265 changed files with 484599 additions and 699 deletions
245
test/unit-tests/toasts/IncomingCallToast-test.tsx
Normal file
245
test/unit-tests/toasts/IncomingCallToast-test.tsx
Normal 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),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
72
test/unit-tests/toasts/IncomingLegacyCallToast-test.tsx
Normal file
72
test/unit-tests/toasts/IncomingLegacyCallToast-test.tsx
Normal 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();
|
||||
});
|
||||
});
|
103
test/unit-tests/toasts/UnverifiedSessionToast-test.tsx
Normal file
103
test/unit-tests/toasts/UnverifiedSessionToast-test.tsx
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
/>
|
||||
`;
|
|
@ -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>
|
||||
`;
|
Loading…
Add table
Add a link
Reference in a new issue