RLS: Remove forgotten room from skiplist (#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
This commit is contained in:
@@ -107,6 +107,7 @@ import Views from "../../Views";
|
||||
import { type FocusNextType, type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
||||
import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
|
||||
import { type AfterForgetRoomPayload } from "../../dispatcher/payloads/AfterForgetRoomPayload";
|
||||
import { type DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
|
||||
import { type ViewStartChatOrReusePayload } from "../../dispatcher/payloads/ViewStartChatOrReusePayload";
|
||||
import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
|
||||
@@ -1269,10 +1270,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
dis.dispatch({ action: Action.ViewHomePage });
|
||||
}
|
||||
|
||||
// We have to manually update the room list because the forgotten room will not
|
||||
// be notified to us, therefore the room list will have no other way of knowing
|
||||
// the room is forgotten.
|
||||
if (room) RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
if (room) {
|
||||
// Legacy room list store needs to be told to manually remove this room
|
||||
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
// New room list store will remove the room on the following dispatch
|
||||
dis.dispatch<AfterForgetRoomPayload>({ action: Action.AfterForgetRoom, room });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
const errCode = err.errcode || _td("error|unknown_error_code");
|
||||
|
||||
@@ -235,6 +235,12 @@ export enum Action {
|
||||
*/
|
||||
AfterLeaveRoom = "after_leave_room",
|
||||
|
||||
/**
|
||||
* Dispatched after a room has been successfully forgotten
|
||||
* Should be used with AfterForgetRoomPayload.
|
||||
*/
|
||||
AfterForgetRoom = "after_forget_room",
|
||||
|
||||
/**
|
||||
* Used to defer actions until after sync is complete
|
||||
* LifecycleStore will emit deferredAction payload after 'MatrixActions.sync'
|
||||
|
||||
16
src/dispatcher/payloads/AfterForgetRoomPayload.ts
Normal file
16
src/dispatcher/payloads/AfterForgetRoomPayload.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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 { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { type Action } from "../actions";
|
||||
import { type ActionPayload } from "../payloads";
|
||||
|
||||
export interface AfterForgetRoomPayload extends ActionPayload {
|
||||
action: Action.AfterForgetRoom;
|
||||
room: Room;
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { type Sorter, SortingAlgorithm } from "./skip-list/sorters";
|
||||
import { SettingLevel } from "../../settings/SettingLevel";
|
||||
import { MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE } from "../../utils/notifications";
|
||||
import { getChangedOverrideRoomMutePushRules } from "../room-list/utils/roomMute";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
||||
/**
|
||||
* These are the filters passed to the room skip list.
|
||||
@@ -245,6 +246,13 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
|
||||
this.addRoomAndEmit(payload.room, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case Action.AfterForgetRoom: {
|
||||
const room = payload.room;
|
||||
this.roomSkipList.removeRoom(room);
|
||||
this.emit(LISTS_UPDATE_EVENT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ import { type ValidatedServerConfig } from "../../../../src/utils/ValidatedServe
|
||||
import Modal from "../../../../src/Modal.tsx";
|
||||
import { SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore.ts";
|
||||
import { clearStorage } from "../../../../src/Lifecycle";
|
||||
import RoomListStore from "../../../../src/stores/room-list/RoomListStore.ts";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
|
||||
completeAuthorizationCodeGrant: jest.fn(),
|
||||
@@ -155,6 +156,7 @@ describe("<MatrixChat />", () => {
|
||||
whoami: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
getDeviceId: jest.fn(),
|
||||
forget: () => Promise.resolve(),
|
||||
});
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
const serverConfig = {
|
||||
@@ -675,6 +677,34 @@ describe("<MatrixChat />", () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("forget_room", () => {
|
||||
it("should dispatch after_forget_room action on successful forget", async () => {
|
||||
await clearAllModals();
|
||||
await getComponentAndWaitForReady();
|
||||
|
||||
// Mock out the old room list store
|
||||
jest.spyOn(RoomListStore.instance, "manualRoomUpdate").mockImplementation(async () => {});
|
||||
|
||||
// Register a mock function to the dispatcher
|
||||
const fn = jest.fn();
|
||||
defaultDispatcher.register(fn);
|
||||
|
||||
// Forge the room
|
||||
defaultDispatcher.dispatch({
|
||||
action: "forget_room",
|
||||
room_id: roomId,
|
||||
});
|
||||
|
||||
// On success, we expect the following action to have been dispatched.
|
||||
await waitFor(() => {
|
||||
expect(fn).toHaveBeenCalledWith({
|
||||
action: Action.AfterForgetRoom,
|
||||
room: room,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("leave_room", () => {
|
||||
beforeEach(async () => {
|
||||
await clearAllModals();
|
||||
|
||||
@@ -27,6 +27,7 @@ import { SortingAlgorithm } from "../../../../src/stores/room-list-v3/skip-list/
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import * as utils from "../../../../src/utils/notifications";
|
||||
import * as roomMute from "../../../../src/stores/room-list/utils/roomMute";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
||||
describe("RoomListStoreV3", () => {
|
||||
async function getRoomListStore() {
|
||||
@@ -121,6 +122,27 @@ describe("RoomListStoreV3", () => {
|
||||
expect(store.getSortedRooms()[0].roomId).toEqual(room.roomId);
|
||||
});
|
||||
|
||||
it("Forgotten room is removed", async () => {
|
||||
const { store, rooms, dispatcher } = await getRoomListStore();
|
||||
const room = rooms[37];
|
||||
|
||||
// Room at index 37 should be in the store now
|
||||
expect(store.getSortedRooms().map((r) => r.roomId)).toContain(room.roomId);
|
||||
|
||||
// Forget room at index 37
|
||||
const payload = {
|
||||
action: Action.AfterForgetRoom,
|
||||
room: room,
|
||||
};
|
||||
const fn = jest.fn();
|
||||
store.on(LISTS_UPDATE_EVENT, fn);
|
||||
dispatcher.dispatch(payload, true);
|
||||
|
||||
// Room at index 37 should no longer be in the store
|
||||
expect(fn).toHaveBeenCalled();
|
||||
expect(store.getSortedRooms().map((r) => r.roomId)).not.toContain(room.roomId);
|
||||
});
|
||||
|
||||
it.each([KnownMembership.Join, KnownMembership.Invite])(
|
||||
"Room is removed when membership changes to leave",
|
||||
async (membership) => {
|
||||
|
||||
Reference in New Issue
Block a user