mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-10 21:55:19 +00:00
add more test
This commit is contained in:
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { expect, test, vi } from "vitest";
|
import { expect, test, vi } from "vitest";
|
||||||
import { render } from "@testing-library/react";
|
import { render, waitFor } from "@testing-library/react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
import { type FC } from "react";
|
import { type FC } from "react";
|
||||||
@@ -92,3 +92,24 @@ test("follows the client when the host swaps it", () => {
|
|||||||
|
|
||||||
expect(container.textContent).toBe("@bob:example.org");
|
expect(container.textContent).toBe("@bob:example.org");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("finds a client of its own when the host supplies none", async () => {
|
||||||
|
const client = mockClient();
|
||||||
|
vi.doMock("./utils/spa", () => ({
|
||||||
|
initSPA: vi.fn().mockResolvedValue({ client, passwordlessUser: true }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<ClientProvider>
|
||||||
|
<ShowClientState />
|
||||||
|
</ClientProvider>
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nothing to show until a session has been restored or created
|
||||||
|
expect(container.textContent).toBe("loading");
|
||||||
|
await waitFor(() => expect(container.textContent).toBe("@alice:example.org"));
|
||||||
|
|
||||||
|
vi.doUnmock("./utils/spa");
|
||||||
|
});
|
||||||
|
|||||||
+159
-2
@@ -6,10 +6,16 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, expect, test, vi } from "vitest";
|
import { describe, expect, test, vi } from "vitest";
|
||||||
import { type WidgetApi } from "matrix-widget-api";
|
import { type WidgetApi, WidgetApiToWidgetAction } from "matrix-widget-api";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
|
||||||
import { createWidgetHostBridge, nullHostBridge } from "./HostBridge";
|
import { type Observable } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createWidgetHostBridge,
|
||||||
|
type HostBridge,
|
||||||
|
nullHostBridge,
|
||||||
|
} from "./HostBridge";
|
||||||
import { ElementWidgetActions, type WidgetHelpers } from "./widget";
|
import { ElementWidgetActions, type WidgetHelpers } from "./widget";
|
||||||
|
|
||||||
function mockWidget(api: Partial<WidgetApi>): WidgetHelpers {
|
function mockWidget(api: Partial<WidgetApi>): WidgetHelpers {
|
||||||
@@ -20,7 +26,158 @@ function mockWidget(api: Partial<WidgetApi>): WidgetHelpers {
|
|||||||
} as unknown as WidgetHelpers;
|
} as unknown as WidgetHelpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A widget whose transport records what Element Call sends it. */
|
||||||
|
function mockTransport(): {
|
||||||
|
send: ReturnType<typeof vi.fn>;
|
||||||
|
reply: ReturnType<typeof vi.fn>;
|
||||||
|
stop: ReturnType<typeof vi.fn>;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
send: vi.fn().mockResolvedValue(undefined),
|
||||||
|
reply: vi.fn(),
|
||||||
|
stop: vi.fn(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("createWidgetHostBridge", () => {
|
describe("createWidgetHostBridge", () => {
|
||||||
|
describe("telling the host what Element Call is doing", () => {
|
||||||
|
test("asks to be kept on screen, and to stop being", async () => {
|
||||||
|
const setAlwaysOnScreen = vi.fn().mockResolvedValue(true);
|
||||||
|
const bridge = createWidgetHostBridge(mockWidget({ setAlwaysOnScreen }));
|
||||||
|
|
||||||
|
await bridge.setAlwaysOnScreen(true);
|
||||||
|
await bridge.setAlwaysOnScreen(false);
|
||||||
|
|
||||||
|
expect(setAlwaysOnScreen).toHaveBeenNthCalledWith(1, true);
|
||||||
|
expect(setAlwaysOnScreen).toHaveBeenNthCalledWith(2, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reports that it has loaded", async () => {
|
||||||
|
const sendContentLoaded = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const bridge = createWidgetHostBridge(mockWidget({ sendContentLoaded }));
|
||||||
|
|
||||||
|
await bridge.contentLoaded();
|
||||||
|
|
||||||
|
expect(sendContentLoaded).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["notifyJoined", ElementWidgetActions.JoinCall, {}],
|
||||||
|
["notifyHungUp", ElementWidgetActions.HangupCall, {}],
|
||||||
|
] as const)("sends %s as %s", async (method, action, payload) => {
|
||||||
|
const transport = mockTransport();
|
||||||
|
const bridge = createWidgetHostBridge(mockWidget({ transport } as never));
|
||||||
|
|
||||||
|
await bridge[method]();
|
||||||
|
|
||||||
|
expect(transport.send).toHaveBeenCalledWith(action, payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sends the mute state the host needs to mirror", async () => {
|
||||||
|
const transport = mockTransport();
|
||||||
|
const bridge = createWidgetHostBridge(mockWidget({ transport } as never));
|
||||||
|
|
||||||
|
await bridge.notifyDeviceMute({
|
||||||
|
audio_enabled: true,
|
||||||
|
video_enabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.send).toHaveBeenCalledWith(
|
||||||
|
ElementWidgetActions.DeviceMute,
|
||||||
|
{ audio_enabled: true, video_enabled: false },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("relaying what the host asks for", () => {
|
||||||
|
/** Emits a widget action the way widget.ts does, and returns the event. */
|
||||||
|
function askHost(
|
||||||
|
widget: WidgetHelpers,
|
||||||
|
action: string,
|
||||||
|
data: unknown,
|
||||||
|
): CustomEvent {
|
||||||
|
const ev = new CustomEvent(action, { detail: { action, data } });
|
||||||
|
widget.lazyActions.emit(action, ev);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectors rather than keys, so each stream keeps its own request type
|
||||||
|
const inboundStreams: [
|
||||||
|
name: string,
|
||||||
|
select: (bridge: HostBridge) => Observable<{ data: unknown }>,
|
||||||
|
action: string,
|
||||||
|
][] = [
|
||||||
|
[
|
||||||
|
"themeChange$",
|
||||||
|
(bridge) => bridge.themeChange$,
|
||||||
|
WidgetApiToWidgetAction.ThemeChange,
|
||||||
|
],
|
||||||
|
["join$", (bridge) => bridge.join$, ElementWidgetActions.JoinCall],
|
||||||
|
["hangUp$", (bridge) => bridge.hangUp$, ElementWidgetActions.HangupCall],
|
||||||
|
[
|
||||||
|
"deviceMute$",
|
||||||
|
(bridge) => bridge.deviceMute$,
|
||||||
|
ElementWidgetActions.DeviceMute,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(inboundStreams)(
|
||||||
|
"surfaces %s with the host's data",
|
||||||
|
(_name, select, action) => {
|
||||||
|
const widget = mockWidget({ transport: mockTransport() } as never);
|
||||||
|
const bridge = createWidgetHostBridge(widget);
|
||||||
|
const seen: unknown[] = [];
|
||||||
|
select(bridge).subscribe((request) => seen.push(request.data));
|
||||||
|
|
||||||
|
askHost(widget, action, { some: "payload" });
|
||||||
|
|
||||||
|
expect(seen).toEqual([{ some: "payload" }]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test("replies to the host against the request it made", () => {
|
||||||
|
const transport = mockTransport();
|
||||||
|
const widget = mockWidget({ transport } as never);
|
||||||
|
const bridge = createWidgetHostBridge(widget);
|
||||||
|
bridge.deviceMute$.subscribe((request) =>
|
||||||
|
request.reply({ audio_enabled: false, video_enabled: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ev = askHost(widget, ElementWidgetActions.DeviceMute, {
|
||||||
|
audio_enabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.reply).toHaveBeenCalledWith(ev.detail, {
|
||||||
|
audio_enabled: false,
|
||||||
|
video_enabled: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("still replies when there is nothing to say", () => {
|
||||||
|
const transport = mockTransport();
|
||||||
|
const widget = mockWidget({ transport } as never);
|
||||||
|
const bridge = createWidgetHostBridge(widget);
|
||||||
|
bridge.hangUp$.subscribe((request) => request.reply());
|
||||||
|
|
||||||
|
const ev = askHost(widget, ElementWidgetActions.HangupCall, {});
|
||||||
|
|
||||||
|
// The widget API requires an answer, so an empty reply becomes {}
|
||||||
|
expect(transport.reply).toHaveBeenCalledWith(ev.detail, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("stops listening once unsubscribed", () => {
|
||||||
|
const widget = mockWidget({ transport: mockTransport() } as never);
|
||||||
|
const bridge = createWidgetHostBridge(widget);
|
||||||
|
const seen: unknown[] = [];
|
||||||
|
const subscription = bridge.hangUp$.subscribe((r) => seen.push(r.data));
|
||||||
|
|
||||||
|
subscription.unsubscribe();
|
||||||
|
askHost(widget, ElementWidgetActions.HangupCall, {});
|
||||||
|
|
||||||
|
expect(seen).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("downloadMedia", () => {
|
describe("downloadMedia", () => {
|
||||||
const mxcUri = "mxc://example.org/alice-avatar";
|
const mxcUri = "mxc://example.org/alice-avatar";
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
afterAll,
|
afterAll,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import posthog, { type CaptureResult } from "posthog-js";
|
import posthog, { type CaptureResult } from "posthog-js";
|
||||||
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Anonymity,
|
Anonymity,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
} from "./PosthogAnalytics";
|
} from "./PosthogAnalytics";
|
||||||
import { mockConfig } from "../utils/test";
|
import { mockConfig } from "../utils/test";
|
||||||
import { analyticsConfigFromEnvironment } from "../initializer";
|
import { analyticsConfigFromEnvironment } from "../initializer";
|
||||||
|
import { optInAnalytics } from "../settings/settings";
|
||||||
|
|
||||||
describe("PosthogAnalytics", () => {
|
describe("PosthogAnalytics", () => {
|
||||||
describe("enablement", () => {
|
describe("enablement", () => {
|
||||||
@@ -281,3 +283,86 @@ describe("PosthogAnalytics", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("identifying the user", () => {
|
||||||
|
const credentials = {
|
||||||
|
apiKey: "api_key",
|
||||||
|
apiHost: "https://api.example.com.localhost",
|
||||||
|
};
|
||||||
|
|
||||||
|
function mockClient(accountDataId: string | null): MatrixClient {
|
||||||
|
return {
|
||||||
|
isGuest: () => false,
|
||||||
|
getCrypto: () => undefined,
|
||||||
|
getAccountDataFromServer: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(
|
||||||
|
accountDataId === null ? null : { id: accountDataId },
|
||||||
|
),
|
||||||
|
setAccountData: vi.fn().mockResolvedValue({}),
|
||||||
|
} as Partial<MatrixClient> as MatrixClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
PosthogAnalytics.resetInstance();
|
||||||
|
optInAnalytics.setValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports under the ID its host assigned, and stores nothing", async () => {
|
||||||
|
const client = mockClient(null);
|
||||||
|
window.matrixclient = client;
|
||||||
|
PosthogAnalytics.configure({
|
||||||
|
...credentials,
|
||||||
|
matrixBackend: "embedded",
|
||||||
|
hostAnalyticsId: "assigned-by-host",
|
||||||
|
});
|
||||||
|
const identify = vi.spyOn(posthog, "identify");
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.startListeningToSettingsChanges();
|
||||||
|
await vi.waitFor(() =>
|
||||||
|
expect(identify).toHaveBeenCalledWith("assigned-by-host"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The host owns the user's account, so Element Call must not write to it
|
||||||
|
expect(client.setAccountData).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps its own ID in account data when it owns the session", async () => {
|
||||||
|
const client = mockClient(null);
|
||||||
|
window.matrixclient = client;
|
||||||
|
PosthogAnalytics.configure({ ...credentials, matrixBackend: "jssdk" });
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.startListeningToSettingsChanges();
|
||||||
|
|
||||||
|
// No ID on the server yet, so one is minted and stored for other devices
|
||||||
|
await vi.waitFor(() => expect(client.setAccountData).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reuses the ID already in account data", async () => {
|
||||||
|
const client = mockClient("stored-earlier");
|
||||||
|
window.matrixclient = client;
|
||||||
|
PosthogAnalytics.configure({ ...credentials, matrixBackend: "jssdk" });
|
||||||
|
const identify = vi.spyOn(posthog, "identify");
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.startListeningToSettingsChanges();
|
||||||
|
await vi.waitFor(() =>
|
||||||
|
expect(identify).toHaveBeenCalledWith("stored-earlier"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(client.setAccountData).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("records how it reaches Matrix as a super property", async () => {
|
||||||
|
window.matrixclient = mockClient("stored-earlier");
|
||||||
|
PosthogAnalytics.configure({ ...credentials, matrixBackend: "embedded" });
|
||||||
|
const register = vi.spyOn(posthog, "register");
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.startListeningToSettingsChanges();
|
||||||
|
|
||||||
|
await vi.waitFor(() =>
|
||||||
|
expect(register).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ matrixBackend: "embedded" }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { getKeyForRoom, saveKeyForRoom } from "./sharedKeyManagement";
|
||||||
|
|
||||||
|
const roomId = "!room:example.org";
|
||||||
|
|
||||||
|
describe("getKeyForRoom", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
window.location.hash = "#";
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prefers a key given in the parameters over the stored one", () => {
|
||||||
|
saveKeyForRoom(roomId, "stored");
|
||||||
|
window.location.hash = `#?roomId=${encodeURIComponent(roomId)}&password=from-the-link`;
|
||||||
|
|
||||||
|
expect(getKeyForRoom(roomId)).toBe("from-the-link");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("falls back to the stored key", () => {
|
||||||
|
saveKeyForRoom(roomId, "stored");
|
||||||
|
|
||||||
|
expect(getKeyForRoom(roomId)).toBe("stored");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("has no key to offer for a room it has never seen", () => {
|
||||||
|
expect(getKeyForRoom(roomId)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations 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 { of } from "rxjs";
|
||||||
|
|
||||||
|
const getPlatform = vi.hoisted(() => vi.fn(() => "desktop"));
|
||||||
|
vi.mock("../Platform", () => ({
|
||||||
|
get platform(): string {
|
||||||
|
return getPlatform();
|
||||||
|
},
|
||||||
|
isFirefox: (): boolean => false,
|
||||||
|
}));
|
||||||
|
vi.mock("@livekit/components-core", () => ({
|
||||||
|
createMediaDeviceObserver: () => of([]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { AudioOutput, MediaDevices } from "./MediaDevices";
|
||||||
|
import { AndroidControlledAudioOutput } from "./AndroidControlledAudioOutput";
|
||||||
|
import { IOSControlledAudioOutput } from "./IOSControlledAudioOutput";
|
||||||
|
import { ObservableScope } from "./ObservableScope";
|
||||||
|
|
||||||
|
// Which audio output implementation is used is decided by what the app hosting
|
||||||
|
// Element Call told it, rather than being discovered from the environment.
|
||||||
|
describe("MediaDevices audio output", () => {
|
||||||
|
test("uses the browser's own output when nobody else is controlling it", () => {
|
||||||
|
const devices = new MediaDevices(new ObservableScope(), {
|
||||||
|
controlledAudioDevices: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(devices.audioOutput).toBeInstanceOf(AudioOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hands control to the host on Android", () => {
|
||||||
|
getPlatform.mockReturnValue("android");
|
||||||
|
|
||||||
|
const devices = new MediaDevices(new ObservableScope(), {
|
||||||
|
controlledAudioDevices: true,
|
||||||
|
callIntent: "audio",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(devices.audioOutput).toBeInstanceOf(AndroidControlledAudioOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hands control to the host elsewhere too", () => {
|
||||||
|
getPlatform.mockReturnValue("ios");
|
||||||
|
|
||||||
|
const devices = new MediaDevices(new ObservableScope(), {
|
||||||
|
controlledAudioDevices: true,
|
||||||
|
callIntent: "video",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(devices.audioOutput).toBeInstanceOf(IOSControlledAudioOutput);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations 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 } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ErrorCategory,
|
||||||
|
ErrorCode,
|
||||||
|
FailToStartLivekitConnection,
|
||||||
|
MembershipManagerError,
|
||||||
|
NoMatrix2AuthorizationService,
|
||||||
|
SFURoomCreationRestrictedError,
|
||||||
|
} from "./errors";
|
||||||
|
|
||||||
|
// These errors take their wording from Element Call's own i18next instance
|
||||||
|
// rather than the global one, so each needs to come out translated rather than
|
||||||
|
// as a raw key.
|
||||||
|
describe("localised errors", () => {
|
||||||
|
test("MembershipManagerError describes the failure and keeps its cause", () => {
|
||||||
|
const cause = new Error("the underlying problem");
|
||||||
|
const error = new MembershipManagerError(cause);
|
||||||
|
|
||||||
|
expect(error.code).toBe(ErrorCode.INTERNAL_MEMBERSHIP_MANAGER);
|
||||||
|
expect(error.category).toBe(ErrorCategory.SYSTEM_FAILURE);
|
||||||
|
expect(error.localisedTitle).not.toContain("error.");
|
||||||
|
expect(error.localisedMessage).not.toContain("error.");
|
||||||
|
expect(error.cause).toBe(cause);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("NoMatrix2AuthorizationService is a configuration problem", () => {
|
||||||
|
const cause = new Error("404");
|
||||||
|
const error = new NoMatrix2AuthorizationService(cause);
|
||||||
|
|
||||||
|
expect(error.code).toBe(ErrorCode.NO_MATRIX_2_AUTHORIZATION_SERVICE);
|
||||||
|
expect(error.category).toBe(ErrorCategory.CONFIGURATION_ISSUE);
|
||||||
|
expect(error.localisedTitle).not.toContain("error.");
|
||||||
|
expect(error.localisedMessage).not.toContain("error.");
|
||||||
|
expect(error.cause).toBe(cause);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("FailToStartLivekitConnection passes its detail through", () => {
|
||||||
|
const error = new FailToStartLivekitConnection("could not publish");
|
||||||
|
|
||||||
|
expect(error.code).toBe(ErrorCode.FAILED_TO_START_LIVEKIT);
|
||||||
|
expect(error.category).toBe(ErrorCategory.NETWORK_CONNECTIVITY);
|
||||||
|
expect(error.localisedTitle).not.toContain("error.");
|
||||||
|
expect(error.localisedMessage).toBe("could not publish");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SFURoomCreationRestrictedError explains the restriction", () => {
|
||||||
|
const error = new SFURoomCreationRestrictedError();
|
||||||
|
|
||||||
|
expect(error.code).toBe(ErrorCode.SFU_ERROR);
|
||||||
|
expect(error.category).toBe(ErrorCategory.CONFIGURATION_ISSUE);
|
||||||
|
expect(error.localisedTitle).not.toContain("error.");
|
||||||
|
expect(error.localisedMessage).not.toContain("error.");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user