171 lines
4.8 KiB
TypeScript
171 lines
4.8 KiB
TypeScript
|
|
/*
|
||
|
|
Copyright 2025 New Vector Ltd.
|
||
|
|
|
||
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||
|
|
Please see LICENSE in the repository root for full details.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { describe, expect, test, vi } from "vitest";
|
||
|
|
import { render, screen } from "@testing-library/react";
|
||
|
|
import { type ReactElement, type ReactNode } from "react";
|
||
|
|
import { BrowserRouter } from "react-router-dom";
|
||
|
|
import userEvent from "@testing-library/user-event";
|
||
|
|
|
||
|
|
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
|
||
|
|
import {
|
||
|
|
ConnectionLostError,
|
||
|
|
E2EENotSupportedError,
|
||
|
|
type ElementCallError,
|
||
|
|
InsufficientCapacityError,
|
||
|
|
MatrixRTCFocusMissingError,
|
||
|
|
UnknownCallError,
|
||
|
|
} from "../utils/errors.ts";
|
||
|
|
import { mockConfig } from "../utils/test.ts";
|
||
|
|
|
||
|
|
test.each([
|
||
|
|
{
|
||
|
|
error: new MatrixRTCFocusMissingError("example.com"),
|
||
|
|
expectedTitle: "Call is not supported",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
error: new ConnectionLostError(),
|
||
|
|
expectedTitle: "Connection lost",
|
||
|
|
expectedDescription: "You were disconnected from the call.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
error: new E2EENotSupportedError(),
|
||
|
|
expectedTitle: "Incompatible browser",
|
||
|
|
expectedDescription:
|
||
|
|
"Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
error: new InsufficientCapacityError(),
|
||
|
|
expectedTitle: "Insufficient capacity",
|
||
|
|
expectedDescription:
|
||
|
|
"The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.",
|
||
|
|
},
|
||
|
|
])(
|
||
|
|
"should report correct error for $expectedTitle",
|
||
|
|
async ({ error, expectedTitle, expectedDescription }) => {
|
||
|
|
const TestComponent = (): ReactNode => {
|
||
|
|
throw error;
|
||
|
|
};
|
||
|
|
|
||
|
|
const onErrorMock = vi.fn();
|
||
|
|
const { asFragment } = render(
|
||
|
|
<BrowserRouter>
|
||
|
|
<GroupCallErrorBoundary onError={onErrorMock}>
|
||
|
|
<TestComponent />
|
||
|
|
</GroupCallErrorBoundary>
|
||
|
|
</BrowserRouter>,
|
||
|
|
);
|
||
|
|
|
||
|
|
await screen.findByText(expectedTitle);
|
||
|
|
if (expectedDescription) {
|
||
|
|
expect(screen.queryByText(expectedDescription)).toBeInTheDocument();
|
||
|
|
}
|
||
|
|
expect(onErrorMock).toHaveBeenCalledWith(error);
|
||
|
|
|
||
|
|
expect(asFragment()).toMatchSnapshot();
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
test("should render the error page with link back to home", async () => {
|
||
|
|
const error = new MatrixRTCFocusMissingError("example.com");
|
||
|
|
const TestComponent = (): ReactNode => {
|
||
|
|
throw error;
|
||
|
|
};
|
||
|
|
|
||
|
|
const onErrorMock = vi.fn();
|
||
|
|
const { asFragment } = render(
|
||
|
|
<BrowserRouter>
|
||
|
|
<GroupCallErrorBoundary onError={onErrorMock}>
|
||
|
|
<TestComponent />
|
||
|
|
</GroupCallErrorBoundary>
|
||
|
|
</BrowserRouter>,
|
||
|
|
);
|
||
|
|
|
||
|
|
await screen.findByText("Call is not supported");
|
||
|
|
expect(screen.getByText(/Domain: example.com/i)).toBeInTheDocument();
|
||
|
|
expect(
|
||
|
|
screen.getByText(/Error Code: MISSING_MATRIX_RTC_FOCUS/i),
|
||
|
|
).toBeInTheDocument();
|
||
|
|
|
||
|
|
await screen.findByRole("button", { name: "Return to home screen" });
|
||
|
|
|
||
|
|
expect(onErrorMock).toHaveBeenCalledOnce();
|
||
|
|
expect(onErrorMock).toHaveBeenCalledWith(error);
|
||
|
|
|
||
|
|
expect(asFragment()).toMatchSnapshot();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("should have a reconnect button for ConnectionLostError", async () => {
|
||
|
|
const user = userEvent.setup();
|
||
|
|
|
||
|
|
const reconnectCallback = vi.fn();
|
||
|
|
|
||
|
|
const TestComponent = (): ReactNode => {
|
||
|
|
throw new ConnectionLostError();
|
||
|
|
};
|
||
|
|
|
||
|
|
const { asFragment } = render(
|
||
|
|
<BrowserRouter>
|
||
|
|
<GroupCallErrorBoundary
|
||
|
|
onError={vi.fn()}
|
||
|
|
recoveryActionHandler={reconnectCallback}
|
||
|
|
>
|
||
|
|
<TestComponent />
|
||
|
|
</GroupCallErrorBoundary>
|
||
|
|
</BrowserRouter>,
|
||
|
|
);
|
||
|
|
|
||
|
|
await screen.findByText("Connection lost");
|
||
|
|
await screen.findByRole("button", { name: "Reconnect" });
|
||
|
|
await screen.findByRole("button", { name: "Return to home screen" });
|
||
|
|
|
||
|
|
expect(asFragment()).toMatchSnapshot();
|
||
|
|
|
||
|
|
await user.click(screen.getByRole("button", { name: "Reconnect" }));
|
||
|
|
|
||
|
|
expect(reconnectCallback).toHaveBeenCalledOnce();
|
||
|
|
expect(reconnectCallback).toHaveBeenCalledWith("reconnect");
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Rageshake button", () => {
|
||
|
|
function setupTest(testError: ElementCallError): void {
|
||
|
|
mockConfig({
|
||
|
|
rageshake: {
|
||
|
|
submit_url: "https://rageshake.example.com.localhost",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const TestComponent = (): ReactElement => {
|
||
|
|
throw testError;
|
||
|
|
};
|
||
|
|
|
||
|
|
render(
|
||
|
|
<BrowserRouter>
|
||
|
|
<GroupCallErrorBoundary onError={vi.fn()}>
|
||
|
|
<TestComponent />
|
||
|
|
</GroupCallErrorBoundary>
|
||
|
|
</BrowserRouter>,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
test("should show send rageshake button for unknown errors", () => {
|
||
|
|
setupTest(new UnknownCallError(new Error("FOO")));
|
||
|
|
|
||
|
|
expect(
|
||
|
|
screen.queryByRole("button", { name: "Send debug logs" }),
|
||
|
|
).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("should not show send rageshake button for call errors", () => {
|
||
|
|
setupTest(new E2EENotSupportedError());
|
||
|
|
|
||
|
|
expect(
|
||
|
|
screen.queryByRole("button", { name: "Send debug logs" }),
|
||
|
|
).not.toBeInTheDocument();
|
||
|
|
});
|
||
|
|
});
|