Skip to content

Commit cfeb696

Browse files
MidhunSureshRsnowping
authored andcommitted
RLS: Remove forgotten room from skiplist (element-hq#29933)
* Dispatch an action when room is forgotten * Dispatch an action when room is forgotten * Remove room on action * Add test * Write test for matrixchat * Add payload info to comment
1 parent 91705eb commit cfeb696

File tree

6 files changed

+89
-4
lines changed

6 files changed

+89
-4
lines changed

src/components/structures/MatrixChat.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import Views from "../../Views";
107107
import { type FocusNextType, type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
108108
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
109109
import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
110+
import { type AfterForgetRoomPayload } from "../../dispatcher/payloads/AfterForgetRoomPayload";
110111
import { type DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
111112
import { type ViewStartChatOrReusePayload } from "../../dispatcher/payloads/ViewStartChatOrReusePayload";
112113
import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
@@ -1269,10 +1270,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
12691270
dis.dispatch({ action: Action.ViewHomePage });
12701271
}
12711272

1272-
// We have to manually update the room list because the forgotten room will not
1273-
// be notified to us, therefore the room list will have no other way of knowing
1274-
// the room is forgotten.
1275-
if (room) RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
1273+
if (room) {
1274+
// Legacy room list store needs to be told to manually remove this room
1275+
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
1276+
// New room list store will remove the room on the following dispatch
1277+
dis.dispatch<AfterForgetRoomPayload>({ action: Action.AfterForgetRoom, room });
1278+
}
12761279
})
12771280
.catch((err) => {
12781281
const errCode = err.errcode || _td("error|unknown_error_code");

src/dispatcher/actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ export enum Action {
235235
*/
236236
AfterLeaveRoom = "after_leave_room",
237237

238+
/**
239+
* Dispatched after a room has been successfully forgotten
240+
* Should be used with AfterForgetRoomPayload.
241+
*/
242+
AfterForgetRoom = "after_forget_room",
243+
238244
/**
239245
* Used to defer actions until after sync is complete
240246
* LifecycleStore will emit deferredAction payload after 'MatrixActions.sync'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { type Room } from "matrix-js-sdk/src/matrix";
9+
10+
import { type Action } from "../actions";
11+
import { type ActionPayload } from "../payloads";
12+
13+
export interface AfterForgetRoomPayload extends ActionPayload {
14+
action: Action.AfterForgetRoom;
15+
room: Room;
16+
}

src/stores/room-list-v3/RoomListStoreV3.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { type Sorter, SortingAlgorithm } from "./skip-list/sorters";
3434
import { SettingLevel } from "../../settings/SettingLevel";
3535
import { MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE } from "../../utils/notifications";
3636
import { getChangedOverrideRoomMutePushRules } from "../room-list/utils/roomMute";
37+
import { Action } from "../../dispatcher/actions";
3738

3839
/**
3940
* These are the filters passed to the room skip list.
@@ -245,6 +246,13 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
245246
this.addRoomAndEmit(payload.room, true);
246247
break;
247248
}
249+
250+
case Action.AfterForgetRoom: {
251+
const room = payload.room;
252+
this.roomSkipList.removeRoom(room);
253+
this.emit(LISTS_UPDATE_EVENT);
254+
break;
255+
}
248256
}
249257
}
250258

test/unit-tests/components/structures/MatrixChat-test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import { type ValidatedServerConfig } from "../../../../src/utils/ValidatedServe
6969
import Modal from "../../../../src/Modal.tsx";
7070
import { SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore.ts";
7171
import { clearStorage } from "../../../../src/Lifecycle";
72+
import RoomListStore from "../../../../src/stores/room-list/RoomListStore.ts";
7273

7374
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
7475
completeAuthorizationCodeGrant: jest.fn(),
@@ -155,6 +156,7 @@ describe("<MatrixChat />", () => {
155156
whoami: jest.fn(),
156157
logout: jest.fn(),
157158
getDeviceId: jest.fn(),
159+
forget: () => Promise.resolve(),
158160
});
159161
let mockClient: Mocked<MatrixClient>;
160162
const serverConfig = {
@@ -675,6 +677,34 @@ describe("<MatrixChat />", () => {
675677
jest.restoreAllMocks();
676678
});
677679

680+
describe("forget_room", () => {
681+
it("should dispatch after_forget_room action on successful forget", async () => {
682+
await clearAllModals();
683+
await getComponentAndWaitForReady();
684+
685+
// Mock out the old room list store
686+
jest.spyOn(RoomListStore.instance, "manualRoomUpdate").mockImplementation(async () => {});
687+
688+
// Register a mock function to the dispatcher
689+
const fn = jest.fn();
690+
defaultDispatcher.register(fn);
691+
692+
// Forge the room
693+
defaultDispatcher.dispatch({
694+
action: "forget_room",
695+
room_id: roomId,
696+
});
697+
698+
// On success, we expect the following action to have been dispatched.
699+
await waitFor(() => {
700+
expect(fn).toHaveBeenCalledWith({
701+
action: Action.AfterForgetRoom,
702+
room: room,
703+
});
704+
});
705+
});
706+
});
707+
678708
describe("leave_room", () => {
679709
beforeEach(async () => {
680710
await clearAllModals();

test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { SortingAlgorithm } from "../../../../src/stores/room-list-v3/skip-list/
2727
import SettingsStore from "../../../../src/settings/SettingsStore";
2828
import * as utils from "../../../../src/utils/notifications";
2929
import * as roomMute from "../../../../src/stores/room-list/utils/roomMute";
30+
import { Action } from "../../../../src/dispatcher/actions";
3031

3132
describe("RoomListStoreV3", () => {
3233
async function getRoomListStore() {
@@ -121,6 +122,27 @@ describe("RoomListStoreV3", () => {
121122
expect(store.getSortedRooms()[0].roomId).toEqual(room.roomId);
122123
});
123124

125+
it("Forgotten room is removed", async () => {
126+
const { store, rooms, dispatcher } = await getRoomListStore();
127+
const room = rooms[37];
128+
129+
// Room at index 37 should be in the store now
130+
expect(store.getSortedRooms().map((r) => r.roomId)).toContain(room.roomId);
131+
132+
// Forget room at index 37
133+
const payload = {
134+
action: Action.AfterForgetRoom,
135+
room: room,
136+
};
137+
const fn = jest.fn();
138+
store.on(LISTS_UPDATE_EVENT, fn);
139+
dispatcher.dispatch(payload, true);
140+
141+
// Room at index 37 should no longer be in the store
142+
expect(fn).toHaveBeenCalled();
143+
expect(store.getSortedRooms().map((r) => r.roomId)).not.toContain(room.roomId);
144+
});
145+
124146
it.each([KnownMembership.Join, KnownMembership.Invite])(
125147
"Room is removed when membership changes to leave",
126148
async (membership) => {

0 commit comments

Comments
 (0)