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

Fix changing space sometimes bouncing to the wrong space #7910

Merged
merged 4 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/stores/room-list/SpaceWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class SpaceWatcher {
if (!isMetaSpace(this.activeSpace)) {
SpaceStore.instance.traverseSpace(this.activeSpace, roomId => {
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
});
}, false);
}
this.filter.updateSpace(this.activeSpace);
};
Expand Down
21 changes: 13 additions & 8 deletions src/stores/spaces/SpaceStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return;
}

this._activeSpace = space;
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);

if (contextSwitch) {
// view last selected room from space
const roomId = window.localStorage.getItem(getSpaceContextKey(this.activeSpace));
const roomId = window.localStorage.getItem(getSpaceContextKey(space));

// if the space being selected is an invite then always view that invite
// else if the last viewed room in this space is joined then view that
Expand All @@ -255,22 +251,31 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
room_id: roomId,
context_switch: true,
metricsTrigger: "WebSpaceContextSwitch",
});
}, true);
} else if (cliSpace) {
defaultDispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: space,
context_switch: true,
metricsTrigger: "WebSpaceContextSwitch",
});
}, true);
} else {
defaultDispatcher.dispatch<ViewHomePagePayload>({
action: Action.ViewHomePage,
context_switch: true,
});
}, true);
}
}

// We can set the space after context switching as the dispatch handler which stores the last viewed room
// specifically no-ops on context_switch=true.
this._activeSpace = space;
// Emit after a synchronous dispatch for context switching to prevent racing with SpaceWatcher calling
// Room::loadMembersIfNeeded which could (via onMemberUpdate) call upon switchSpaceIfNeeded causing the
// space to wrongly bounce.
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);

// persist space selected
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space);

Expand Down
64 changes: 62 additions & 2 deletions test/stores/SpaceStore-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ limitations under the License.

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

import "../skinned-sdk"; // Must be first for skinning to work

import SpaceStore from "../../src/stores/spaces/SpaceStore";
import {
MetaSpace,
Expand All @@ -30,11 +31,11 @@ import {
import * as testUtils from "../test-utils";
import { mkEvent, stubClient } from "../test-utils";
import DMRoomMap from "../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
import SettingsStore from "../../src/settings/SettingsStore";
import { SettingLevel } from "../../src/settings/SettingLevel";
import { Action } from "../../src/dispatcher/actions";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";

jest.useFakeTimers();

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

const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");

let rooms = [];
const mkRoom = (roomId: string) => testUtils.mkRoom(client, roomId, rooms);
const mkSpace = (spaceId: string, children: string[] = []) => testUtils.mkSpace(client, spaceId, rooms, children);
Expand Down Expand Up @@ -122,6 +125,8 @@ describe("SpaceStore", () => {
[MetaSpace.People]: true,
[MetaSpace.Orphans]: true,
});

spyDispatcher.mockClear();
});

afterEach(async () => {
Expand Down Expand Up @@ -842,6 +847,61 @@ describe("SpaceStore", () => {
});
});

it("does not race with lazy loading", async () => {
store.setActiveSpace(MetaSpace.Home);

mkRoom(room1);
const space = mkSpace(space1, [room1]);
// seed the context for space1 to be room1
window.localStorage.setItem(`mx_space_context_${space1}`, room1);

await run();

const deferred = defer<void>();
(space.loadMembersIfNeeded as jest.Mock).mockImplementation(() => {
const event = mkEvent({
event: true,
type: EventType.RoomMember,
content: { membership: "join" },
skey: dm1Partner.userId,
user: dm1Partner.userId,
room: space1,
});

client.emit(RoomStateEvent.Members, event, null, null);
deferred.resolve();
});

spyDispatcher.mockClear();
const getCurrentRoom = () => {
for (let i = spyDispatcher.mock.calls.length - 1; i >= 0; i--) {
if (spyDispatcher.mock.calls[i][0].action === Action.ViewRoom) {
return spyDispatcher.mock.calls[i][0]["room_id"];
}
}
};

// set up space with LL where loadMembersIfNeeded emits membership events which trip switchSpaceIfNeeded
expect(space.loadMembersIfNeeded).not.toHaveBeenCalled();

store.setActiveSpace(space1, true);
// traverse the space and call loadMembersIfNeeded, similarly to SpaceWatcher's behaviour
store.traverseSpace(space1, roomId => {
client.getRoom(roomId)?.loadMembersIfNeeded();
}, false);

expect(store.activeSpace).toBe(space1);
expect(getCurrentRoom()).toBe(room1);

await deferred.promise;
expect(store.activeSpace).toBe(space1);
expect(getCurrentRoom()).toBe(room1);

jest.runAllTimers();
expect(store.activeSpace).toBe(space1);
expect(getCurrentRoom()).toBe(room1);
});

describe("context switching tests", () => {
let dispatcherRef;
let currentRoom = null;
Expand Down
1 change: 1 addition & 0 deletions test/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export function mkStubRoom(roomId = null, name: string, client: MatrixClient): R
getAltAliases: jest.fn().mockReturnValue([]),
timeline: [],
getJoinRule: jest.fn().mockReturnValue("invite"),
loadMembersIfNeeded: jest.fn(),
client,
} as unknown as Room;
}
Expand Down