mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-10 22:56:27 +02:00
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:
parent
ac37bebf22
commit
1c66f0ba01
@ -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);
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
56
apps/web/src/utils/room/getTagsForRoom.ts
Normal file
56
apps/web/src/utils/room/getTagsForRoom.ts
Normal 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;
|
||||
}
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
149
apps/web/test/unit-tests/utils/room/getTagsForRoom-test.ts
Normal file
149
apps/web/test/unit-tests/utils/room/getTagsForRoom-test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user