Wire local room logic (#9078)

* Wire local room logic

* Migrate to testling-lib; update test descriptions
pull/28788/head^2
Michael Weimann 2022-07-20 09:26:25 +02:00 committed by GitHub
parent 66f7c9f564
commit fa1bff67cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 494 additions and 12 deletions

View File

@ -22,6 +22,7 @@ import { split } from "lodash";
import DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media";
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(
@ -142,7 +143,12 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
if (room.isSpaceRoom()) return null;
// If the room is not a DM don't fallback to a member avatar
if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) return null;
if (
!DMRoomMap.shared().getUserIdForRoomId(room.roomId)
&& !(isLocalRoom(room))
) {
return null;
}
// If there are only two members in the DM use the avatar of the other member
const otherMember = room.getAvatarFallbackMember();

View File

@ -132,6 +132,7 @@ import VideoChannelStore from "../../stores/VideoChannelStore";
import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
import { UseCaseSelection } from '../views/elements/UseCaseSelection';
import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig';
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
// legacy export
export { default as Views } from "../../Views";
@ -890,7 +891,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
// If we are redirecting to a Room Alias and it is for the room we already showing then replace history item
const replaceLast = presentedId[0] === "#" && roomInfo.room_id === this.state.currentRoomId;
let replaceLast = presentedId[0] === "#" && roomInfo.room_id === this.state.currentRoomId;
if (isLocalRoom(this.state.currentRoomId)) {
// Replace local room history items
replaceLast = true;
}
if (roomInfo.room_id === this.state.currentRoomId) {
// if we are re-viewing the same room then copy any state we already know

View File

@ -91,6 +91,7 @@ import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
import { RoomResultContextMenus } from "./RoomResultContextMenus";
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
import { TooltipOption } from "./TooltipOption";
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@ -243,6 +244,9 @@ export const useWebSearchMetrics = (numResults: number, queryLength: number, via
const findVisibleRooms = (cli: MatrixClient) => {
return cli.getVisibleRooms().filter(room => {
// Do not show local rooms
if (isLocalRoom(room)) return false;
// TODO we may want to put invites in their own list
return room.getMyMembership() === "join" || room.getMyMembership() == "invite";
});
@ -395,7 +399,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
possibleResults.forEach(entry => {
if (isRoomResult(entry)) {
if (!entry.room.normalizedName.includes(normalizedQuery) &&
if (!entry.room.normalizedName?.includes(normalizedQuery) &&
!entry.room.getCanonicalAlias()?.toLowerCase().includes(lcQuery) &&
!entry.query?.some(q => q.includes(lcQuery))
) return; // bail, does not match query

View File

@ -24,6 +24,7 @@ import EventTileBubble from "./EventTileBubble";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap";
import { objectHasDiff } from "../../../utils/objects";
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
interface IProps {
mxEvent: MatrixEvent;
@ -46,12 +47,15 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
if (content.algorithm === ALGORITHM && isRoomEncrypted) {
let subtitle: string;
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
const room = cli?.getRoom(roomId);
if (prevContent.algorithm === ALGORITHM) {
subtitle = _t("Some encryption parameters have been changed.");
} else if (dmPartner) {
const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
const displayName = room.getMember(dmPartner)?.rawDisplayName || dmPartner;
subtitle = _t("Messages here are end-to-end encrypted. " +
"Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
} else if (isLocalRoom(room)) {
subtitle = _t("Messages in this chat will be end-to-end encrypted.");
} else {
subtitle = _t("Messages in this room are end-to-end encrypted. " +
"When people join, you can verify them in their profile, just tap on their avatar.");

View File

@ -2101,6 +2101,7 @@
"View Source": "View Source",
"Some encryption parameters have been changed.": "Some encryption parameters have been changed.",
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
"Messages in this chat will be end-to-end encrypted.": "Messages in this chat will be end-to-end encrypted.",
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
"Encryption enabled": "Encryption enabled",
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",

View File

@ -16,6 +16,7 @@ limitations under the License.
import { MatrixClientPeg } from "../MatrixClientPeg";
import SettingsStore from "../settings/SettingsStore";
import { isLocalRoom } from "../utils/localRoom/isLocalRoom";
import Timer from "../utils/Timer";
const TYPING_USER_TIMEOUT = 10000;
@ -64,6 +65,9 @@ export default class TypingStore {
* @param {boolean} isTyping Whether the user is typing or not.
*/
public setSelfTyping(roomId: string, threadId: string | null, isTyping: boolean): void {
// No typing notifications for local rooms
if (isLocalRoom(roomId)) return;
if (!SettingsStore.getValue('sendTypingNotifications')) return;
if (SettingsStore.getValue('lowBandwidth')) return;
// Disable typing notification for threads for the initial launch

View File

@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import CallHandler from "../../../CallHandler";
import { RoomListCustomisations } from "../../../customisations/RoomList";
import { LocalRoom } from "../../../models/LocalRoom";
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
import VoipUserMapper from "../../../VoipUserMapper";
export class VisibilityProvider {
@ -55,7 +55,7 @@ export class VisibilityProvider {
return false;
}
if (room instanceof LocalRoom) {
if (isLocalRoom(room)) {
// local rooms shouldn't show up anywhere
return false;
}

View File

@ -0,0 +1,26 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/matrix";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
export function isLocalRoom(roomOrID: Room|string): boolean {
if (typeof roomOrID === "string") {
return roomOrID.startsWith(LOCAL_ROOM_ID_PREFIX);
}
return roomOrID instanceof LocalRoom;
}

102
test/Avatar-test.ts Normal file
View File

@ -0,0 +1,102 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from "jest-mock";
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { avatarUrlForRoom } from "../src/Avatar";
import { Media, mediaFromMxc } from "../src/customisations/Media";
import DMRoomMap from "../src/utils/DMRoomMap";
jest.mock("../src/customisations/Media", () => ({
mediaFromMxc: jest.fn(),
}));
const roomId = "!room:example.com";
const avatarUrl1 = "https://example.com/avatar1";
const avatarUrl2 = "https://example.com/avatar2";
describe("avatarUrlForRoom", () => {
let getThumbnailOfSourceHttp: jest.Mock;
let room: Room;
let roomMember: RoomMember;
let dmRoomMap: DMRoomMap;
beforeEach(() => {
getThumbnailOfSourceHttp = jest.fn();
mocked(mediaFromMxc).mockImplementation((): Media => {
return {
getThumbnailOfSourceHttp,
} as unknown as Media;
});
room = {
roomId,
getMxcAvatarUrl: jest.fn(),
isSpaceRoom: jest.fn(),
getAvatarFallbackMember: jest.fn(),
} as unknown as Room;
dmRoomMap = {
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap);
roomMember = {
getMxcAvatarUrl: jest.fn(),
} as unknown as RoomMember;
});
it("should return null for a null room", () => {
expect(avatarUrlForRoom(null, 128, 128)).toBeNull();
});
it("should return the HTTP source if the room provides a MXC url", () => {
mocked(room.getMxcAvatarUrl).mockReturnValue(avatarUrl1);
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
});
it("should return null for a space room", () => {
mocked(room.isSpaceRoom).mockReturnValue(true);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
});
it("should return null if the room is not a DM", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue(null);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
expect(dmRoomMap.getUserIdForRoomId).toHaveBeenCalledWith(roomId);
});
it("should return null if there is no other member in the room", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(null);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
});
it("should return null if the other member has no avatar URL", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
});
it("should return the other member's avatar URL", () => {
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
mocked(roomMember.getMxcAvatarUrl).mockReturnValue(avatarUrl2);
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
});
});

View File

@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mount } from "enzyme";
import { IProtocol, IPublicRoomsChunkRoom, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
import { mount, ReactWrapper } from "enzyme";
import { mocked } from "jest-mock";
import { IProtocol, IPublicRoomsChunkRoom, MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import React from "react";
import { act } from "react-dom/test-utils";
@ -23,7 +24,15 @@ import sanitizeHtml from "sanitize-html";
import SpotlightDialog, { Filter } from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { stubClient } from "../../../test-utils";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { mkRoom, stubClient } from "../../../test-utils";
jest.mock("../../../../src/utils/direct-messages", () => ({
// @ts-ignore
...jest.requireActual("../../../../src/utils/direct-messages"),
startDmOnFirstMessage: jest.fn(),
}));
interface IUserChunkMember {
user_id: string;
@ -110,10 +119,23 @@ describe("Spotlight Dialog", () => {
guest_can_join: false,
};
beforeEach(() => {
mockClient({ rooms: [testPublicRoom], users: [testPerson] });
});
let testRoom: Room;
let testLocalRoom: LocalRoom;
let mockedClient: MatrixClient;
beforeEach(() => {
mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] });
testRoom = mkRoom(mockedClient, "!test23:example.com");
mocked(testRoom.getMyMembership).mockReturnValue("join");
testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId());
testLocalRoom.updateMyMembership("join");
mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]);
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap);
});
describe("should apply filters supplied via props", () => {
it("without filter", async () => {
const wrapper = mount(
@ -289,4 +311,38 @@ describe("Spotlight Dialog", () => {
wrapper.unmount();
});
});
describe("searching for rooms", () => {
let wrapper: ReactWrapper;
let options: ReactWrapper;
beforeAll(async () => {
wrapper = mount(
<SpotlightDialog
initialText="test23"
onFinished={() => null} />,
);
await act(async () => {
await sleep(200);
});
wrapper.update();
const content = wrapper.find("#mx_SpotlightDialog_content");
options = content.find("div.mx_SpotlightDialog_option");
});
afterAll(() => {
wrapper.unmount();
});
it("should find Rooms", () => {
expect(options.length).toBe(3);
expect(options.first().text()).toContain(testRoom.name);
});
it("should not find LocalRooms", () => {
expect(options.length).toBe(3);
expect(options.first().text()).not.toContain(testLocalRoom.name);
});
});
});

View File

@ -0,0 +1,131 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { mocked } from "jest-mock";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { render, screen } from '@testing-library/react';
import EncryptionEvent from "../../../../src/components/views/messages/EncryptionEvent";
import { createTestClient, mkMessage } from "../../../test-utils";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { LocalRoom } from '../../../../src/models/LocalRoom';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
const renderEncryptionEvent = (client: MatrixClient, event: MatrixEvent) => {
render(<MatrixClientContext.Provider value={client}>
<EncryptionEvent mxEvent={event} />
</MatrixClientContext.Provider>);
};
const checkTexts = (title: string, subTitle: string) => {
screen.getByText(title);
screen.getByText(subTitle);
};
describe("EncryptionEvent", () => {
const roomId = "!room:example.com";
const algorithm = "m.megolm.v1.aes-sha2";
let client: MatrixClient;
let event: MatrixEvent;
beforeEach(() => {
jest.clearAllMocks();
client = createTestClient();
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client);
event = mkMessage({
event: true,
room: roomId,
user: client.getUserId(),
});
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap);
});
describe("for an encrypted room", () => {
beforeEach(() => {
event.event.content.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true);
const room = new Room(roomId, client, client.getUserId());
mocked(client.getRoom).mockReturnValue(room);
});
it("should show the expected texts", () => {
renderEncryptionEvent(client, event);
checkTexts(
"Encryption enabled",
"Messages in this room are end-to-end encrypted. "
+ "When people join, you can verify them in their profile, just tap on their avatar.",
);
});
describe("with same previous algorithm", () => {
beforeEach(() => {
jest.spyOn(event, "getPrevContent").mockReturnValue({
algorithm: algorithm,
});
});
it("should show the expected texts", () => {
renderEncryptionEvent(client, event);
checkTexts(
"Encryption enabled",
"Some encryption parameters have been changed.",
);
});
});
describe("with unknown algorithm", () => {
beforeEach(() => {
event.event.content.algorithm = "unknown";
});
it("should show the expected texts", () => {
renderEncryptionEvent(client, event);
checkTexts("Encryption enabled", "Ignored attempt to disable encryption");
});
});
});
describe("for an unencrypted room", () => {
beforeEach(() => {
mocked(client.isRoomEncrypted).mockReturnValue(false);
renderEncryptionEvent(client, event);
});
it("should show the expected texts", () => {
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported.");
});
});
describe("for an encrypted local room", () => {
beforeEach(() => {
event.event.content.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true);
const localRoom = new LocalRoom(roomId, client, client.getUserId());
mocked(client.getRoom).mockReturnValue(localRoom);
renderEncryptionEvent(client, event);
});
it("should show the expected texts", () => {
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
});
});
});

View File

@ -0,0 +1,88 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from "jest-mock";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import TypingStore from "../../src/stores/TypingStore";
import { LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom";
import SettingsStore from "../../src/settings/SettingsStore";
jest.mock("../../src/settings/SettingsStore", () => ({
getValue: jest.fn(),
}));
describe("TypingStore", () => {
let typingStore: TypingStore;
let mockClient: MatrixClient;
const settings = {
"sendTypingNotifications": true,
"feature_thread": false,
};
const roomId = "!test:example.com";
const localRoomId = LOCAL_ROOM_ID_PREFIX + "test";
beforeEach(() => {
typingStore = new TypingStore();
mockClient = {
sendTyping: jest.fn(),
} as unknown as MatrixClient;
MatrixClientPeg.get = () => mockClient;
mocked(SettingsStore.getValue).mockImplementation((setting: string) => {
return settings[setting];
});
});
describe("setSelfTyping", () => {
it("shouldn't do anything for a local room", () => {
typingStore.setSelfTyping(localRoomId, null, true);
expect(mockClient.sendTyping).not.toHaveBeenCalled();
});
describe("in typing state true", () => {
beforeEach(() => {
typingStore.setSelfTyping(roomId, null, true);
});
it("should change to false when setting false", () => {
typingStore.setSelfTyping(roomId, null, false);
expect(mockClient.sendTyping).toHaveBeenCalledWith(roomId, false, 30000);
});
it("should change to true when setting true", () => {
typingStore.setSelfTyping(roomId, null, true);
expect(mockClient.sendTyping).toHaveBeenCalledWith(roomId, true, 30000);
});
});
describe("in typing state false", () => {
beforeEach(() => {
typingStore.setSelfTyping(roomId, null, false);
});
it("shouldn't change when setting false", () => {
typingStore.setSelfTyping(roomId, null, false);
expect(mockClient.sendTyping).not.toHaveBeenCalled();
});
it("should change to true when setting true", () => {
typingStore.setSelfTyping(roomId, null, true);
expect(mockClient.sendTyping).toHaveBeenCalledWith(roomId, true, 30000);
});
});
});
});

View File

@ -31,6 +31,7 @@ import {
IEventRelation,
IUnsigned,
} from 'matrix-js-sdk/src/matrix';
import { normalize } from "matrix-js-sdk/src/utils";
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
import dis from '../../src/dispatcher/dispatcher';
@ -389,6 +390,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
removeListener: jest.fn(),
getDMInviter: jest.fn(),
name,
normalizedName: normalize(name || ""),
getAvatarUrl: () => 'mxc://avatar.url/room.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
isSpaceRoom: jest.fn().mockReturnValue(false),

View File

@ -0,0 +1,52 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/matrix";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../src/models/LocalRoom";
import { isLocalRoom } from "../../../src/utils/localRoom/isLocalRoom";
import { createTestClient } from "../../test-utils";
describe("isLocalRoom", () => {
let room: Room;
let localRoom: LocalRoom;
beforeEach(() => {
const client = createTestClient();
room = new Room("!room:example.com", client, client.getUserId());
localRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test", client, client.getUserId());
});
it("should return false for null", () => {
expect(isLocalRoom(null)).toBe(false);
});
it("should return false for a Room", () => {
expect(isLocalRoom(room)).toBe(false);
});
it("should return false for a non-local room ID", () => {
expect(isLocalRoom(room.roomId)).toBe(false);
});
it("should return true for LocalRoom", () => {
expect(isLocalRoom(localRoom)).toBe(true);
});
it("should return true for local room ID", () => {
expect(isLocalRoom(LOCAL_ROOM_ID_PREFIX + "test")).toBe(true);
});
});