Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 08c47ac

Browse files
authored
Fix changing space sometimes bouncing to the wrong space (#7910)
1 parent 482d756 commit 08c47ac

File tree

4 files changed

+77
-11
lines changed

4 files changed

+77
-11
lines changed

src/stores/room-list/SpaceWatcher.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class SpaceWatcher {
6969
if (!isMetaSpace(this.activeSpace)) {
7070
SpaceStore.instance.traverseSpace(this.activeSpace, roomId => {
7171
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
72-
});
72+
}, false);
7373
}
7474
this.filter.updateSpace(this.activeSpace);
7575
};

src/stores/spaces/SpaceStore.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
235235
return;
236236
}
237237

238-
this._activeSpace = space;
239-
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
240-
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);
241-
242238
if (contextSwitch) {
243239
// view last selected room from space
244-
const roomId = window.localStorage.getItem(getSpaceContextKey(this.activeSpace));
240+
const roomId = window.localStorage.getItem(getSpaceContextKey(space));
245241

246242
// if the space being selected is an invite then always view that invite
247243
// else if the last viewed room in this space is joined then view that
@@ -255,22 +251,31 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
255251
room_id: roomId,
256252
context_switch: true,
257253
metricsTrigger: "WebSpaceContextSwitch",
258-
});
254+
}, true);
259255
} else if (cliSpace) {
260256
defaultDispatcher.dispatch<ViewRoomPayload>({
261257
action: Action.ViewRoom,
262258
room_id: space,
263259
context_switch: true,
264260
metricsTrigger: "WebSpaceContextSwitch",
265-
});
261+
}, true);
266262
} else {
267263
defaultDispatcher.dispatch<ViewHomePagePayload>({
268264
action: Action.ViewHomePage,
269265
context_switch: true,
270-
});
266+
}, true);
271267
}
272268
}
273269

270+
// We can set the space after context switching as the dispatch handler which stores the last viewed room
271+
// specifically no-ops on context_switch=true.
272+
this._activeSpace = space;
273+
// Emit after a synchronous dispatch for context switching to prevent racing with SpaceWatcher calling
274+
// Room::loadMembersIfNeeded which could (via onMemberUpdate) call upon switchSpaceIfNeeded causing the
275+
// space to wrongly bounce.
276+
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
277+
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);
278+
274279
// persist space selected
275280
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space);
276281

test/stores/SpaceStore-test.ts

+62-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ limitations under the License.
1616

1717
import { EventType } from "matrix-js-sdk/src/@types/event";
1818
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
19+
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
20+
import { defer } from "matrix-js-sdk/src/utils";
1921

2022
import "../skinned-sdk"; // Must be first for skinning to work
21-
2223
import SpaceStore from "../../src/stores/spaces/SpaceStore";
2324
import {
2425
MetaSpace,
@@ -30,11 +31,11 @@ import {
3031
import * as testUtils from "../test-utils";
3132
import { mkEvent, stubClient } from "../test-utils";
3233
import DMRoomMap from "../../src/utils/DMRoomMap";
33-
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
3434
import defaultDispatcher from "../../src/dispatcher/dispatcher";
3535
import SettingsStore from "../../src/settings/SettingsStore";
3636
import { SettingLevel } from "../../src/settings/SettingLevel";
3737
import { Action } from "../../src/dispatcher/actions";
38+
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
3839

3940
jest.useFakeTimers();
4041

@@ -92,6 +93,8 @@ describe("SpaceStore", () => {
9293
const store = SpaceStore.instance;
9394
const client = MatrixClientPeg.get();
9495

96+
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
97+
9598
let rooms = [];
9699
const mkRoom = (roomId: string) => testUtils.mkRoom(client, roomId, rooms);
97100
const mkSpace = (spaceId: string, children: string[] = []) => testUtils.mkSpace(client, spaceId, rooms, children);
@@ -122,6 +125,8 @@ describe("SpaceStore", () => {
122125
[MetaSpace.People]: true,
123126
[MetaSpace.Orphans]: true,
124127
});
128+
129+
spyDispatcher.mockClear();
125130
});
126131

127132
afterEach(async () => {
@@ -842,6 +847,61 @@ describe("SpaceStore", () => {
842847
});
843848
});
844849

850+
it("does not race with lazy loading", async () => {
851+
store.setActiveSpace(MetaSpace.Home);
852+
853+
mkRoom(room1);
854+
const space = mkSpace(space1, [room1]);
855+
// seed the context for space1 to be room1
856+
window.localStorage.setItem(`mx_space_context_${space1}`, room1);
857+
858+
await run();
859+
860+
const deferred = defer<void>();
861+
(space.loadMembersIfNeeded as jest.Mock).mockImplementation(() => {
862+
const event = mkEvent({
863+
event: true,
864+
type: EventType.RoomMember,
865+
content: { membership: "join" },
866+
skey: dm1Partner.userId,
867+
user: dm1Partner.userId,
868+
room: space1,
869+
});
870+
871+
client.emit(RoomStateEvent.Members, event, null, null);
872+
deferred.resolve();
873+
});
874+
875+
spyDispatcher.mockClear();
876+
const getCurrentRoom = () => {
877+
for (let i = spyDispatcher.mock.calls.length - 1; i >= 0; i--) {
878+
if (spyDispatcher.mock.calls[i][0].action === Action.ViewRoom) {
879+
return spyDispatcher.mock.calls[i][0]["room_id"];
880+
}
881+
}
882+
};
883+
884+
// set up space with LL where loadMembersIfNeeded emits membership events which trip switchSpaceIfNeeded
885+
expect(space.loadMembersIfNeeded).not.toHaveBeenCalled();
886+
887+
store.setActiveSpace(space1, true);
888+
// traverse the space and call loadMembersIfNeeded, similarly to SpaceWatcher's behaviour
889+
store.traverseSpace(space1, roomId => {
890+
client.getRoom(roomId)?.loadMembersIfNeeded();
891+
}, false);
892+
893+
expect(store.activeSpace).toBe(space1);
894+
expect(getCurrentRoom()).toBe(room1);
895+
896+
await deferred.promise;
897+
expect(store.activeSpace).toBe(space1);
898+
expect(getCurrentRoom()).toBe(room1);
899+
900+
jest.runAllTimers();
901+
expect(store.activeSpace).toBe(space1);
902+
expect(getCurrentRoom()).toBe(room1);
903+
});
904+
845905
describe("context switching tests", () => {
846906
let dispatcherRef;
847907
let currentRoom = null;

test/test-utils/test-utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ export function mkStubRoom(roomId = null, name: string, client: MatrixClient): R
349349
getAltAliases: jest.fn().mockReturnValue([]),
350350
timeline: [],
351351
getJoinRule: jest.fn().mockReturnValue("invite"),
352+
loadMembersIfNeeded: jest.fn(),
352353
client,
353354
} as unknown as Room;
354355
}

0 commit comments

Comments
 (0)