Files
element-call-Github/src/room/preJoinRoomInfo.test.ts
T
Quentin GliechandClaude Fable 5.1 3653a7fd05 Give the lobby a join state instead of an enter callback
`LobbyView` took `onEnter`, `enterLabel` and `waitingForInvite`, which cannot
express a declined, banned or invite-only room, so each of those ended at a
full-screen error and took the camera preview with it.

It now takes a `LobbyJoinState`, one of seven kinds, and renders both the
button and a message under the preview from it. `KnockLobbyView` passes
through what the loader offers, which is a single `lobby` state in place of
`canKnock` and `waitForInvite`, so a decline, a ban, or a room that takes
neither joins nor knocks keeps the user in the lobby. Waiting says what it is
waiting for and offers a way to withdraw the request.

The loader drives that state from the lobby's own callbacks rather than from
the load promise, so a refused request leaves the user where they were, and
every membership listener it adds is dropped when the effect is cleaned up.
What it knows of a room nobody has joined is a `PreJoinRoomInfo`, built from
a summary or from a `Room`, so a ban Element Call finds at load time is a
lobby state as well.

Denied and banned reuse the translated `group_call_loader.*` copy, so four
strings are new.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 22:44:59 +02:00

105 lines
2.9 KiB
TypeScript

/*
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, it } from "vitest";
import {
EventType,
MatrixEvent,
type Room,
type RoomState,
type RoomSummary,
} from "matrix-js-sdk";
import { E2eeType } from "../e2ee/e2eeType";
import { mockMatrixRoom } from "../utils/test";
import {
preJoinRoomInfoFromRoom,
preJoinRoomInfoFromSummary,
} from "./preJoinRoomInfo";
const roomId = "!call:example.org";
const summary = (extra: Partial<RoomSummary> = {}): RoomSummary =>
({
room_id: roomId,
name: "Weekly sync",
canonical_alias: "#sync:example.org",
avatar_url: "mxc://example.org/avatar",
world_readable: false,
guest_can_join: false,
num_joined_members: 3,
...extra,
}) as RoomSummary;
const room = (extra: { encryption?: string } = {}): Room =>
mockMatrixRoom({
roomId,
name: "Weekly sync",
getCanonicalAlias: () => "#sync:example.org",
getMxcAvatarUrl: () => "mxc://example.org/avatar",
currentState: {
getStateEvents: (type: string) =>
type === EventType.RoomEncryption && extra.encryption !== undefined
? new MatrixEvent({
type: EventType.RoomEncryption,
state_key: "",
content: { algorithm: extra.encryption },
})
: null,
} as unknown as RoomState,
});
describe("preJoinRoomInfoFromSummary", () => {
it("reads the stable encryption field", () => {
expect(
preJoinRoomInfoFromSummary(
summary({ encryption: "m.megolm.v1.aes-sha2" } as Partial<RoomSummary>),
),
).toEqual({
roomId,
roomName: "Weekly sync",
roomAlias: "#sync:example.org",
roomAvatar: "mxc://example.org/avatar",
e2eeSystem: { kind: E2eeType.PER_PARTICIPANT },
});
});
it("falls back to the unstable encryption field", () => {
expect(
preJoinRoomInfoFromSummary(
summary({ "im.nheko.summary.encryption": "m.megolm.v1.aes-sha2" }),
).e2eeSystem,
).toEqual({ kind: E2eeType.PER_PARTICIPANT });
});
it("is unencrypted when neither field is set", () => {
expect(preJoinRoomInfoFromSummary(summary()).e2eeSystem).toEqual({
kind: E2eeType.NONE,
});
});
});
describe("preJoinRoomInfoFromRoom", () => {
it("reads the encryption state event", () => {
expect(
preJoinRoomInfoFromRoom(room({ encryption: "m.megolm.v1.aes-sha2" })),
).toEqual({
roomId,
roomName: "Weekly sync",
roomAlias: "#sync:example.org",
roomAvatar: "mxc://example.org/avatar",
e2eeSystem: { kind: E2eeType.PER_PARTICIPANT },
});
});
it("is unencrypted with no encryption state event", () => {
expect(preJoinRoomInfoFromRoom(room()).e2eeSystem).toEqual({
kind: E2eeType.NONE,
});
});
});