Room list: extract getTagsForRoom from old room list store (#32716)

* refactor: extract `getTagsForRoom` from old rls

`getTagsForRoom` doesn't rely on the rls state. We can extract it safely
to an external function.

The function is not moved into the rls v3 because the rls is for the
room list and not for other ui elements.

* refactor: remove dead code

* test: add more tests for `getTagsForRoom`

* refactor: `getTagsForRoom` in old rls

* doc: add missing tsdoc for `room`
This commit is contained in:
Florian Duros 2026-03-04 18:14:48 +01:00 committed by GitHub
parent ac37bebf22
commit 1c66f0ba01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 231 additions and 137 deletions

View File

@ -34,6 +34,7 @@ import { Key } from "../../../Keyboard";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { tagRoom } from "../../../utils/room/tagRoom";
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
import { getTagsForRoom } from "../../../utils/room/getTagsForRoom";
export interface RoomSummaryCardState {
isDirectMessage: boolean;
@ -171,9 +172,7 @@ export function useRoomSummaryCardViewModel(
// value to check if the user can invite to the room
const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
RoomListStore.instance.getTagsForRoom(room),
);
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => getTagsForRoom(room));
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
const isDirectMessage = useIsDirectMessage(room);

View File

@ -44,6 +44,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature";
import { DeveloperToolsOption } from "./DeveloperToolsOption";
import { useSettingValue } from "../../../hooks/useSettings";
import { getTagsForRoom } from "../../../utils/room/getTagsForRoom";
export interface RoomGeneralContextMenuProps extends IContextMenuProps {
room: Room;
@ -121,9 +122,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
...props
}) => {
const cli = useContext(MatrixClientContext);
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
RoomListStore.instance.getTagsForRoom(room),
);
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => getTagsForRoom(room));
const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
const wrapHandler = (
handler: (ev: ButtonEvent) => void,
@ -148,7 +147,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
if (!cli) return;
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
const isApplied = getTagsForRoom(room).includes(tagId);
const removeTag = isApplied ? tagId : inverseTag;
const addTag = isApplied ? null : tagId;
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0));

View File

@ -14,8 +14,8 @@ import { arrayDiff } from "../../utils/arrays";
import { type RoomNotificationState } from "./RoomNotificationState";
import { NotificationState, NotificationStateEvents } from "./NotificationState";
import { DefaultTagID } from "../room-list-v3/skip-list/tag";
import RoomListStore from "../room-list/RoomListStore";
import { RoomNotificationStateStore } from "./RoomNotificationStateStore";
import { getTagsForRoom } from "../../utils/room/getTagsForRoom";
export class SpaceNotificationState extends NotificationState {
public rooms: Room[] = []; // exposed only for tests
@ -72,7 +72,7 @@ export class SpaceNotificationState extends NotificationState {
this._level = NotificationLevel.None;
for (const [roomId, state] of Object.entries(this.states)) {
const room = this.rooms.find((r) => r.roomId === roomId);
const roomTags = room ? RoomListStore.instance.getTagsForRoom(room) : [];
const roomTags = room ? getTagsForRoom(room) : [];
// We ignore unreads in LowPriority rooms, see https://github.com/vector-im/element-web/issues/16836
if (roomTags.includes(DefaultTagID.LowPriority) && state.level === NotificationLevel.Activity) continue;

View File

@ -92,15 +92,6 @@ export interface RoomListStore extends EventEmitter {
*/
removeFilter(filter: IFilterCondition): void;
/**
* Gets the tags for a room identified by the store. The returned set
* should never be empty, and will contain DefaultTagID.Untagged if
* the store is not aware of any tags.
* @param room The room to get the tags for.
* @returns The tags for the room.
*/
getTagsForRoom(room: Room): TagID[];
/**
* Manually update a room with a given cause. This should only be used if the
* room list store would otherwise be incapable of doing the update itself. Note

View File

@ -35,7 +35,7 @@ import { type RoomListStore as Interface, RoomListStoreEvent } from "./Interface
import { UPDATE_EVENT } from "../AsyncStore";
import { SdkContextClass } from "../../contexts/SDKContext";
import { getChangedOverrideRoomMutePushRules } from "../room-list-v3/utils";
import { DefaultTagID, type TagID } from "../room-list-v3/skip-list/tag";
import { type TagID } from "../room-list-v3/skip-list/tag";
export const LISTS_UPDATE_EVENT = RoomListStoreEvent.ListsUpdate;
export const LISTS_LOADING_EVENT = RoomListStoreEvent.ListsLoading; // unused; used by SlidingRoomListStore
@ -597,19 +597,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient<EmptyObject> implem
}
}
/**
* Gets the tags for a room identified by the store. The returned set
* should never be empty, and will contain DefaultTagID.Untagged if
* the store is not aware of any tags.
* @param room The room to get the tags for.
* @returns The tags for the room.
*/
public getTagsForRoom(room: Room): TagID[] {
const algorithmTags = this.algorithm.getTagsForRoom(room);
if (!algorithmTags) return [DefaultTagID.Untagged];
return algorithmTags;
}
public getCount(tagId: TagID): number {
// The room list store knows about all the rooms, so just return the length.
return this.orderedLists[tagId].length || 0;

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { type Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { EventEmitter } from "events";
@ -24,16 +24,12 @@ import {
type ListAlgorithm,
type SortAlgorithm,
} from "./models";
import {
EffectiveMembership,
getEffectiveMembership,
getEffectiveMembershipTag,
splitRoomsByMembership,
} from "../../../utils/membership";
import { EffectiveMembership, splitRoomsByMembership } from "../../../utils/membership";
import { type OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
import { isRoomVisible } from "../../room-list-v3/isRoomVisible";
import { CallStore, CallStoreEvent } from "../../CallStore";
import { getTagsForRoom, getTagsOfJoinedRoom } from "../../../utils/room/getTagsForRoom";
/**
* Fired when the Algorithm has determined a list has been updated.
@ -499,7 +495,7 @@ export class Algorithm extends EventEmitter {
// Now process all the joined rooms. This is a bit more complicated
for (const room of memberships[EffectiveMembership.Join]) {
const tags = this.getTagsOfJoinedRoom(room);
const tags = getTagsOfJoinedRoom(room);
let inTag = false;
if (tags.length > 0) {
@ -541,42 +537,6 @@ export class Algorithm extends EventEmitter {
}
}
public getTagsForRoom(room: Room): TagID[] {
const tags: TagID[] = [];
if (!getEffectiveMembership(room.getMyMembership())) return []; // peeked room has no tags
const membership = getEffectiveMembershipTag(room);
if (membership === EffectiveMembership.Invite) {
tags.push(DefaultTagID.Invite);
} else if (membership === EffectiveMembership.Leave) {
tags.push(DefaultTagID.Archived);
} else {
tags.push(...this.getTagsOfJoinedRoom(room));
}
if (!tags.length) tags.push(DefaultTagID.Untagged);
return tags;
}
private getTagsOfJoinedRoom(room: Room): TagID[] {
let tags = Object.keys(room.tags || {});
if (tags.length === 0) {
// Check to see if it's a DM if it isn't anything else
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
tags = [DefaultTagID.DM];
}
}
if (room.isCallRoom() && (room.getJoinRule() === JoinRule.Public || room.getJoinRule() === JoinRule.Knock)) {
tags.push(DefaultTagID.Conference);
}
return tags;
}
/**
* Updates the roomsToTags map
*/
@ -677,7 +637,7 @@ export class Algorithm extends EventEmitter {
let didTagChange = false;
if (cause === RoomUpdateCause.PossibleTagChange) {
const oldTags = this.roomIdsToTags[room.roomId] || [];
const newTags = this.getTagsForRoom(room);
const newTags = getTagsForRoom(room);
const diff = arrayDiff(oldTags, newTags);
if (diff.removed.length > 0 || diff.added.length > 0) {
for (const rmTag of diff.removed) {
@ -737,7 +697,7 @@ export class Algorithm extends EventEmitter {
}
// Get the tags for the room and populate the cache
const roomTags = this.getTagsForRoom(room).filter((t) => !isNullOrUndefined(this.cachedRooms[t]));
const roomTags = getTagsForRoom(room).filter((t) => !isNullOrUndefined(this.cachedRooms[t]));
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
// which means we should *always* have a tag to go off of.

View File

@ -0,0 +1,56 @@
/*
* 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.
*/
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { DefaultTagID, type TagID } from "../../stores/room-list-v3/skip-list/tag";
import { EffectiveMembership, getEffectiveMembershipTag } from "../membership";
import DMRoomMap from "../DMRoomMap";
/**
* Get the tags for a room.
* @param room - the room to get the tags for
* @returns an array of tags for the room. If the room has no tags, it will return an array with the DefaultTagID.Untagged tag.
*/
export function getTagsForRoom(room: Room): TagID[] {
const tags: TagID[] = [];
const membership = getEffectiveMembershipTag(room);
if (membership === EffectiveMembership.Invite) {
tags.push(DefaultTagID.Invite);
} else if (membership === EffectiveMembership.Leave) {
tags.push(DefaultTagID.Archived);
} else {
tags.push(...getTagsOfJoinedRoom(room));
}
if (!tags.length) tags.push(DefaultTagID.Untagged);
return tags;
}
/**
* Get the tags for a room that the user has joined. It checks for user defined tags first, then checks if it's a DM, and finally checks if it's a conference room.
* @param room - the room to get the tags for
* @returns an array of tags for the room. If the room has no user defined tags, is not a DM, and is not a conference room, it will return an empty array.
*/
export function getTagsOfJoinedRoom(room: Room): TagID[] {
let tags = Object.keys(room.tags || {});
if (tags.length === 0) {
// Check to see if it's a DM if it isn't anything else
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
tags = [DefaultTagID.DM];
}
}
if (room.isCallRoom() && (room.getJoinRule() === JoinRule.Public || room.getJoinRule() === JoinRule.Knock)) {
tags.push(DefaultTagID.Conference);
}
return tags;
}

View File

@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
import { type Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import RoomListStore from "../../stores/room-list/RoomListStore";
import { DefaultTagID, type TagID } from "../../stores/room-list-v3/skip-list/tag";
import RoomListActions from "../../actions/RoomListActions";
import dis from "../../dispatcher/dispatcher";
import { getTagsForRoom } from "./getTagsForRoom";
/**
* Toggle tag for a given room
@ -22,7 +22,7 @@ import dis from "../../dispatcher/dispatcher";
export function tagRoom(room: Room, tagId: TagID): void {
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
const isApplied = getTagsForRoom(room).includes(tagId);
const removeTag = isApplied ? tagId : inverseTag;
const addTag = isApplied ? null : tagId;
dis.dispatch(RoomListActions.tagRoom(room.client, room, removeTag, addTag, 0));

View File

@ -11,7 +11,6 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { useRoomSummaryCardViewModel } from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel";
import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
import RoomListStore from "../../../../../src/stores/room-list/RoomListStore";
import { DefaultTagID } from "../../../../../src/stores/room-list-v3/skip-list/tag";
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
@ -23,6 +22,7 @@ import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/Re
import { inviteToRoom } from "../../../../../src/utils/room/inviteToRoom";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import * as hooks from "../../../../../src/hooks/useAccountData";
import * as getTagsForRoomUtils from "../../../../../src/utils/room/getTagsForRoom";
jest.mock("../../../../../src/utils/room/inviteToRoom", () => ({
inviteToRoom: jest.fn(),
@ -43,7 +43,7 @@ describe("useRoomSummaryCardViewModel", () => {
getUserIdForRoomId: jest.fn(),
} as unknown as DMRoomMap);
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]);
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValue([]);
});
afterEach(() => {
@ -195,14 +195,14 @@ describe("useRoomSummaryCardViewModel", () => {
describe("favorite room state", () => {
it("should identify favorite rooms", () => {
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([DefaultTagID.Favourite]);
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValue([DefaultTagID.Favourite]);
const { result } = render();
expect(result.current.isFavorite).toBe(true);
});
it("should identify non-favorite rooms", () => {
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]);
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValue([]);
const { result } = render();
expect(result.current.isFavorite).toBe(false);

View File

@ -22,13 +22,13 @@ import {
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { DefaultTagID } from "../../../../../src/stores/room-list-v3/skip-list/tag";
import RoomListStore from "../../../../../src/stores/room-list/RoomListStore";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { mkMessage, stubClient } from "../../../../test-utils/test-utils";
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../../src/settings/UIFeature";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { clearAllModals } from "../../../../test-utils";
import * as getTagsForRoomUtils from "../../../../../src/utils/room/getTagsForRoom";
jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
@ -74,7 +74,7 @@ describe("RoomGeneralContextMenu", () => {
} as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap);
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValueOnce([
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValueOnce([
DefaultTagID.DM,
DefaultTagID.Favourite,
]);
@ -87,7 +87,7 @@ describe("RoomGeneralContextMenu", () => {
});
it("renders an empty context menu for archived rooms", async () => {
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValueOnce([DefaultTagID.Archived]);
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValueOnce([DefaultTagID.Archived]);
const { container } = getComponent({});
expect(container).toMatchSnapshot();

View File

@ -10,7 +10,6 @@ import {
ConditionKind,
EventType,
type IPushRule,
JoinRule,
MatrixEvent,
PendingEventOrdering,
PushRuleActionName,
@ -23,11 +22,10 @@ import defaultDispatcher, { type MatrixDispatcher } from "../../../../src/dispat
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import SettingsStore, { type CallbackFn } from "../../../../src/settings/SettingsStore";
import { ListAlgorithm, SortAlgorithm } from "../../../../src/stores/room-list/algorithms/models";
import { DefaultTagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
import { OrderedDefaultTagIDs, RoomUpdateCause } from "../../../../src/stores/room-list/models";
import RoomListStore, { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { flushPromises, stubClient, upsertRoomStateEvents, mkRoom } from "../../../test-utils";
import { flushPromises, stubClient, upsertRoomStateEvents } from "../../../test-utils";
import { DEFAULT_PUSH_RULES, makePushRule } from "../../../test-utils/pushRules";
describe("RoomListStore", () => {
@ -350,49 +348,4 @@ describe("RoomListStore", () => {
});
});
});
describe("Correctly tags rooms", () => {
it("renders Public and Knock rooms in Conferences section", () => {
const videoRoomPrivate = "!videoRoomPrivate_server";
const videoRoomPublic = "!videoRoomPublic_server";
const videoRoomKnock = "!videoRoomKnock_server";
const rooms: Room[] = [];
mkRoom(client, videoRoomPrivate, rooms);
mkRoom(client, videoRoomPublic, rooms);
mkRoom(client, videoRoomKnock, rooms);
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
mocked(client).getRooms.mockImplementation(() => rooms);
const videoRoomKnockRoom = client.getRoom(videoRoomKnock);
(videoRoomKnockRoom!.getJoinRule as jest.Mock).mockReturnValue(JoinRule.Knock);
const videoRoomPrivateRoom = client.getRoom(videoRoomPrivate);
(videoRoomPrivateRoom!.getJoinRule as jest.Mock).mockReturnValue(JoinRule.Invite);
const videoRoomPublicRoom = client.getRoom(videoRoomPublic);
(videoRoomPublicRoom!.getJoinRule as jest.Mock).mockReturnValue(JoinRule.Public);
[videoRoomPrivateRoom, videoRoomPublicRoom, videoRoomKnockRoom].forEach((room) => {
(room!.isCallRoom as jest.Mock).mockReturnValue(true);
});
expect(
RoomListStore.instance
.getTagsForRoom(client.getRoom(videoRoomPublic)!)
.includes(DefaultTagID.Conference),
).toBeTruthy();
expect(
RoomListStore.instance
.getTagsForRoom(client.getRoom(videoRoomKnock)!)
.includes(DefaultTagID.Conference),
).toBeTruthy();
expect(
RoomListStore.instance
.getTagsForRoom(client.getRoom(videoRoomPrivate)!)
.includes(DefaultTagID.Conference),
).toBeFalsy();
});
});
});

View File

@ -0,0 +1,149 @@
/*
* 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.
*/
import { JoinRule, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked } from "jest-mock";
import { createTestClient, mkRoom } from "../../../test-utils";
import { DefaultTagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
import { getTagsForRoom } from "../../../../src/utils/room/getTagsForRoom";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
describe("getTagsForRoom", () => {
let client: MatrixClient;
let rooms: Room[];
beforeEach(() => {
client = createTestClient();
rooms = [];
const dmRoomMap = {
getUserIdForRoomId: jest.fn().mockReturnValue(undefined),
} as unknown as DMRoomMap;
DMRoomMap.setShared(dmRoomMap);
});
function makeRoom(roomId: string): Room {
mkRoom(client, roomId, rooms);
mocked(client).getRoom.mockImplementation((id) => rooms.find((r) => r.roomId === id) ?? null);
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");
(room.getMyMembership as jest.Mock).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`);
(room.getMyMembership as jest.Mock).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");
(room.getMyMembership as jest.Mock).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");
(room.getMyMembership as jest.Mock).mockReturnValue(KnownMembership.Join);
(room as any).tags = {};
mocked(DMRoomMap.shared().getUserIdForRoomId as jest.Mock).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");
(room.getMyMembership as jest.Mock).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");
(room.getMyMembership as jest.Mock).mockReturnValue(KnownMembership.Join);
(room as any).tags = { "m.lowpriority": {} };
// Even if the room is a DM, user-defined tags take priority
mocked(DMRoomMap.shared().getUserIdForRoomId as jest.Mock).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`);
(room.getMyMembership as jest.Mock).mockReturnValue(KnownMembership.Join);
(room.isCallRoom as jest.Mock).mockReturnValue(true);
(room.getJoinRule as jest.Mock).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`);
(room.getMyMembership as jest.Mock).mockReturnValue(KnownMembership.Join);
(room.isCallRoom as jest.Mock).mockReturnValue(true);
(room.getJoinRule as jest.Mock).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");
(room.getMyMembership as jest.Mock).mockReturnValue(KnownMembership.Join);
(room as any).tags = {};
(room.isCallRoom as jest.Mock).mockReturnValue(true);
(room.getJoinRule as jest.Mock).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);
});
});
});

View File

@ -11,9 +11,9 @@ import { Room } from "matrix-js-sdk/src/matrix";
import RoomListActions from "../../../../src/actions/RoomListActions";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { DefaultTagID, type TagID } from "../../../../src/stores/room-list-v3/skip-list/tag";
import RoomListStore from "../../../../src/stores/room-list/RoomListStore";
import { tagRoom } from "../../../../src/utils/room/tagRoom";
import { getMockClientWithEventEmitter } from "../../../test-utils";
import * as getTagsForRoomUtils from "../../../../src/utils/room/getTagsForRoom";
describe("tagRoom()", () => {
const userId = "@alice:server.org";
@ -25,7 +25,7 @@ describe("tagRoom()", () => {
});
const room = new Room(roomId, client, userId);
jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue(tags);
jest.spyOn(getTagsForRoomUtils, "getTagsForRoom").mockReturnValue(tags);
return room;
};