diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 6faac2a485a..0723e30374e 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -144,6 +144,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // The following properties are set by onReady as they live in account_data private _allRoomsInHome = false; private _enabledMetaSpaces: MetaSpace[] = []; + /** Whether the feature flag is set for MSC3946 */ + private _msc3946ProcessDynamicPredecessor: boolean = SettingsStore.getValue("feature_dynamic_room_predecessors"); public constructor() { super(defaultDispatcher, {}); @@ -151,6 +153,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); SettingsStore.monitorSetting("Spaces.showPeopleInSpace", null); + SettingsStore.monitorSetting("feature_dynamic_room_predecessors", null); } public get invitedSpaces(): Room[] { @@ -352,7 +355,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()!); }) .map((ev) => { - const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey()!, true); + const history = this.matrixClient.getRoomUpgradeHistory( + ev.getStateKey()!, + true, + this._msc3946ProcessDynamicPredecessor, + ); return history[history.length - 1]; }) .filter((room) => { @@ -450,7 +457,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { useCache = true, ): Set => { if (space === MetaSpace.Home && this.allRoomsInHome) { - return new Set(this.matrixClient.getVisibleRooms().map((r) => r.roomId)); + return new Set( + this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor).map((r) => r.roomId), + ); } // meta spaces never have descendants @@ -539,7 +548,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private rebuildSpaceHierarchy = (): void => { - const visibleSpaces = this.matrixClient.getVisibleRooms().filter((r) => r.isSpaceRoom()); + const visibleSpaces = this.matrixClient + .getVisibleRooms(this._msc3946ProcessDynamicPredecessor) + .filter((r) => r.isSpaceRoom()); const [joinedSpaces, invitedSpaces] = visibleSpaces.reduce( ([joined, invited], s) => { switch (getEffectiveMembership(s.getMyMembership())) { @@ -573,7 +584,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private rebuildParentMap = (): void => { - const joinedSpaces = this.matrixClient.getVisibleRooms().filter((r) => { + const joinedSpaces = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor).filter((r) => { return r.isSpaceRoom() && r.getMyMembership() === "join"; }); @@ -595,7 +606,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } else { const rooms = new Set( this.matrixClient - .getVisibleRooms() + .getVisibleRooms(this._msc3946ProcessDynamicPredecessor) .filter(this.showInHomeSpace) .map((r) => r.roomId), ); @@ -609,7 +620,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private rebuildMetaSpaces = (): void => { const enabledMetaSpaces = new Set(this.enabledMetaSpaces); - const visibleRooms = this.matrixClient.getVisibleRooms(); + const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); if (enabledMetaSpaces.has(MetaSpace.Home)) { this.rebuildHomeSpace(); @@ -643,7 +654,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private updateNotificationStates = (spaces?: SpaceKey[]): void => { const enabledMetaSpaces = new Set(this.enabledMetaSpaces); - const visibleRooms = this.matrixClient.getVisibleRooms(); + const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); let dmBadgeSpace: MetaSpace | undefined; // only show badges on dms on the most relevant space if such exists @@ -729,7 +740,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private onRoomsUpdate = (): void => { - const visibleRooms = this.matrixClient.getVisibleRooms(); + const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); const prevRoomsBySpace = this.roomIdsBySpace; const prevUsersBySpace = this.userIdsBySpace; @@ -792,7 +803,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Expand room IDs to all known versions of the given rooms const expandedRoomIds = new Set( Array.from(roomIds).flatMap((roomId) => { - return this.matrixClient.getRoomUpgradeHistory(roomId, true).map((r) => r.roomId); + return this.matrixClient + .getRoomUpgradeHistory(roomId, true, this._msc3946ProcessDynamicPredecessor) + .map((r) => r.roomId); }), ); @@ -1275,6 +1288,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.updateNotificationStates([payload.roomId]); } break; + + case "feature_dynamic_room_predecessors": + this._msc3946ProcessDynamicPredecessor = SettingsStore.getValue( + "feature_dynamic_room_predecessors", + ); + this.rebuildSpaceHierarchy(); + break; } } } @@ -1355,6 +1375,15 @@ export default class SpaceStore { public static get instance(): SpaceStoreClass { return SpaceStore.internalInstance; } + + /** + * @internal for test only + */ + public static testInstance(): SpaceStoreClass { + const store = new SpaceStoreClass(); + store.start(); + return store; + } } window.mxSpaceStore = SpaceStore.instance; diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 2e086fd6367..04a94c1bbad 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -1331,4 +1331,102 @@ describe("SpaceStore", () => { expect(metaSpaces).toEqual(store.enabledMetaSpaces); removeListener(); }); + + describe("when feature_dynamic_room_predecessors is not enabled", () => { + beforeAll(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (settingName) => settingName === "Spaces.allRoomsInHome", + ); + // @ts-ignore calling a private function + SpaceStore.instance.onAction({ + action: Action.SettingUpdated, + settingName: "feature_dynamic_room_predecessors", + roomId: null, + level: SettingLevel.ACCOUNT, + newValueAtLevel: SettingLevel.ACCOUNT, + newValue: false, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("passes that value in calls to getVisibleRooms and getRoomUpgradeHistory during startup", async () => { + // When we create an instance, which calls onReady and rebuildSpaceHierarchy + mkSpace("!dynspace:example.com", [mkRoom("!dynroom:example.com").roomId]); + await run(); + + // Then we pass through the correct value of the feature flag to + // everywhere that needs it. + expect(client.getVisibleRooms).toHaveBeenCalledWith(false); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(true); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(expect.anything(), expect.anything(), false); + expect(client.getRoomUpgradeHistory).not.toHaveBeenCalledWith(expect.anything(), expect.anything(), true); + expect(client.getRoomUpgradeHistory).not.toHaveBeenCalledWith(expect.anything(), expect.anything()); + }); + + it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => { + // Given a store + const store = SpaceStore.testInstance(); + + // When we ask for filtered room ids + store.getSpaceFilteredRoomIds(MetaSpace.Home); + + // Then we pass the correct feature flag + expect(client.getVisibleRooms).toHaveBeenCalledWith(false); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(true); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(); + }); + }); + + describe("when feature_dynamic_room_predecessors is enabled", () => { + beforeAll(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation( + (settingName) => + settingName === "Spaces.allRoomsInHome" || settingName === "feature_dynamic_room_predecessors", + ); + // @ts-ignore calling a private function + SpaceStore.instance.onAction({ + action: Action.SettingUpdated, + settingName: "feature_dynamic_room_predecessors", + roomId: null, + level: SettingLevel.ACCOUNT, + newValueAtLevel: SettingLevel.ACCOUNT, + newValue: true, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("passes that value in calls to getVisibleRooms and getRoomUpgradeHistory during startup", async () => { + // When we create an instance, which calls onReady and rebuildSpaceHierarchy + mkSpace("!dynspace:example.com", [mkRoom("!dynroom:example.com").roomId]); + await run(); + + // Then we pass through the correct value of the feature flag to + // everywhere that needs it. + expect(client.getVisibleRooms).toHaveBeenCalledWith(true); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(false); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(); + expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(expect.anything(), expect.anything(), true); + expect(client.getRoomUpgradeHistory).not.toHaveBeenCalledWith(expect.anything(), expect.anything(), false); + expect(client.getRoomUpgradeHistory).not.toHaveBeenCalledWith(expect.anything(), expect.anything()); + }); + + it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => { + // Given a store + const store = SpaceStore.testInstance(); + // When we ask for filtered room ids + store.getSpaceFilteredRoomIds(MetaSpace.Home); + + // Then we pass the correct feature flag + expect(client.getVisibleRooms).toHaveBeenCalledWith(true); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(false); + expect(client.getVisibleRooms).not.toHaveBeenCalledWith(); + }); + }); });