Migrate batch of tests to vitest (#34108)

* Migrate utils/dm tests to vitest

* Migrate utils/room tests to vitest

* Migrate utils/location tests to vitest

* Fix types

* Satisfy tsc & knip
This commit is contained in:
Michael Telatynski
2026-07-03 08:50:11 +00:00
committed by GitHub
parent 8aba166f1e
commit df4a1f0c85
21 changed files with 209 additions and 166 deletions
@@ -0,0 +1,109 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { EventType, KNOWN_SAFE_ROOM_VERSION, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { createTestClient } from "test-utils";
import { canEncryptToAllUsers } from "../../createRoom";
import { type LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
import { DirectoryMember, type Member, ThreepidMember } from "../direct-messages";
import { createDmLocalRoom } from "./createDmLocalRoom";
import { privateShouldBeEncrypted } from "../rooms";
vi.mock("../rooms", () => ({
privateShouldBeEncrypted: vi.fn(),
}));
vi.mock("../../createRoom", () => ({
canEncryptToAllUsers: vi.fn(),
}));
function assertLocalRoom(room: LocalRoom, targets: Member[], encrypted: boolean) {
expect(room.roomId).toBe(LOCAL_ROOM_ID_PREFIX + "t1");
expect(room.name).toBe(targets.length ? targets[0].name : "Empty Room");
expect(room.encrypted).toBe(encrypted);
expect(room.targets).toEqual(targets);
expect(room.getMyMembership()).toBe(KnownMembership.Join);
const roomCreateEvent = room.currentState.getStateEvents(EventType.RoomCreate)[0];
expect(roomCreateEvent).toBeDefined();
expect(roomCreateEvent.getContent()["room_version"]).toBe(KNOWN_SAFE_ROOM_VERSION);
// check that the user and all targets are joined
expect(room.getMember("@userId:matrix.org")?.membership).toBe(KnownMembership.Join);
targets.forEach((target: Member) => {
expect(room.getMember(target.userId)?.membership).toBe(KnownMembership.Join);
});
if (encrypted) {
const encryptionEvent = room.currentState.getStateEvents(EventType.RoomEncryption)[0];
expect(encryptionEvent).toBeDefined();
}
}
describe("createDmLocalRoom", () => {
let mockClient: MatrixClient;
const userId1 = "@user1:example.com";
const member1 = new DirectoryMember({ user_id: userId1 });
const member2 = new ThreepidMember("user2");
beforeEach(() => {
mockClient = createTestClient();
});
describe("when rooms should be encrypted", () => {
beforeEach(() => {
vi.mocked(privateShouldBeEncrypted).mockReturnValue(true);
});
it("should create an encrytped room for 3PID targets", async () => {
const room = await createDmLocalRoom(mockClient, [member2]);
expect(mockClient.store.storeRoom).toHaveBeenCalledWith(room);
assertLocalRoom(room, [member2], true);
});
describe("for MXID targets with encryption available", () => {
beforeEach(() => {
vi.mocked(canEncryptToAllUsers).mockResolvedValue(true);
});
it("should create an encrypted room", async () => {
const room = await createDmLocalRoom(mockClient, [member1]);
expect(mockClient.store.storeRoom).toHaveBeenCalledWith(room);
assertLocalRoom(room, [member1], true);
});
});
describe("for MXID targets with encryption unavailable", () => {
beforeEach(() => {
vi.mocked(canEncryptToAllUsers).mockResolvedValue(false);
});
it("should create an unencrypted room", async () => {
const room = await createDmLocalRoom(mockClient, [member1]);
expect(mockClient.store.storeRoom).toHaveBeenCalledWith(room);
assertLocalRoom(room, [member1], false);
});
});
});
describe("if rooms should not be encrypted", () => {
beforeEach(() => {
vi.mocked(privateShouldBeEncrypted).mockReturnValue(false);
});
it("should create an unencrypted room", async () => {
const room = await createDmLocalRoom(mockClient, [member1]);
assertLocalRoom(room, [member1], false);
});
});
});
@@ -0,0 +1,71 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import { filterValidMDirect } from "./filterValidMDirect";
const roomId1 = "!room1:example.com";
const roomId2 = "!room2:example.com";
const userId1 = "@user1:example.com";
const userId2 = "@user2:example.com";
const userId3 = "@user3:example.com";
describe("filterValidMDirect", () => {
it("should return an empty object as valid content", () => {
expect(filterValidMDirect({})).toEqual({
valid: true,
filteredContent: {},
});
});
it("should return valid content", () => {
expect(
filterValidMDirect({
[userId1]: [roomId1, roomId2],
[userId2]: [roomId1],
}),
).toEqual({
valid: true,
filteredContent: {
[userId1]: [roomId1, roomId2],
[userId2]: [roomId1],
},
});
});
it("should return an empy object for null", () => {
expect(filterValidMDirect(null)).toEqual({
valid: false,
filteredContent: {},
});
});
it("should return an empy object for a non-object", () => {
expect(filterValidMDirect(23)).toEqual({
valid: false,
filteredContent: {},
});
});
it("should only return valid content", () => {
const invalidContent = {
[userId1]: [23],
[userId2]: [roomId2],
[userId3]: "room1",
};
expect(filterValidMDirect(invalidContent)).toEqual({
valid: false,
filteredContent: {
[userId1]: [],
[userId2]: [roomId2],
},
});
});
});
+180
View File
@@ -0,0 +1,180 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { createTestClient, makeMembershipEvent, mkThirdPartyInviteEvent } from "test-utils";
import DMRoomMap from "../DMRoomMap";
import { LocalRoom } from "../../models/LocalRoom";
import { findDMForUser } from "./findDMForUser";
import { getFunctionalMembers } from "../room/getFunctionalMembers";
vi.mock("../room/getFunctionalMembers", () => ({
getFunctionalMembers: vi.fn(),
}));
describe("findDMForUser", () => {
const userId1 = "@user1:example.com";
const userId2 = "@user2:example.com";
const userId3 = "@user3:example.com";
const botId = "@bot:example.com";
const thirdPartyId = "party@example.com";
let room1: Room;
let room2: LocalRoom;
let room3: Room;
let room4: Room;
let room5: Room;
let room6: Room;
let room7: Room;
const unknownRoomId = "!unknown:example.com";
let dmRoomMap: DMRoomMap;
let mockClient: MatrixClient;
beforeEach(() => {
mockClient = createTestClient();
// always return the bot user as functional member
vi.mocked(getFunctionalMembers).mockReturnValue([botId]);
room1 = new Room("!room1:example.com", mockClient, userId1);
room1.getMyMembership = () => KnownMembership.Join;
room1.currentState.setStateEvents([
makeMembershipEvent(room1.roomId, userId1, KnownMembership.Join),
makeMembershipEvent(room1.roomId, userId2, KnownMembership.Join),
]);
// this should not be a DM room because it is a local room
room2 = new LocalRoom("!room2:example.com", mockClient, userId1);
room2.getMyMembership = () => KnownMembership.Join;
room2.getLastActiveTimestamp = () => 100;
room3 = new Room("!room3:example.com", mockClient, userId1);
room3.getMyMembership = () => KnownMembership.Join;
room3.currentState.setStateEvents([
makeMembershipEvent(room3.roomId, userId1, KnownMembership.Join),
makeMembershipEvent(room3.roomId, userId2, KnownMembership.Join),
// Adding the bot user here. Should be excluded when determining if the room is a DM.
makeMembershipEvent(room3.roomId, botId, KnownMembership.Join),
]);
// this should not be a DM room because it has only one joined user
room4 = new Room("!room4:example.com", mockClient, userId1);
room4.getMyMembership = () => KnownMembership.Join;
room4.currentState.setStateEvents([
makeMembershipEvent(room4.roomId, userId1, KnownMembership.Invite),
makeMembershipEvent(room4.roomId, userId2, KnownMembership.Join),
]);
// this should not be a DM room because it has no users
room5 = new Room("!room5:example.com", mockClient, userId1);
room5.getLastActiveTimestamp = () => 100;
// room not correctly stored in userId → room map; should be found by the "all rooms" fallback
room6 = new Room("!room6:example.com", mockClient, userId1);
room6.getMyMembership = () => KnownMembership.Join;
room6.currentState.setStateEvents([
makeMembershipEvent(room6.roomId, userId1, KnownMembership.Join),
makeMembershipEvent(room6.roomId, userId3, KnownMembership.Join),
]);
// room with pending third-party invite
room7 = new Room("!room7:example.com", mockClient, userId1);
room7.getMyMembership = () => KnownMembership.Join;
room7.currentState.setStateEvents([
makeMembershipEvent(room7.roomId, userId1, KnownMembership.Join),
mkThirdPartyInviteEvent(thirdPartyId, "third-party", room7.roomId),
]);
vi.mocked(mockClient.getRoom).mockImplementation((roomId?: string) => {
return (
{
[room1.roomId]: room1,
[room2.roomId]: room2,
[room3.roomId]: room3,
[room4.roomId]: room4,
[room5.roomId]: room5,
[room6.roomId]: room6,
[room7.roomId]: room7,
}[roomId!] || null
);
});
dmRoomMap = {
getDMRoomForIdentifiers: vi.fn(),
getDMRoomsForUserId: vi.fn(),
getRoomIds: vi.fn().mockReturnValue(
new Set([
room1.roomId,
room2.roomId,
room3.roomId,
room4.roomId,
room5.roomId,
room6.roomId,
room7.roomId,
unknownRoomId, // this room does not exist in client
]),
),
} as unknown as DMRoomMap;
vi.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
vi.mocked(dmRoomMap.getDMRoomsForUserId).mockImplementation((userId: string) => {
if (userId === userId1) {
return [room1.roomId, room2.roomId, room3.roomId, room4.roomId, room5.roomId, unknownRoomId];
}
if (userId === thirdPartyId) {
return [room7.roomId];
}
return [];
});
});
describe("for an empty DM room list", () => {
beforeEach(() => {
vi.mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([]);
vi.mocked(dmRoomMap.getRoomIds).mockReturnValue(new Set());
});
it("should return undefined", () => {
expect(findDMForUser(mockClient, userId1)).toBeUndefined();
});
});
it("should find a room ordered by last activity 1", () => {
room1.getLastActiveTimestamp = () => 2;
room3.getLastActiveTimestamp = () => 1;
expect(findDMForUser(mockClient, userId1)).toBe(room1);
});
it("should find a room ordered by last activity 2", () => {
room1.getLastActiveTimestamp = () => 1;
room3.getLastActiveTimestamp = () => 2;
expect(findDMForUser(mockClient, userId1)).toBe(room3);
});
it("should find a room by the 'all rooms' fallback", () => {
room1.getLastActiveTimestamp = () => 1;
room6.getLastActiveTimestamp = () => 2;
expect(findDMForUser(mockClient, userId3)).toBe(room6);
});
it("should find a room with a pending third-party invite", () => {
expect(findDMForUser(mockClient, thirdPartyId)).toBe(room7);
});
it("should not find a room for an unknown Id", () => {
expect(findDMForUser(mockClient, "@unknown:example.com")).toBe(undefined);
});
});
+64
View File
@@ -0,0 +1,64 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { createTestClient } from "test-utils";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import { DirectoryMember, ThreepidMember } from "../direct-messages";
import { findDMForUser } from "./findDMForUser";
import { findDMRoom } from "./findDMRoom";
import DMRoomMap from "../DMRoomMap";
vi.mock("../dm/findDMForUser", () => ({
findDMForUser: vi.fn(),
}));
describe("findDMRoom", () => {
const userId1 = "@user1:example.com";
const member1 = new DirectoryMember({ user_id: userId1 });
const member2 = new ThreepidMember("user2");
let mockClient: MatrixClient;
let room1: Room;
let dmRoomMap: DMRoomMap;
beforeEach(() => {
mockClient = createTestClient();
vi.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
room1 = new Room("!room1:example.com", mockClient, userId1);
dmRoomMap = {
getDMRoomForIdentifiers: vi.fn(),
getDMRoomsForUserId: vi.fn(),
} as unknown as DMRoomMap;
vi.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
});
it("should return the room for a single target with a room", () => {
vi.mocked(findDMForUser).mockReturnValue(room1);
expect(findDMRoom(mockClient, [member1])).toBe(room1);
});
it("should return undefined for a single target without a room", () => {
vi.mocked(findDMForUser).mockReturnValue(undefined);
expect(findDMRoom(mockClient, [member1])).toBeNull();
});
it("should return the room for 2 targets with a room", () => {
vi.mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(room1);
expect(findDMRoom(mockClient, [member1, member2])).toBe(room1);
});
it("should return null for 2 targets without a room", () => {
vi.mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(null);
expect(findDMRoom(mockClient, [member1, member2])).toBeNull();
});
});
@@ -0,0 +1,66 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import {
M_TEXT,
type ILocationContent,
type LocationAssetType,
M_ASSET,
M_LOCATION,
M_TIMESTAMP,
ContentHelpers,
} from "matrix-js-sdk/src/matrix";
import { isSelfLocation } from "./isSelfLocation";
describe("isSelfLocation", () => {
it("Returns true for a full m.asset event", () => {
const content = ContentHelpers.makeLocationContent("", "0", Date.now());
expect(isSelfLocation(content)).toBe(true);
});
it("Returns true for a missing m.asset", () => {
const content = {
body: "",
msgtype: "m.location",
geo_uri: "",
[M_LOCATION.name]: { uri: "" },
[M_TEXT.name]: "",
[M_TIMESTAMP.name]: 0,
// Note: no m.asset!
} as unknown as ILocationContent;
expect(isSelfLocation(content)).toBe(true);
});
it("Returns true for a missing m.asset type", () => {
const content = {
body: "",
msgtype: "m.location",
geo_uri: "",
[M_LOCATION.name]: { uri: "" },
[M_TEXT.name]: "",
[M_TIMESTAMP.name]: 0,
[M_ASSET.name]: {
// Note: no type!
},
} as unknown as ILocationContent;
expect(isSelfLocation(content)).toBe(true);
});
it("Returns false for an unknown asset type", () => {
const content = ContentHelpers.makeLocationContent(
undefined /* text */,
"geo:foo",
0,
undefined /* description */,
"org.example.unknown" as unknown as LocationAssetType,
);
expect(isSelfLocation(content)).toBe(false);
});
});
+48
View File
@@ -0,0 +1,48 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { describe, it, expect } from "vitest";
import { mkMessage, makeLegacyLocationEvent, makeLocationEvent } from "test-utils";
import { createMapSiteLinkFromEvent } from "./links";
describe("createMapSiteLinkFromEvent", () => {
it("returns null if event does not contain geouri", () => {
expect(
createMapSiteLinkFromEvent(
mkMessage({
room: "1",
user: "@sender:server",
event: true,
}),
),
).toBeNull();
});
it("returns OpenStreetMap link if event contains m.location with valid uri", () => {
expect(createMapSiteLinkFromEvent(makeLocationEvent("geo:51.5076,-0.1276"))).toEqual(
"https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276",
);
});
it("returns null if event contains m.location with invalid uri", () => {
expect(createMapSiteLinkFromEvent(makeLocationEvent("123 Sesame St"))).toBeNull();
});
it("returns OpenStreetMap link if event contains geo_uri", () => {
expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("geo:51.5076,-0.1276"))).toEqual(
"https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276",
);
});
it("returns null if event contains an invalid geo_uri", () => {
expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("123 Sesame St"))).toBeNull();
});
});
@@ -0,0 +1,22 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import { makeLegacyLocationEvent, makeLocationEvent } from "test-utils/location";
import { locationEventGeoUri } from "./locationEventGeoUri";
describe("locationEventGeoUri()", () => {
it("returns m.location uri when available", () => {
expect(locationEventGeoUri(makeLocationEvent("geo:51.5076,-0.1276"))).toEqual("geo:51.5076,-0.1276");
});
it("returns legacy uri when m.location content not found", () => {
expect(locationEventGeoUri(makeLegacyLocationEvent("geo:51.5076,-0.1276"))).toEqual("geo:51.5076,-0.1276");
});
});
@@ -0,0 +1,150 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import { parseGeoUri } from "./parseGeoUri";
describe("parseGeoUri", () => {
it("fails if the supplied URI is empty", () => {
expect(parseGeoUri("")).toBeFalsy();
});
it("returns undefined if latitude is not a number", () => {
expect(parseGeoUri("geo:ABCD,16.3695,183")).toBeUndefined();
});
it("returns undefined if longitude is not a number", () => {
expect(parseGeoUri("geo:48.2010,EFGH,183")).toBeUndefined();
});
// We use some examples from the spec, but don't check semantics
// like two textually-different URIs being equal, since we are
// just a humble parser.
// Note: we do not understand geo URIs with percent-encoded coords
// or accuracy. It is RECOMMENDED in the spec never to percent-encode
// these, but it is permitted, and we will fail to parse in that case.
it("rfc5870 6.1 Simple 3-dimensional", () => {
expect(parseGeoUri("geo:48.2010,16.3695,183")).toEqual({
latitude: 48.201,
longitude: 16.3695,
altitude: 183,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.2 Explicit CRS and accuracy", () => {
expect(parseGeoUri("geo:48.198634,16.371648;crs=wgs84;u=40")).toEqual({
latitude: 48.198634,
longitude: 16.371648,
altitude: null,
accuracy: 40,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.4 Negative longitude and explicit CRS", () => {
expect(parseGeoUri("geo:90,-22.43;crs=WGS84")).toEqual({
latitude: 90,
longitude: -22.43,
altitude: null,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.4 Integer lat and lon", () => {
expect(parseGeoUri("geo:90,46")).toEqual({
latitude: 90,
longitude: 46,
altitude: null,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.4 Percent-encoded param value", () => {
expect(parseGeoUri("geo:66,30;u=6.500;FOo=this%2dthat")).toEqual({
latitude: 66,
longitude: 30,
altitude: null,
accuracy: 6.5,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.4 Unknown param", () => {
expect(parseGeoUri("geo:66.0,30;u=6.5;foo=this-that>")).toEqual({
latitude: 66.0,
longitude: 30,
altitude: null,
accuracy: 6.5,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("rfc5870 6.4 Multiple unknown params", () => {
expect(parseGeoUri("geo:70,20;foo=1.00;bar=white")).toEqual({
latitude: 70,
longitude: 20,
altitude: null,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("Negative latitude", () => {
expect(parseGeoUri("geo:-7.5,20")).toEqual({
latitude: -7.5,
longitude: 20,
altitude: null,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
it("Zero altitude is not unknown", () => {
expect(parseGeoUri("geo:-7.5,-20,0")).toEqual({
latitude: -7.5,
longitude: -20,
altitude: 0,
accuracy: undefined,
altitudeAccuracy: null,
heading: null,
speed: null,
toJSON: expect.any(Function),
});
});
});
@@ -0,0 +1,32 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { describe, it, expect } from "vitest";
import { positionFailureMessage } from "./positionFailureMessage";
describe("positionFailureMessage()", () => {
// error codes from GeolocationPositionError
// see: https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError
// 1: PERMISSION_DENIED
// 2: POSITION_UNAVAILABLE
// 3: TIMEOUT
type TestCase = [number, string | undefined];
it.each<TestCase>([
[
1,
"Element was denied permission to fetch your location. Please allow location access in your browser settings.",
],
[2, "Failed to fetch your location. Please try again later."],
[3, "Timed out trying to fetch your location. Please try again later."],
[4, "Unknown error fetching location. Please try again later."],
[5, undefined],
])("returns correct message for error code %s", (code, expectedMessage) => {
expect(positionFailureMessage(code)).toEqual(expectedMessage);
});
});
+100
View File
@@ -0,0 +1,100 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { JoinRule, Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "test-utils";
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
import { UIComponent } from "../../settings/UIFeature";
import { canInviteTo } from "./canInviteTo";
vi.mock("../../customisations/helpers/UIComponents", () => ({
shouldShowComponent: vi.fn(),
}));
describe("canInviteTo()", () => {
afterEach(() => {
vi.restoreAllMocks();
});
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const makeRoom = (): Room => {
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
});
const room = new Room(roomId, client, userId);
vi.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
vi.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
vi.spyOn(room, "canInvite").mockReturnValue(true);
return room;
};
beforeEach(() => {
vi.mocked(shouldShowComponent).mockReturnValue(true);
});
describe("when user has permissions to issue an invite for this room", () => {
// aka when Room.canInvite is true
it("should return false when current user membership is not joined", () => {
const room = makeRoom();
vi.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Invite);
expect(canInviteTo(room)).toEqual(false);
});
it("should return false when UIComponent.InviteUsers customisation hides invite", () => {
const room = makeRoom();
vi.mocked(shouldShowComponent).mockReturnValue(false);
expect(canInviteTo(room)).toEqual(false);
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.InviteUsers);
});
it("should return true when user can invite and is a room member", () => {
const room = makeRoom();
expect(canInviteTo(room)).toEqual(true);
});
});
describe("when user does not have permissions to issue an invite for this room", () => {
// aka when Room.canInvite is false
it("should return false when room is a private space", () => {
const room = makeRoom();
vi.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
vi.spyOn(room, "isSpaceRoom").mockReturnValue(true);
vi.spyOn(room, "canInvite").mockReturnValue(false);
expect(canInviteTo(room)).toEqual(false);
});
it("should return false when room is just a room", () => {
const room = makeRoom();
vi.spyOn(room, "canInvite").mockReturnValue(false);
expect(canInviteTo(room)).toEqual(false);
});
it("should return true when room is a public space", () => {
const room = makeRoom();
// default join rule is public
vi.spyOn(room, "isSpaceRoom").mockReturnValue(true);
vi.spyOn(room, "canInvite").mockReturnValue(false);
expect(canInviteTo(room)).toEqual(true);
});
});
});
@@ -0,0 +1,79 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { vi, describe, it, expect, beforeEach } from "vitest";
import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { getFunctionalMembers } from "./getFunctionalMembers";
import { getJoinedNonFunctionalMembers } from "./getJoinedNonFunctionalMembers";
vi.mock("./getFunctionalMembers", () => ({
getFunctionalMembers: vi.fn(),
}));
describe("getJoinedNonFunctionalMembers", () => {
let room: Room;
let roomMember1: RoomMember;
let roomMember2: RoomMember;
beforeEach(() => {
room = new Room("!room:example.com", {} as unknown as MatrixClient, "@user:example.com");
room.getJoinedMembers = vi.fn();
roomMember1 = new RoomMember(room.roomId, "@user1:example.com");
roomMember2 = new RoomMember(room.roomId, "@user2:example.com");
});
describe("if there are no members", () => {
beforeEach(() => {
vi.mocked(room.getJoinedMembers).mockReturnValue([]);
vi.mocked(getFunctionalMembers).mockReturnValue([]);
});
it("should return an empty list", () => {
expect(getJoinedNonFunctionalMembers(room)).toHaveLength(0);
});
});
describe("if there are only regular room members", () => {
beforeEach(() => {
vi.mocked(room.getJoinedMembers).mockReturnValue([roomMember1, roomMember2]);
vi.mocked(getFunctionalMembers).mockReturnValue([]);
});
it("should return the room members", () => {
const members = getJoinedNonFunctionalMembers(room);
expect(members).toContain(roomMember1);
expect(members).toContain(roomMember2);
});
});
describe("if there are only functional room members", () => {
beforeEach(() => {
vi.mocked(room.getJoinedMembers).mockReturnValue([]);
vi.mocked(getFunctionalMembers).mockReturnValue(["@functional:example.com"]);
});
it("should return an empty list", () => {
expect(getJoinedNonFunctionalMembers(room)).toHaveLength(0);
});
});
describe("if there are some functional room members", () => {
beforeEach(() => {
vi.mocked(room.getJoinedMembers).mockReturnValue([roomMember1, roomMember2]);
vi.mocked(getFunctionalMembers).mockReturnValue([roomMember1.userId]);
});
it("should only return the non-functional members", () => {
const members = getJoinedNonFunctionalMembers(room);
expect(members).not.toContain(roomMember1);
expect(members).toContain(roomMember2);
});
});
});
@@ -0,0 +1,52 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { describe, it, expect } from "vitest";
import { Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
import { createTestClient, mkEvent } from "test-utils";
import { getFunctionalMembers } from "./getFunctionalMembers";
describe("getRoomFunctionalMembers", () => {
const client = createTestClient();
const room = new Room("!room:example.com", client, client.getUserId()!);
it("should return an empty array if no functional members state event exists", () => {
expect(getFunctionalMembers(room)).toHaveLength(0);
});
it("should return an empty array if functional members state event does not have a service_members field", () => {
room.currentState.setStateEvents([
mkEvent({
event: true,
type: UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name,
user: "@user:example.com)",
room: room.roomId,
skey: "",
content: {},
}),
]);
expect(getFunctionalMembers(room)).toHaveLength(0);
});
it("should return service_members field of the functional users state event", () => {
room.currentState.setStateEvents([
mkEvent({
event: true,
type: UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name,
user: "@user:example.com)",
room: room.roomId,
skey: "",
content: { service_members: ["@user:example.com"] },
}),
]);
expect(getFunctionalMembers(room)).toEqual(["@user:example.com"]);
});
});
@@ -0,0 +1,54 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect } from "vitest";
import { type Room } from "matrix-js-sdk/src/matrix";
import { DefaultTagID } from "../../stores/room-list-v3/skip-list/tag";
import { CUSTOM_SECTION_TAG_PREFIX } from "../../stores/room-list-v3/section";
import { getSectionTagForRoom } from "./getSectionTagForRoom";
import { getTagsForRoom } from "./getTagsForRoom";
vi.mock("./getTagsForRoom");
const mockGetTagsForRoom = vi.mocked(getTagsForRoom);
describe("getSectionTagForRoom", () => {
const room = {} as Room;
it("should return null when room has no tags", () => {
mockGetTagsForRoom.mockReturnValue([]);
expect(getSectionTagForRoom(room)).toBeNull();
});
it("should return null when room only has a non-section tag", () => {
mockGetTagsForRoom.mockReturnValue([DefaultTagID.Untagged]);
expect(getSectionTagForRoom(room)).toBeNull();
});
it.each([DefaultTagID.Favourite, DefaultTagID.LowPriority, `${CUSTOM_SECTION_TAG_PREFIX}abc-123`])(
"should return section tag %s when present",
(tag) => {
mockGetTagsForRoom.mockReturnValue([tag]);
expect(getSectionTagForRoom(room)).toBe(tag);
},
);
it("should return the first section tag when multiple are present", () => {
const customTag = `${CUSTOM_SECTION_TAG_PREFIX}abc-123`;
mockGetTagsForRoom.mockReturnValue([DefaultTagID.Favourite, customTag]);
expect(getSectionTagForRoom(room)).toBe(DefaultTagID.Favourite);
});
it("should ignore non-section tags and return the section tag", () => {
const customTag = `${CUSTOM_SECTION_TAG_PREFIX}abc-123`;
mockGetTagsForRoom.mockReturnValue([DefaultTagID.Untagged, customTag]);
expect(getSectionTagForRoom(room)).toBe(customTag);
});
});
@@ -0,0 +1,151 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach } from "vitest";
import { JoinRule, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { createTestClient, mkRoom } from "test-utils";
import { DefaultTagID } from "../../stores/room-list-v3/skip-list/tag";
import { getTagsForRoom } from "./getTagsForRoom";
import DMRoomMap from "../DMRoomMap";
describe("getTagsForRoom", () => {
let client: MatrixClient;
let rooms: Room[];
beforeEach(() => {
client = createTestClient();
rooms = [];
const dmRoomMap = {
getUserIdForRoomId: vi.fn().mockReturnValue(undefined),
} as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap);
});
function makeRoom(roomId: string): Room {
mkRoom(client, roomId, rooms);
vi.mocked(client).getRoom.mockImplementation((id) => rooms.find((r) => r.roomId === id) ?? null);
vi.mocked(client).getRooms.mockImplementation(() => rooms);
return client.getRoom(roomId)!;
}
it("should return [Invite] for a room where the user is invited", () => {
const room = makeRoom("!invited:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Invite);
const tags = getTagsForRoom(room);
expect(tags).toEqual([DefaultTagID.Invite]);
});
it.each([KnownMembership.Leave, KnownMembership.Ban])(
"should return [Archived] for a room where the user has %s",
(membership) => {
const room = makeRoom(`!${membership.toLowerCase()}:server`);
vi.mocked(room.getMyMembership).mockReturnValue(membership);
const tags = getTagsForRoom(room);
expect(tags).toEqual([DefaultTagID.Archived]);
},
);
describe("joined rooms", () => {
describe("with no user-defined tags and not a DM", () => {
it("should return [Untagged] when the room has no tags and is not a DM", () => {
const room = makeRoom("!plain:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
(room as any).tags = {};
const tags = getTagsForRoom(room);
expect(tags).toEqual([DefaultTagID.Untagged]);
});
});
it("should return [DM] when the room is a DM", () => {
const room = makeRoom("!dm:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
(room as any).tags = {};
vi.mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue("@alice:server");
const tags = getTagsForRoom(room);
expect(tags).toContain(DefaultTagID.DM);
expect(tags).not.toContain(DefaultTagID.Untagged);
});
describe("rooms with user-defined tags", () => {
it("should return the user-defined tags", () => {
const room = makeRoom("!tagged:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
(room as any).tags = { "m.favourite": {}, "u.alice": {} };
const tags = getTagsForRoom(room);
expect(tags).toContain("m.favourite");
expect(tags).toContain("u.alice");
expect(tags).not.toContain(DefaultTagID.Untagged);
});
it("should not check DM status when user-defined tags are already present", () => {
const room = makeRoom("!tagged-dm:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
(room as any).tags = { "m.lowpriority": {} };
// Even if the room is a DM, user-defined tags take priority
vi.mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue("@alice:server");
const tags = getTagsForRoom(room);
expect(tags).toContain("m.lowpriority");
expect(tags).not.toContain(DefaultTagID.DM);
});
});
});
describe("conference (call) rooms", () => {
it.each([JoinRule.Public, JoinRule.Knock])(
"should include Conference tag for a call room with %s join rule",
(joinRule) => {
const room = makeRoom(`!call:${joinRule}:server`);
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
vi.mocked(room.isCallRoom).mockReturnValue(true);
vi.mocked(room.getJoinRule).mockReturnValue(joinRule);
const tags = getTagsForRoom(room);
expect(tags).toContain(DefaultTagID.Conference);
},
);
it.each([JoinRule.Invite, JoinRule.Private])(
"should not include Conference tag for a call room with %s join rule",
(joinRule) => {
const room = makeRoom(`!call:${joinRule}:server`);
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
vi.mocked(room.isCallRoom).mockReturnValue(true);
vi.mocked(room.getJoinRule).mockReturnValue(joinRule);
const tags = getTagsForRoom(room);
expect(tags).not.toContain(DefaultTagID.Conference);
},
);
it("should include Conference alongside Untagged for a public call room with no other tags", () => {
const room = makeRoom("!callPublicPlain:server");
vi.mocked(room.getMyMembership).mockReturnValue(KnownMembership.Join);
(room as any).tags = {};
vi.mocked(room.isCallRoom).mockReturnValue(true);
vi.mocked(room.getJoinRule).mockReturnValue(JoinRule.Public);
const tags = getTagsForRoom(room);
// Conference is added to the tag list before the Untagged fallback check,
// so tags.length is already 1 — Untagged is not appended.
expect(tags).toContain(DefaultTagID.Conference);
expect(tags).not.toContain(DefaultTagID.Untagged);
});
});
});
@@ -0,0 +1,60 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { Room } from "matrix-js-sdk/src/matrix";
import { getMockClientWithEventEmitter } from "test-utils";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { inviteToRoom } from "./inviteToRoom";
describe("inviteToRoom()", () => {
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const makeRoom = (): Room => {
const client = getMockClientWithEventEmitter({
isGuest: vi.fn(),
});
const room = new Room(roomId, client, userId);
return room;
};
beforeEach(() => {
// stub
vi.spyOn(defaultDispatcher, "dispatch").mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
it("requires registration when a guest tries to invite to a room", () => {
const room = makeRoom();
vi.spyOn(room.client, "isGuest").mockReturnValue(true);
inviteToRoom(room);
expect(defaultDispatcher.dispatch).toHaveBeenCalledTimes(1);
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "require_registration" });
});
it("opens the room inviter", () => {
const room = makeRoom();
vi.spyOn(room.client, "isGuest").mockReturnValue(false);
inviteToRoom(room);
expect(defaultDispatcher.dispatch).toHaveBeenCalledTimes(1);
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "view_invite", roomId });
});
});
@@ -0,0 +1,104 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeAll, beforeEach } from "vitest";
import { type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { mkRoomMemberJoinEvent, mkThirdPartyInviteEvent, stubClient } from "test-utils";
import DMRoomMap from "../DMRoomMap";
import { shouldEncryptRoomWithSingle3rdPartyInvite } from "./shouldEncryptRoomWithSingle3rdPartyInvite";
import { privateShouldBeEncrypted } from "../rooms";
vi.mock("../rooms", () => ({
privateShouldBeEncrypted: vi.fn(),
}));
describe("shouldEncryptRoomWithSingle3rdPartyInvite", () => {
let client: MatrixClient;
let thirdPartyInviteEvent: MatrixEvent;
let roomWithOneThirdPartyInvite: Room;
beforeAll(() => {
client = stubClient();
DMRoomMap.makeShared(client);
});
beforeEach(() => {
roomWithOneThirdPartyInvite = new Room("!room1:example.com", client, client.getSafeUserId());
thirdPartyInviteEvent = mkThirdPartyInviteEvent(
client.getSafeUserId(),
"user@example.com",
roomWithOneThirdPartyInvite.roomId,
);
roomWithOneThirdPartyInvite.currentState.setStateEvents([
mkRoomMemberJoinEvent(client.getSafeUserId(), roomWithOneThirdPartyInvite.roomId),
thirdPartyInviteEvent,
]);
vi.spyOn(DMRoomMap.shared(), "getRoomIds").mockReturnValue(new Set([roomWithOneThirdPartyInvite.roomId]));
});
describe("when well-known promotes encryption", () => {
beforeEach(() => {
vi.mocked(privateShouldBeEncrypted).mockReturnValue(true);
});
it("should return true + invite event for a DM room with one third-party invite", () => {
expect(shouldEncryptRoomWithSingle3rdPartyInvite(roomWithOneThirdPartyInvite)).toEqual({
shouldEncrypt: true,
inviteEvent: thirdPartyInviteEvent,
});
});
it("should return false for a non-DM room with one third-party invite", () => {
vi.mocked(DMRoomMap.shared().getRoomIds).mockReturnValue(new Set());
expect(shouldEncryptRoomWithSingle3rdPartyInvite(roomWithOneThirdPartyInvite)).toEqual({
shouldEncrypt: false,
});
});
it("should return false for a DM room with two members", () => {
roomWithOneThirdPartyInvite.currentState.setStateEvents([
mkRoomMemberJoinEvent("@user2:example.com", roomWithOneThirdPartyInvite.roomId),
]);
expect(shouldEncryptRoomWithSingle3rdPartyInvite(roomWithOneThirdPartyInvite)).toEqual({
shouldEncrypt: false,
});
});
it("should return false for a DM room with two third-party invites", () => {
roomWithOneThirdPartyInvite.currentState.setStateEvents([
mkThirdPartyInviteEvent(
client.getSafeUserId(),
"user2@example.com",
roomWithOneThirdPartyInvite.roomId,
),
]);
expect(shouldEncryptRoomWithSingle3rdPartyInvite(roomWithOneThirdPartyInvite)).toEqual({
shouldEncrypt: false,
});
});
});
describe("when well-known does not promote encryption", () => {
beforeEach(() => {
vi.mocked(privateShouldBeEncrypted).mockReturnValue(false);
});
it("should return false for a DM room with one third-party invite", () => {
expect(shouldEncryptRoomWithSingle3rdPartyInvite(roomWithOneThirdPartyInvite)).toEqual({
shouldEncrypt: false,
});
});
});
});
@@ -0,0 +1,60 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { describe, it, expect, beforeEach } from "vitest";
import { type Room } from "matrix-js-sdk/src/matrix";
import { createTestClient } from "test-utils";
import { getMockedRooms } from "../../../test/unit-tests/stores/room-list-v3/skip-list/getMockedRooms";
import { DefaultTagID } from "../../stores/room-list-v3/skip-list/tag";
import { compareRoomsByRecency, sortRoomsByRecency } from "./sortRoomsByRecency";
describe("sortRoomsByRecency", () => {
let userId: string;
let rooms: Room[];
beforeEach(() => {
const client = createTestClient();
userId = client.getSafeUserId();
rooms = getMockedRooms(client);
});
describe("sortRoomsByRecency", () => {
it("sorts an arbitrary list by recency without mutating the input", () => {
const input = [rooms[0], rooms[5], rooms[2]];
const inputCopy = [...input];
const sorted = sortRoomsByRecency(input, userId);
// ts: room5 (6) > room2 (3) > room0 (1)
expect(sorted).toEqual([rooms[5], rooms[2], rooms[0]]);
// The input array is not mutated.
expect(input).toEqual(inputCopy);
});
it("does not move muted or low-priority rooms (pure recency)", () => {
const recent = rooms[99]; // highest ts
recent.tags = { [DefaultTagID.LowPriority]: { order: 0 } };
const sorted = sortRoomsByRecency([rooms[0], rooms[50], recent], userId);
// A pure recency sort keeps the most recent room first even though it is
// low priority (the full RecencySorter would sink it).
expect(sorted[0]).toBe(recent);
});
});
describe("compareRoomsByRecency", () => {
it("orders the more recent room first", () => {
// rooms[10] (ts 11) is more recent than rooms[3] (ts 4)
expect(compareRoomsByRecency(rooms[10], rooms[3], userId)).toBeLessThan(0);
expect(compareRoomsByRecency(rooms[3], rooms[10], userId)).toBeGreaterThan(0);
});
});
});
+211
View File
@@ -0,0 +1,211 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// @vitest-environment happy-dom
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { Room } from "matrix-js-sdk/src/matrix";
import { getMockClientWithEventEmitter } from "test-utils";
import RoomListActions from "../../actions/RoomListActions";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { DefaultTagID, type TagID } from "../../stores/room-list-v3/skip-list/tag";
import { CHATS_TAG, CUSTOM_SECTION_TAG_PREFIX } from "../../stores/room-list-v3/section";
import { tagRoom } from "./tagRoom";
import * as getSectionTagForRoomUtils from "./getSectionTagForRoom";
describe("tagRoom()", () => {
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const customTag = `${CUSTOM_SECTION_TAG_PREFIX}my-section`;
const makeRoom = (currentSectionTag: TagID | null = null): Room => {
const client = getMockClientWithEventEmitter({
isGuest: vi.fn(),
});
const room = new Room(roomId, client, userId);
vi.spyOn(getSectionTagForRoomUtils, "getSectionTagForRoom").mockReturnValue(currentSectionTag);
return room;
};
beforeEach(() => {
// stub
vi.spyOn(defaultDispatcher, "dispatch").mockImplementation(() => {});
vi.spyOn(RoomListActions, "tagRoom").mockReturnValue({ action: "mocked_tag_room_action", fn: () => {} });
});
afterEach(() => {
vi.restoreAllMocks();
});
it("does nothing when room tag is not allowed", () => {
const room = makeRoom();
tagRoom(room, DefaultTagID.ServerNotice);
expect(defaultDispatcher.dispatch).not.toHaveBeenCalled();
expect(RoomListActions.tagRoom).not.toHaveBeenCalled();
});
describe("when a room has no section tag", () => {
it("should tag a room as favourite", () => {
const room = makeRoom();
tagRoom(room, DefaultTagID.Favourite);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
null, // remove
DefaultTagID.Favourite, // add
);
});
it("should tag a room low priority", () => {
const room = makeRoom();
tagRoom(room, DefaultTagID.LowPriority);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
null, // remove
DefaultTagID.LowPriority, // add
);
});
it("should tag a room with a custom section", () => {
const room = makeRoom();
tagRoom(room, customTag);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
null, // remove
customTag, // add
);
});
it("should do nothing meaningful when applying CHATS_TAG", () => {
const room = makeRoom();
tagRoom(room, CHATS_TAG);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
null, // remove
null, // add
);
});
});
describe("when a room is tagged as favourite", () => {
it("should unfavourite a room", () => {
const room = makeRoom(DefaultTagID.Favourite);
tagRoom(room, DefaultTagID.Favourite);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
DefaultTagID.Favourite, // remove
null, // add
);
});
it("should tag a room low priority", () => {
const room = makeRoom(DefaultTagID.Favourite);
tagRoom(room, DefaultTagID.LowPriority);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
DefaultTagID.Favourite, // remove
DefaultTagID.LowPriority, // add
);
});
it("should remove the favourite tag when applying CHATS_TAG", () => {
const room = makeRoom(DefaultTagID.Favourite);
tagRoom(room, CHATS_TAG);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
DefaultTagID.Favourite, // remove
null, // add
);
});
});
describe("when a room is tagged as low priority", () => {
it("should favourite a room", () => {
const room = makeRoom(DefaultTagID.LowPriority);
tagRoom(room, DefaultTagID.Favourite);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
DefaultTagID.LowPriority, // remove
DefaultTagID.Favourite, // add
);
});
it("should untag a room low priority", () => {
const room = makeRoom(DefaultTagID.LowPriority);
tagRoom(room, DefaultTagID.LowPriority);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
DefaultTagID.LowPriority, // remove
null, // add
);
});
});
describe("when a room is tagged with a custom section", () => {
const otherCustomTag = `${CUSTOM_SECTION_TAG_PREFIX}other-section`;
it.each([
{ label: "untag the custom section", applyTag: customTag, expectedAdd: null },
{ label: "replace with favourite", applyTag: DefaultTagID.Favourite, expectedAdd: DefaultTagID.Favourite },
{ label: "replace with another custom section", applyTag: otherCustomTag, expectedAdd: otherCustomTag },
{ label: "remove section tag when applying CHATS_TAG", applyTag: CHATS_TAG, expectedAdd: null },
])("should $label", ({ applyTag, expectedAdd }) => {
const room = makeRoom(customTag);
tagRoom(room, applyTag);
expect(defaultDispatcher.dispatch).toHaveBeenCalled();
expect(RoomListActions.tagRoom).toHaveBeenCalledWith(
room.client,
room,
customTag, // remove
expectedAdd, // add
);
});
});
});