From aa423da73551e40cb08e9c84540a9473102bde90 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 26 Jan 2022 12:46:35 +0100 Subject: [PATCH 01/14] rough working filter --- src/settings/Settings.tsx | 5 ++++ .../room-list/filters/SpaceFilterCondition.ts | 9 ++++++- src/stores/spaces/SpaceStore.ts | 26 +++++++++++++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 26ed5f872e6..3e85c0fdf86 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -858,6 +858,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: true, controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", null), }, + "Spaces.includeSubSpaceRoomsInRoomList": { + displayName: _td("Include all sub-space rooms in Space room list"), + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, + default: false, + }, "showCommunitiesInsteadOfSpaces": { displayName: _td("Display Communities instead of Spaces"), description: _td("Temporarily show communities instead of Spaces for this session. " + diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 90bf22ed207..c5efa4f612b 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -34,6 +34,7 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi private roomIds = new Set(); private userIds = new Set(); private showPeopleInSpace = true; + private showSubSpaceRoomsInSpace = true; private space: SpaceKey = MetaSpace.Home; public get kind(): FilterKind { @@ -41,11 +42,16 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi } public isVisible(room: Room): boolean { - return SpaceStore.instance.isRoomInSpace(this.space, room.roomId); + console.log('isVisible', this.showSubSpaceRoomsInSpace); + return SpaceStore.instance.isRoomInSpace(this.space, room.roomId, this.showSubSpaceRoomsInSpace); } private onStoreUpdate = async (forceUpdate = false): Promise => { + const beforeShowSubSpaceRoomsInSpace = this.showSubSpaceRoomsInSpace; + this.showSubSpaceRoomsInSpace = SettingsStore.getValue("Spaces.includeSubSpaceRoomsInRoomList"); + const beforeRoomIds = this.roomIds; + // clone the set as it may be mutated by the space store internally this.roomIds = new Set(SpaceStore.instance.getSpaceFilteredRoomIds(this.space)); @@ -59,6 +65,7 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi if (forceUpdate || beforeShowPeopleInSpace !== this.showPeopleInSpace || + beforeShowSubSpaceRoomsInSpace !== this.showSubSpaceRoomsInSpace || setHasDiff(beforeRoomIds, this.roomIds) || setHasDiff(beforeUserIds, this.userIds) ) { diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 63b00731916..56d43c0f83b 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -101,6 +101,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private notificationStateMap = new Map(); // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); // won't contain MetaSpace.People + // Map from space key to Set of room IDs that should be shown as part of that space's filter + private spaceFilteredDirectChildRooms = new Map>(); // won't contain MetaSpace.People // Map from space ID to Set of user IDs that should be shown as part of that space's filter private spaceFilteredUsers = new Map>(); // The space currently selected in the Space Panel @@ -119,6 +121,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); SettingsStore.monitorSetting("Spaces.showPeopleInSpace", null); + SettingsStore.monitorSetting("Spaces.includeSubSpaceRoomsInRoomList", null); } public get invitedSpaces(): Room[] { @@ -350,12 +353,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.parentMap.get(roomId) || new Set(); } - public isRoomInSpace(space: SpaceKey, roomId: string): boolean { + public isRoomInSpace(space: SpaceKey, roomId: string, includeSubSpaceRooms = true): boolean { if (space === MetaSpace.Home && this.allRoomsInHome) { return true; } - if (this.spaceFilteredRooms.get(space)?.has(roomId)) { + if (includeSubSpaceRooms && this.spaceFilteredRooms.get(space)?.has(roomId)) { + return true; + } + if (!includeSubSpaceRooms && this.spaceFilteredDirectChildRooms.get(space)?.has(roomId)) { return true; } @@ -380,7 +386,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return false; } - public getSpaceFilteredRoomIds = (space: SpaceKey): Set => { + public getSpaceFilteredRoomIds = (space: SpaceKey, includeSubSpaceRooms?: boolean): Set => { if (space === MetaSpace.Home && this.allRoomsInHome) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } @@ -692,14 +698,17 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // reusing results from like subtrees. const fn = (spaceId: string, parentPath: Set): [Set, Set] => { if (parentPath.has(spaceId)) return; // prevent cycles - // reuse existing results if multiple similar branches exist if (this.spaceFilteredRooms.has(spaceId) && this.spaceFilteredUsers.has(spaceId)) { return [this.spaceFilteredRooms.get(spaceId), this.spaceFilteredUsers.get(spaceId)]; } + + const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId)); - const roomIds = new Set(childRooms.map(r => r.roomId)); + + const directChildRoomIds = childRooms.map(r => r.roomId); + const roomIds = new Set(directChildRoomIds); const space = this.matrixClient?.getRoom(spaceId); const userIds = new Set(space?.getMembers().filter(m => { return m.membership === "join" || m.membership === "invite"; @@ -719,6 +728,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const expandedRoomIds = new Set(Array.from(roomIds).flatMap(roomId => { return this.matrixClient.getRoomUpgradeHistory(roomId, true).map(r => r.roomId); })); + const expandedDirectChildRoomIds = new Set(directChildRoomIds.flatMap(roomId => { + return this.matrixClient.getRoomUpgradeHistory(roomId, true).map(r => r.roomId); + })); + + console.log('YOOO', s, expandedRoomIds, expandedDirectChildRoomIds); + + this.spaceFilteredDirectChildRooms.set(spaceId, expandedDirectChildRoomIds); this.spaceFilteredRooms.set(spaceId, expandedRoomIds); this.spaceFilteredUsers.set(spaceId, userIds); return [expandedRoomIds, userIds]; From 90fb06cc692ecf5e1bb45fb50e5b071b6582f47e Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 28 Jan 2022 11:59:41 +0100 Subject: [PATCH 02/14] maintain set of direct space children, apply showSubSpaceRoomsInSpace setting in SpaceFilterCondition Signed-off-by: Kerry Archibald --- src/settings/Settings.tsx | 4 ++-- src/stores/room-list/filters/SpaceFilterCondition.ts | 3 +-- src/stores/spaces/SpaceStore.ts | 6 +----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 3e85c0fdf86..52d03fc382a 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -860,8 +860,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "Spaces.includeSubSpaceRoomsInRoomList": { displayName: _td("Include all sub-space rooms in Space room list"), - supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - default: false, + supportedLevels: LEVELS_ROOM_SETTINGS, + default: false, // @KERRY debugging }, "showCommunitiesInsteadOfSpaces": { displayName: _td("Display Communities instead of Spaces"), diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index c5efa4f612b..34cee0a69e4 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -42,13 +42,12 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi } public isVisible(room: Room): boolean { - console.log('isVisible', this.showSubSpaceRoomsInSpace); return SpaceStore.instance.isRoomInSpace(this.space, room.roomId, this.showSubSpaceRoomsInSpace); } private onStoreUpdate = async (forceUpdate = false): Promise => { const beforeShowSubSpaceRoomsInSpace = this.showSubSpaceRoomsInSpace; - this.showSubSpaceRoomsInSpace = SettingsStore.getValue("Spaces.includeSubSpaceRoomsInRoomList"); + this.showSubSpaceRoomsInSpace = SettingsStore.getValue("Spaces.includeSubSpaceRoomsInRoomList", this.space); const beforeRoomIds = this.roomIds; diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 56d43c0f83b..a1aa89481f8 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -386,7 +386,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return false; } - public getSpaceFilteredRoomIds = (space: SpaceKey, includeSubSpaceRooms?: boolean): Set => { + public getSpaceFilteredRoomIds = (space: SpaceKey): Set => { if (space === MetaSpace.Home && this.allRoomsInHome) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } @@ -703,8 +703,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return [this.spaceFilteredRooms.get(spaceId), this.spaceFilteredUsers.get(spaceId)]; } - - const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId)); const directChildRoomIds = childRooms.map(r => r.roomId); @@ -732,8 +730,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.matrixClient.getRoomUpgradeHistory(roomId, true).map(r => r.roomId); })); - console.log('YOOO', s, expandedRoomIds, expandedDirectChildRoomIds); - this.spaceFilteredDirectChildRooms.set(spaceId, expandedDirectChildRoomIds); this.spaceFilteredRooms.set(spaceId, expandedRoomIds); this.spaceFilteredUsers.set(spaceId, userIds); From ab1834784d6bf37c00b13c539fc12c89b805f50f Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 15:34:47 +0100 Subject: [PATCH 03/14] test isRoomInSpace Signed-off-by: Kerry Archibald --- src/settings/Settings.tsx | 2 +- src/stores/spaces/SpaceStore.ts | 1 + test/stores/SpaceStore-test.ts | 187 ++++++++++++++++++++------------ 3 files changed, 122 insertions(+), 68 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 52d03fc382a..cce95000584 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -861,7 +861,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "Spaces.includeSubSpaceRoomsInRoomList": { displayName: _td("Include all sub-space rooms in Space room list"), supportedLevels: LEVELS_ROOM_SETTINGS, - default: false, // @KERRY debugging + default: true, }, "showCommunitiesInsteadOfSpaces": { displayName: _td("Display Communities instead of Spaces"), diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index a1aa89481f8..2065295c90f 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -732,6 +732,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.spaceFilteredDirectChildRooms.set(spaceId, expandedDirectChildRoomIds); this.spaceFilteredRooms.set(spaceId, expandedRoomIds); + this.spaceFilteredUsers.set(spaceId, userIds); return [expandedRoomIds, userIds]; }; diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index ffb1ef91665..7ad60f5089b 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -58,9 +58,11 @@ const invite2 = "!invite2:server"; const room1 = "!room1:server"; const room2 = "!room2:server"; const room3 = "!room3:server"; +const room4 = "!room4:server"; const space1 = "!space1:server"; const space2 = "!space2:server"; const space3 = "!space3:server"; +const space4 = "!space4:server"; const getUserIdForRoomId = jest.fn(roomId => { return { @@ -303,11 +305,13 @@ describe("SpaceStore", () => { describe("test fixture 1", () => { beforeEach(async () => { - [fav1, fav2, fav3, dm1, dm2, dm3, orphan1, orphan2, invite1, invite2, room1, room2, room3] + [fav1, fav2, fav3, dm1, dm2, dm3, orphan1, orphan2, invite1, invite2, room1, room2, room3, room4] .forEach(mkRoom); mkSpace(space1, [fav1, room1]); mkSpace(space2, [fav1, fav2, fav3, room1]); mkSpace(space3, [invite2]); + mkSpace(space4, [room4, fav2, space2, space3]); + client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); [fav1, fav2, fav3].forEach(roomId => { @@ -383,85 +387,134 @@ describe("SpaceStore", () => { await run(); }); - it("home space contains orphaned rooms", () => { - expect(store.isRoomInSpace(MetaSpace.Home, orphan1)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Home, orphan2)).toBeTruthy(); - }); + describe('isRoomInSpace()', () => { - it("home space does not contain all favourites", () => { - expect(store.isRoomInSpace(MetaSpace.Home, fav1)).toBeFalsy(); - expect(store.isRoomInSpace(MetaSpace.Home, fav2)).toBeFalsy(); - expect(store.isRoomInSpace(MetaSpace.Home, fav3)).toBeFalsy(); - }); - it("home space contains dm rooms", () => { - expect(store.isRoomInSpace(MetaSpace.Home, dm1)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Home, dm2)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Home, dm3)).toBeTruthy(); - }); + it("home space contains orphaned rooms", () => { + expect(store.isRoomInSpace(MetaSpace.Home, orphan1)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Home, orphan2)).toBeTruthy(); + }); - it("home space contains invites", () => { - expect(store.isRoomInSpace(MetaSpace.Home, invite1)).toBeTruthy(); - }); + it("home space does not contain all favourites", () => { + expect(store.isRoomInSpace(MetaSpace.Home, fav1)).toBeFalsy(); + expect(store.isRoomInSpace(MetaSpace.Home, fav2)).toBeFalsy(); + expect(store.isRoomInSpace(MetaSpace.Home, fav3)).toBeFalsy(); + }); - it("home space contains invites even if they are also shown in a space", () => { - expect(store.isRoomInSpace(MetaSpace.Home, invite2)).toBeTruthy(); - }); + it("home space contains dm rooms", () => { + expect(store.isRoomInSpace(MetaSpace.Home, dm1)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Home, dm2)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Home, dm3)).toBeTruthy(); + }); - it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => { - await setShowAllRooms(true); - expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeTruthy(); - }); + it("home space contains invites", () => { + expect(store.isRoomInSpace(MetaSpace.Home, invite1)).toBeTruthy(); + }); - it("favourites space does contain favourites even if they are also shown in a space", async () => { - expect(store.isRoomInSpace(MetaSpace.Favourites, fav1)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Favourites, fav2)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Favourites, fav3)).toBeTruthy(); - }); + it("home space contains invites even if they are also shown in a space", () => { + expect(store.isRoomInSpace(MetaSpace.Home, invite2)).toBeTruthy(); + }); - it("people space does contain people even if they are also shown in a space", async () => { - expect(store.isRoomInSpace(MetaSpace.People, dm1)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.People, dm2)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.People, dm3)).toBeTruthy(); - }); + it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => { + await setShowAllRooms(true); + expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeTruthy(); + }); - it("orphans space does contain orphans even if they are also shown in all rooms", async () => { - await setShowAllRooms(true); - expect(store.isRoomInSpace(MetaSpace.Orphans, orphan1)).toBeTruthy(); - expect(store.isRoomInSpace(MetaSpace.Orphans, orphan2)).toBeTruthy(); - }); + it("favourites space does contain favourites even if they are also shown in a space", async () => { + expect(store.isRoomInSpace(MetaSpace.Favourites, fav1)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Favourites, fav2)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Favourites, fav3)).toBeTruthy(); + }); - it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => { - await setShowAllRooms(false); - expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeFalsy(); - }); + it("people space does contain people even if they are also shown in a space", async () => { + expect(store.isRoomInSpace(MetaSpace.People, dm1)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.People, dm2)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.People, dm3)).toBeTruthy(); + }); - it("space contains child rooms", () => { - expect(store.isRoomInSpace(space1, fav1)).toBeTruthy(); - expect(store.isRoomInSpace(space1, room1)).toBeTruthy(); - }); + it("orphans space does contain orphans even if they are also shown in all rooms", async () => { + await setShowAllRooms(true); + expect(store.isRoomInSpace(MetaSpace.Orphans, orphan1)).toBeTruthy(); + expect(store.isRoomInSpace(MetaSpace.Orphans, orphan2)).toBeTruthy(); + }); - it("space contains child favourites", () => { - expect(store.isRoomInSpace(space2, fav1)).toBeTruthy(); - expect(store.isRoomInSpace(space2, fav2)).toBeTruthy(); - expect(store.isRoomInSpace(space2, fav3)).toBeTruthy(); - expect(store.isRoomInSpace(space2, room1)).toBeTruthy(); - }); + it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => { + await setShowAllRooms(false); + expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeFalsy(); + }); - it("space contains child invites", () => { - expect(store.isRoomInSpace(space3, invite2)).toBeTruthy(); - }); + it("space contains child rooms", () => { + expect(store.isRoomInSpace(space1, fav1)).toBeTruthy(); + expect(store.isRoomInSpace(space1, room1)).toBeTruthy(); + }); + + it("returns true for all sub-space child rooms when includeSubSpaceRooms is undefined", () => { + expect(store.isRoomInSpace(space4, room4)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav2)).toBeTruthy(); + // space2's rooms + expect(store.isRoomInSpace(space4, fav1)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav3)).toBeTruthy(); + expect(store.isRoomInSpace(space4, room1)).toBeTruthy(); + // space3's rooms + expect(store.isRoomInSpace(space4, invite2)).toBeTruthy(); + }); + + it("returns true for all sub-space child rooms when includeSubSpaceRooms is true", () => { + expect(store.isRoomInSpace(space4, room4, true)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav2, true)).toBeTruthy(); + // space2's rooms + expect(store.isRoomInSpace(space4, fav1, true)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav3, true)).toBeTruthy(); + expect(store.isRoomInSpace(space4, room1, true)).toBeTruthy(); + // space3's rooms + expect(store.isRoomInSpace(space4, invite2, true)).toBeTruthy(); + }); - it("spaces contain dms which you have with members of that space", () => { - expect(store.isRoomInSpace(space1, dm1)).toBeTruthy(); - expect(store.isRoomInSpace(space2, dm1)).toBeFalsy(); - expect(store.isRoomInSpace(space3, dm1)).toBeFalsy(); - expect(store.isRoomInSpace(space1, dm2)).toBeFalsy(); - expect(store.isRoomInSpace(space2, dm2)).toBeTruthy(); - expect(store.isRoomInSpace(space3, dm2)).toBeFalsy(); - expect(store.isRoomInSpace(space1, dm3)).toBeFalsy(); - expect(store.isRoomInSpace(space2, dm3)).toBeFalsy(); - expect(store.isRoomInSpace(space3, dm3)).toBeFalsy(); + it("returns false for all sub-space child rooms when includeSubSpaceRooms is false", () => { + // direct children + expect(store.isRoomInSpace(space4, room4, false)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav2, false)).toBeTruthy(); + // space2's rooms + expect(store.isRoomInSpace(space4, fav1, false)).toBeFalsy(); + expect(store.isRoomInSpace(space4, fav3, false)).toBeFalsy(); + expect(store.isRoomInSpace(space4, room1, false)).toBeFalsy(); + // space3's rooms + expect(store.isRoomInSpace(space4, invite2, false)).toBeFalsy(); + }); + + it("space contains all sub-space's child rooms", () => { + expect(store.isRoomInSpace(space4, room4)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav2)).toBeTruthy(); + // space2's rooms + expect(store.isRoomInSpace(space4, fav1)).toBeTruthy(); + expect(store.isRoomInSpace(space4, fav3)).toBeTruthy(); + expect(store.isRoomInSpace(space4, room1)).toBeTruthy(); + // space3's rooms + expect(store.isRoomInSpace(space4, invite2)).toBeTruthy(); + }); + + it("space contains child favourites", () => { + expect(store.isRoomInSpace(space2, fav1)).toBeTruthy(); + expect(store.isRoomInSpace(space2, fav2)).toBeTruthy(); + expect(store.isRoomInSpace(space2, fav3)).toBeTruthy(); + expect(store.isRoomInSpace(space2, room1)).toBeTruthy(); + }); + + it("space contains child invites", () => { + expect(store.isRoomInSpace(space3, invite2)).toBeTruthy(); + }); + + it("spaces contain dms which you have with members of that space", () => { + expect(store.isRoomInSpace(space1, dm1)).toBeTruthy(); + expect(store.isRoomInSpace(space2, dm1)).toBeFalsy(); + expect(store.isRoomInSpace(space3, dm1)).toBeFalsy(); + expect(store.isRoomInSpace(space1, dm2)).toBeFalsy(); + expect(store.isRoomInSpace(space2, dm2)).toBeTruthy(); + expect(store.isRoomInSpace(space3, dm2)).toBeFalsy(); + expect(store.isRoomInSpace(space1, dm3)).toBeFalsy(); + expect(store.isRoomInSpace(space2, dm3)).toBeFalsy(); + expect(store.isRoomInSpace(space3, dm3)).toBeFalsy(); + }); }); it("dms are only added to Notification States for only the People Space", async () => { From 5cda60c47de0bbbb9df79d7b4464e4c1f681ee79 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 17:26:25 +0100 Subject: [PATCH 04/14] test SpaceFilterCOndition Signed-off-by: Kerry Archibald --- .../room-list/filters/SpaceFilterCondition.ts | 6 +- src/stores/spaces/SpaceStore.ts | 7 + .../filters/SpaceFilterCondition-test.ts | 235 ++++++++++++++++++ 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 test/stores/room-list/filters/SpaceFilterCondition-test.ts diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 34cee0a69e4..f5825fdba67 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -32,6 +32,7 @@ import SettingsStore from "../../../settings/SettingsStore"; */ export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { private roomIds = new Set(); + private directChildRoomIds = new Set(); private userIds = new Set(); private showPeopleInSpace = true; private showSubSpaceRoomsInSpace = true; @@ -50,9 +51,11 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi this.showSubSpaceRoomsInSpace = SettingsStore.getValue("Spaces.includeSubSpaceRoomsInRoomList", this.space); const beforeRoomIds = this.roomIds; - // clone the set as it may be mutated by the space store internally this.roomIds = new Set(SpaceStore.instance.getSpaceFilteredRoomIds(this.space)); + const beforeDirectChildRoomIds = this.roomIds; + // clone the set as it may be mutated by the space store internally + this.directChildRoomIds = new Set(SpaceStore.instance.getSpaceFilteredDirectChildRoomIds(this.space)); const beforeUserIds = this.userIds; // clone the set as it may be mutated by the space store internally @@ -66,6 +69,7 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi beforeShowPeopleInSpace !== this.showPeopleInSpace || beforeShowSubSpaceRoomsInSpace !== this.showSubSpaceRoomsInSpace || setHasDiff(beforeRoomIds, this.roomIds) || + setHasDiff(beforeDirectChildRoomIds, this.directChildRoomIds) || setHasDiff(beforeUserIds, this.userIds) ) { this.emit(FILTER_CHANGED); diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 2065295c90f..08c9e4ab2c1 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -393,6 +393,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.spaceFilteredRooms.get(space) || new Set(); }; + public getSpaceFilteredDirectChildRoomIds = (space: SpaceKey): Set => { + if (space === MetaSpace.Home && this.allRoomsInHome) { + return this.getSpaceFilteredRoomIds(space); + } + return this.spaceFilteredDirectChildRooms.get(space) || new Set(); + }; + public getSpaceFilteredUserIds = (space: SpaceKey): Set => { if (space === MetaSpace.Home && this.allRoomsInHome) { return undefined; diff --git a/test/stores/room-list/filters/SpaceFilterCondition-test.ts b/test/stores/room-list/filters/SpaceFilterCondition-test.ts new file mode 100644 index 00000000000..af38edb1723 --- /dev/null +++ b/test/stores/room-list/filters/SpaceFilterCondition-test.ts @@ -0,0 +1,235 @@ +import SettingsStore from "../../../../src/settings/SettingsStore"; +import { FILTER_CHANGED } from "../../../../src/stores/room-list/filters/IFilterCondition"; +import { SpaceFilterCondition } from "../../../../src/stores/room-list/filters/SpaceFilterCondition"; +import { MetaSpace } from "../../../../src/stores/spaces"; +import SpaceStore from "../../../../src/stores/spaces/SpaceStore"; + +jest.mock("../../../../src/settings/SettingsStore"); +jest.mock("../../../../src/stores/spaces/SpaceStore", () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const EventEmitter = require('events'); + class MockSpaceStore extends EventEmitter { + isRoomInSpace = jest.fn(); + getSpaceFilteredRoomIds = jest.fn().mockReturnValue(new Set([])); + getSpaceFilteredUserIds = jest.fn().mockReturnValue(new Set([])); + getSpaceFilteredDirectChildRoomIds = jest.fn().mockReturnValue(new Set([])) + } + return { instance: new MockSpaceStore() } +}); + +jest.useFakeTimers(); + +describe('SpaceFilterCondition', () => { + + const space1 = '!space1:server'; + const space2 = '!space2:server'; + const room1Id = '!r1:server'; + const room2Id = '!r2:server'; + const room3Id = '!r3:server'; + const user1Id = '@u1:server'; + const user2Id = '@u2:server'; + const user3Id = '@u3:server'; + const makeMockGetValue = (settings = {}) => (settingName, space) => settings[settingName]?.[space] || false; + + beforeEach(() => { + jest.resetAllMocks(); + (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue()); + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([])); + SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set([])); + SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([])); + SpaceStore.instance.isRoomInSpace.mockReturnValue(true); + }); + + const initFilter = (space): SpaceFilterCondition => { + const filter = new SpaceFilterCondition(); + filter.updateSpace(space); + jest.runOnlyPendingTimers(); + return filter; + } + + describe('isVisible', () => { + const room1 = { roomId: room1Id } as unknown as Room; + it('calls isRoomInSpace correctly when showSubSpaceRoomsInSpace is truthy', () => { + (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true } + })); + + const filter = initFilter(space1); + + + expect(filter.isVisible(room1)).toEqual(true); + expect(SpaceStore.instance.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, true); + }); + + it('calls isRoomInSpace correctly when showSubSpaceRoomsInSpace is truthy', () => { + (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false } + })); + + const filter = initFilter(space1); + + expect(filter.isVisible(room1)).toEqual(true); + expect(SpaceStore.instance.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, false); + }); + }); + + describe('onStoreUpdate', () => { + it('emits filter changed event when updateSpace is called even without changes', async () => { + const filter = new SpaceFilterCondition(); + const emitSpy = jest.spyOn(filter, 'emit'); + filter.updateSpace(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + + it('emits filter changed event when Spaces.includeSubSpaceRoomsInRoomList setting changes', async () => { + // init filter with setting true for space1 + (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true }, + })); + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false }, + })); + + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + + describe('showPeopleInSpace setting', () => { + it('emits filter changed event when setting changes', async () => { + // init filter with setting true for space1 + (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + ["Spaces.showPeopleInSpace"]: { [space1]: true }, + })); + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + ["Spaces.showPeopleInSpace"]: { [space1]: false }, + })); + + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + + it('emits filter changed event when setting is false and space changes to a meta space', async () => { + // init filter with setting true for space1 + (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + ["Spaces.showPeopleInSpace"]: { [space1]: false }, + })); + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + filter.updateSpace(MetaSpace.Home); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + }); + + it('does not emit filter changed event on store update when nothing changed', async () => { + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); + }); + + it('removes listener when updateSpace is called', async () => { + const filter = initFilter(space1); + filter.updateSpace(space2); + jest.runOnlyPendingTimers(); + const emitSpy = jest.spyOn(filter, 'emit'); + + // update mock so filter would emit change if it was listening to space1 + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + // no filter changed event + expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); + }); + + it('removes listener when destroy is called', async () => { + const filter = initFilter(space1); + filter.destroy(); + jest.runOnlyPendingTimers(); + const emitSpy = jest.spyOn(filter, 'emit'); + + // update mock so filter would emit change if it was listening to space1 + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + // no filter changed event + expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); + }); + + describe('when spaceFilteredRoomIds change', () => { + beforeEach(() => { + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + }) + const filterChangedCases = [ + ['room added', [room1Id, room2Id, room3Id]], + ['room removed', [room1Id]], + ['room swapped', [room1Id, room3Id]], // same number of rooms with changes + ] + + it.each(filterChangedCases)('%s', (_d, rooms) => { + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set(rooms)); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + }); + + describe('when directChildRoomIds change', () => { + beforeEach(() => { + SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + }) + const filterChangedCases = [ + ['room added', [room1Id, room2Id, room3Id]], + ['room removed', [room1Id]], + ['room swapped', [room1Id, room3Id]], // same number of rooms with changes + ] + + it.each(filterChangedCases)('%s', (_d, rooms) => { + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set(rooms)); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + }); + + describe('when directChildRoomIds change', () => { + beforeEach(() => { + SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set([user1Id, user2Id])); + }) + const filterChangedCases = [ + ['user added', [user1Id, user2Id, user3Id]], + ['user removed', [user1Id]], + ['user swapped', [user1Id, user3Id]], // same number of rooms with changes + ] + + it.each(filterChangedCases)('%s', (_d, rooms) => { + const filter = initFilter(space1); + const emitSpy = jest.spyOn(filter, 'emit'); + + SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set(rooms)); + SpaceStore.instance.emit(space1); + jest.runOnlyPendingTimers(); + expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); + }); + }); + }); + +}); \ No newline at end of file From e038df5d72b93a38f3d51a5e91df6d411fa59805 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 17:33:05 +0100 Subject: [PATCH 05/14] lint Signed-off-by: Kerry Archibald --- test/stores/SpaceStore-test.ts | 12 +-- .../filters/SpaceFilterCondition-test.ts | 94 ++++++++++--------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 7ad60f5089b..f53d64c2f0d 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -388,8 +388,6 @@ describe("SpaceStore", () => { }); describe('isRoomInSpace()', () => { - - it("home space contains orphaned rooms", () => { expect(store.isRoomInSpace(MetaSpace.Home, orphan1)).toBeTruthy(); expect(store.isRoomInSpace(MetaSpace.Home, orphan2)).toBeTruthy(); @@ -415,10 +413,12 @@ describe("SpaceStore", () => { expect(store.isRoomInSpace(MetaSpace.Home, invite2)).toBeTruthy(); }); - it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => { - await setShowAllRooms(true); - expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeTruthy(); - }); + it( + "all rooms space does contain rooms/low priority even if they are also shown in a space", + async () => { + await setShowAllRooms(true); + expect(store.isRoomInSpace(MetaSpace.Home, room1)).toBeTruthy(); + }); it("favourites space does contain favourites even if they are also shown in a space", async () => { expect(store.isRoomInSpace(MetaSpace.Favourites, fav1)).toBeTruthy(); diff --git a/test/stores/room-list/filters/SpaceFilterCondition-test.ts b/test/stores/room-list/filters/SpaceFilterCondition-test.ts index af38edb1723..40b0dca743e 100644 --- a/test/stores/room-list/filters/SpaceFilterCondition-test.ts +++ b/test/stores/room-list/filters/SpaceFilterCondition-test.ts @@ -1,3 +1,5 @@ +import { mocked } from 'ts-jest/utils'; + import SettingsStore from "../../../../src/settings/SettingsStore"; import { FILTER_CHANGED } from "../../../../src/stores/room-list/filters/IFilterCondition"; import { SpaceFilterCondition } from "../../../../src/stores/room-list/filters/SpaceFilterCondition"; @@ -12,15 +14,17 @@ jest.mock("../../../../src/stores/spaces/SpaceStore", () => { isRoomInSpace = jest.fn(); getSpaceFilteredRoomIds = jest.fn().mockReturnValue(new Set([])); getSpaceFilteredUserIds = jest.fn().mockReturnValue(new Set([])); - getSpaceFilteredDirectChildRoomIds = jest.fn().mockReturnValue(new Set([])) + getSpaceFilteredDirectChildRoomIds = jest.fn().mockReturnValue(new Set([])); } - return { instance: new MockSpaceStore() } + return { instance: new MockSpaceStore() }; }); +const SettingsStoreMock = mocked(SettingsStore); +const SpaceStoreInstanceMock = mocked(SpaceStore.instance); + jest.useFakeTimers(); describe('SpaceFilterCondition', () => { - const space1 = '!space1:server'; const space2 = '!space2:server'; const room1Id = '!r1:server'; @@ -33,11 +37,11 @@ describe('SpaceFilterCondition', () => { beforeEach(() => { jest.resetAllMocks(); - (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue()); - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([])); - SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set([])); - SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([])); - SpaceStore.instance.isRoomInSpace.mockReturnValue(true); + SettingsStoreMock.getValue.mockClear().mockImplementation(makeMockGetValue()); + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set([])); + SpaceStoreInstanceMock.getSpaceFilteredUserIds.mockReturnValue(new Set([])); + SpaceStoreInstanceMock.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([])); + SpaceStoreInstanceMock.isRoomInSpace.mockReturnValue(true); }); const initFilter = (space): SpaceFilterCondition => { @@ -45,31 +49,30 @@ describe('SpaceFilterCondition', () => { filter.updateSpace(space); jest.runOnlyPendingTimers(); return filter; - } + }; describe('isVisible', () => { const room1 = { roomId: room1Id } as unknown as Room; it('calls isRoomInSpace correctly when showSubSpaceRoomsInSpace is truthy', () => { - (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ - ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true } + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true }, })); const filter = initFilter(space1); - expect(filter.isVisible(room1)).toEqual(true); - expect(SpaceStore.instance.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, true); + expect(SpaceStoreInstanceMock.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, true); }); it('calls isRoomInSpace correctly when showSubSpaceRoomsInSpace is truthy', () => { - (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ - ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false } + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false }, })); const filter = initFilter(space1); expect(filter.isVisible(room1)).toEqual(true); - expect(SpaceStore.instance.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, false); + expect(SpaceStoreInstanceMock.isRoomInSpace).toHaveBeenCalledWith(space1, room1Id, false); }); }); @@ -84,17 +87,17 @@ describe('SpaceFilterCondition', () => { it('emits filter changed event when Spaces.includeSubSpaceRoomsInRoomList setting changes', async () => { // init filter with setting true for space1 - (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true }, })); const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false }, })); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); @@ -102,24 +105,24 @@ describe('SpaceFilterCondition', () => { describe('showPeopleInSpace setting', () => { it('emits filter changed event when setting changes', async () => { // init filter with setting true for space1 - (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ ["Spaces.showPeopleInSpace"]: { [space1]: true }, })); const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - (SettingsStore.getValue as jest.Mock).mockClear().mockImplementation(makeMockGetValue({ + SettingsStoreMock.getValue.mockClear().mockImplementation(makeMockGetValue({ ["Spaces.showPeopleInSpace"]: { [space1]: false }, })); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); it('emits filter changed event when setting is false and space changes to a meta space', async () => { // init filter with setting true for space1 - (SettingsStore.getValue as jest.Mock).mockImplementation(makeMockGetValue({ + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ ["Spaces.showPeopleInSpace"]: { [space1]: false }, })); const filter = initFilter(space1); @@ -134,7 +137,7 @@ describe('SpaceFilterCondition', () => { it('does not emit filter changed event on store update when nothing changed', async () => { const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); }); @@ -146,8 +149,8 @@ describe('SpaceFilterCondition', () => { const emitSpy = jest.spyOn(filter, 'emit'); // update mock so filter would emit change if it was listening to space1 - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); // no filter changed event expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); @@ -160,8 +163,8 @@ describe('SpaceFilterCondition', () => { const emitSpy = jest.spyOn(filter, 'emit'); // update mock so filter would emit change if it was listening to space1 - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id])); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); // no filter changed event expect(emitSpy).not.toHaveBeenCalledWith(FILTER_CHANGED); @@ -169,20 +172,20 @@ describe('SpaceFilterCondition', () => { describe('when spaceFilteredRoomIds change', () => { beforeEach(() => { - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); - }) + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + }); const filterChangedCases = [ ['room added', [room1Id, room2Id, room3Id]], ['room removed', [room1Id]], ['room swapped', [room1Id, room3Id]], // same number of rooms with changes - ] + ]; it.each(filterChangedCases)('%s', (_d, rooms) => { const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set(rooms)); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set(rooms)); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); @@ -190,21 +193,21 @@ describe('SpaceFilterCondition', () => { describe('when directChildRoomIds change', () => { beforeEach(() => { - SpaceStore.instance.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); - SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([room1Id, room2Id])); - }) + SpaceStoreInstanceMock.getSpaceFilteredRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + SpaceStoreInstanceMock.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set([room1Id, room2Id])); + }); const filterChangedCases = [ ['room added', [room1Id, room2Id, room3Id]], ['room removed', [room1Id]], ['room swapped', [room1Id, room3Id]], // same number of rooms with changes - ] + ]; it.each(filterChangedCases)('%s', (_d, rooms) => { const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - SpaceStore.instance.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set(rooms)); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.getSpaceFilteredDirectChildRoomIds.mockReturnValue(new Set(rooms)); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); @@ -212,24 +215,23 @@ describe('SpaceFilterCondition', () => { describe('when directChildRoomIds change', () => { beforeEach(() => { - SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set([user1Id, user2Id])); - }) + SpaceStoreInstanceMock.getSpaceFilteredUserIds.mockReturnValue(new Set([user1Id, user2Id])); + }); const filterChangedCases = [ ['user added', [user1Id, user2Id, user3Id]], ['user removed', [user1Id]], ['user swapped', [user1Id, user3Id]], // same number of rooms with changes - ] + ]; it.each(filterChangedCases)('%s', (_d, rooms) => { const filter = initFilter(space1); const emitSpy = jest.spyOn(filter, 'emit'); - SpaceStore.instance.getSpaceFilteredUserIds.mockReturnValue(new Set(rooms)); - SpaceStore.instance.emit(space1); + SpaceStoreInstanceMock.getSpaceFilteredUserIds.mockReturnValue(new Set(rooms)); + SpaceStoreInstanceMock.emit(space1); jest.runOnlyPendingTimers(); expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); }); }); - -}); \ No newline at end of file +}); From 307d12c7f5f9399c0d0bf28dbcacc738dafe48c9 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 31 Jan 2022 17:47:03 +0100 Subject: [PATCH 06/14] forgotten i18n Signed-off-by: Kerry Archibald --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bff54532f9d..3ca33a7d4f2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -948,6 +948,7 @@ "Show chat effects (animations when receiving e.g. confetti)": "Show chat effects (animations when receiving e.g. confetti)", "Show all rooms in Home": "Show all rooms in Home", "All rooms you're in will appear in Home.": "All rooms you're in will appear in Home.", + "Include all sub-space rooms in Space room list": "Include all sub-space rooms in Space room list", "Display Communities instead of Spaces": "Display Communities instead of Spaces", "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.", "Developer mode": "Developer mode", From 4bd1fd951a53c263c476f5be63354fc365d41a28 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Feb 2022 11:42:39 +0100 Subject: [PATCH 07/14] filter rooms list by showSubSpaceRoomsInSpace Signed-off-by: Kerry Archibald --- .../room-list/filters/SpaceFilterCondition.ts | 12 +++++----- .../filters/SpaceFilterCondition-test.ts | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index f5825fdba67..cb6f4b2e578 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -52,14 +52,15 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi const beforeRoomIds = this.roomIds; // clone the set as it may be mutated by the space store internally - this.roomIds = new Set(SpaceStore.instance.getSpaceFilteredRoomIds(this.space)); - const beforeDirectChildRoomIds = this.roomIds; - // clone the set as it may be mutated by the space store internally - this.directChildRoomIds = new Set(SpaceStore.instance.getSpaceFilteredDirectChildRoomIds(this.space)); + this.roomIds = new Set( + SpaceStore.instance.getSpaceFilteredRoomIds(this.space, this.showSubSpaceRoomsInSpace, true), + ); const beforeUserIds = this.userIds; // clone the set as it may be mutated by the space store internally - this.userIds = new Set(SpaceStore.instance.getSpaceFilteredUserIds(this.space)); + this.userIds = new Set( + SpaceStore.instance.getSpaceFilteredUserIds(this.space, this.showSubSpaceRoomsInSpace, true), + ); const beforeShowPeopleInSpace = this.showPeopleInSpace; this.showPeopleInSpace = isMetaSpace(this.space[0]) || @@ -69,7 +70,6 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi beforeShowPeopleInSpace !== this.showPeopleInSpace || beforeShowSubSpaceRoomsInSpace !== this.showSubSpaceRoomsInSpace || setHasDiff(beforeRoomIds, this.roomIds) || - setHasDiff(beforeDirectChildRoomIds, this.directChildRoomIds) || setHasDiff(beforeUserIds, this.userIds) ) { this.emit(FILTER_CHANGED); diff --git a/test/stores/room-list/filters/SpaceFilterCondition-test.ts b/test/stores/room-list/filters/SpaceFilterCondition-test.ts index b7e6f3524e1..81b4e4c52ae 100644 --- a/test/stores/room-list/filters/SpaceFilterCondition-test.ts +++ b/test/stores/room-list/filters/SpaceFilterCondition-test.ts @@ -99,6 +99,28 @@ describe('SpaceFilterCondition', () => { expect(emitSpy).toHaveBeenCalledWith(FILTER_CHANGED); }); + it('compares sub space rooms and dms when Spaces.includeSubSpaceRoomsInRoomList enabled', async () => { + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: true }, + })); + const filter = new SpaceFilterCondition(); + filter.updateSpace(space1); + jest.runOnlyPendingTimers(); + expect(SpaceStoreInstanceMock.getSpaceFilteredRoomIds).toHaveBeenCalledWith(space1, true, true); + expect(SpaceStoreInstanceMock.getSpaceFilteredUserIds).toHaveBeenCalledWith(space1, true, true); + }); + + it('compares only direct child rooms and dms when Spaces.includeSubSpaceRoomsInRoomList disabled', async () => { + SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ + ["Spaces.includeSubSpaceRoomsInRoomList"]: { [space1]: false }, + })); + const filter = new SpaceFilterCondition(); + filter.updateSpace(space1); + jest.runOnlyPendingTimers(); + expect(SpaceStoreInstanceMock.getSpaceFilteredRoomIds).toHaveBeenCalledWith(space1, false, true); + expect(SpaceStoreInstanceMock.getSpaceFilteredUserIds).toHaveBeenCalledWith(space1, false, true); + }); + it('emits filter changed event when Spaces.includeSubSpaceRoomsInRoomList setting changes', async () => { // init filter with setting true for space1 SettingsStoreMock.getValue.mockImplementation(makeMockGetValue({ From b8ec5657a35eba410d87f42fac5ac43dc613d1fa Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Feb 2022 11:55:03 +0100 Subject: [PATCH 08/14] clunky copy update Signed-off-by: Kerry Archibald --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 8450803b02f..f8bd87c6433 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -867,7 +867,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", null), }, "Spaces.includeSubSpaceRoomsInRoomList": { - displayName: _td("Include all sub-space rooms in Space room list"), + displayName: _td("Display all people and rooms from nested spaces in the room list for a space"), supportedLevels: LEVELS_ROOM_SETTINGS, default: true, }, From 3722f8395f3e1bb486623ed56e2a0fafa81f4570 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Feb 2022 11:56:03 +0100 Subject: [PATCH 09/14] missed i18n Signed-off-by: Kerry Archibald --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 55fd610b20f..cf0846b7e02 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -955,7 +955,7 @@ "Show chat effects (animations when receiving e.g. confetti)": "Show chat effects (animations when receiving e.g. confetti)", "Show all rooms in Home": "Show all rooms in Home", "All rooms you're in will appear in Home.": "All rooms you're in will appear in Home.", - "Include all sub-space rooms in Space room list": "Include all sub-space rooms in Space room list", + "Display all people and rooms from nested spaces in the room list for a space": "Display all people and rooms from nested spaces in the room list for a space", "Display Communities instead of Spaces": "Display Communities instead of Spaces", "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.", "Developer mode": "Developer mode", From d95e7e519570618b03662036e4a3095e6be86385 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Feb 2022 11:57:07 +0100 Subject: [PATCH 10/14] unused prop Signed-off-by: Kerry Archibald --- src/stores/room-list/filters/SpaceFilterCondition.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index cb6f4b2e578..f79d8d1fb59 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -32,7 +32,6 @@ import SettingsStore from "../../../settings/SettingsStore"; */ export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { private roomIds = new Set(); - private directChildRoomIds = new Set(); private userIds = new Set(); private showPeopleInSpace = true; private showSubSpaceRoomsInSpace = true; From cb1ab6034f8c50cf542bfeb8a523a00e375708cc Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 27 Jun 2022 20:37:19 +0200 Subject: [PATCH 11/14] make includeSubSpaceRoomsInRoomList an account level setting --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 296a2e031fe..e9ca496838e 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -937,7 +937,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "Spaces.includeSubSpaceRoomsInRoomList": { displayName: _td("Display all people and rooms from nested spaces in the room list for a space"), - supportedLevels: LEVELS_ROOM_SETTINGS, + supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: true, }, "developerMode": { From 65d712de75887117bc163f391ed9f8f4bc5bd78f Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 27 Jun 2022 20:37:56 +0200 Subject: [PATCH 12/14] add includeSubSpaceRoomsInRoomList setting to user prefs --- .../views/settings/tabs/user/PreferencesUserSettingsTab.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 2b67d22c0df..3941d28d3d4 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -46,6 +46,7 @@ export default class PreferencesUserSettingsTab extends React.Component Date: Mon, 27 Jun 2022 21:15:19 +0200 Subject: [PATCH 13/14] handle includeSubSpaceRoomsInRoomList in switchSpaceIfNeeded --- src/stores/spaces/SpaceStore.ts | 19 +++- test/stores/SpaceStore-test.ts | 153 +++++++++++++++++++++++--------- 2 files changed, 126 insertions(+), 46 deletions(-) diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 443083efc43..b04ee78d46a 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -815,15 +815,30 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // try to find the canonical parent first let parent: SpaceKey = this.getCanonicalParent(roomId)?.roomId; + const includeSubSpaceRoomsInRoomList = SettingsStore.getValue("Spaces.includeSubSpaceRoomsInRoomList"); + console.log('PPPP', roomId, parent, includeSubSpaceRoomsInRoomList); + + // otherwise, if subspaces rooms are not aggregated + // use the first known direct parent of the room + if (!parent && !includeSubSpaceRoomsInRoomList) { + const parents = this.getKnownParents(roomId); + + // use the first known parent + parent = parents.values().next().value; + } + // otherwise, try to find a root space which contains this room if (!parent) { - parent = this.rootSpaces.find(s => this.isRoomInSpace(s.roomId, roomId))?.roomId; + parent = this.rootSpaces.find(s => + this.isRoomInSpace(s.roomId, roomId, includeSubSpaceRoomsInRoomList))?.roomId; } // otherwise, try to find a metaspace which contains this room if (!parent) { // search meta spaces in reverse as Home is the first and least specific one - parent = [...this.enabledMetaSpaces].reverse().find(s => this.isRoomInSpace(s, roomId)); + parent = [...this.enabledMetaSpaces].reverse().find(s => + this.isRoomInSpace(s, roomId, includeSubSpaceRoomsInRoomList), + ); } // don't trigger a context switch when we are switching a space to match the chosen room diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index d83bc21f224..7ab037ff786 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -1000,10 +1000,12 @@ describe("SpaceStore", () => { }); describe("space auto switching tests", () => { - beforeEach(async () => { - [room1, room2, room3, orphan1].forEach(mkRoom); + const setupStore = async () => { + [room1, room2, room3, room4, orphan1].forEach(mkRoom); + mkSpace(space4, [room1]); + mkSpace(space3, [room4, space4]); mkSpace(space1, [room1, room2, room3]); - mkSpace(space2, [room1, room2]); + mkSpace(space2, [room1, room2, space3]); const cliRoom2 = client.getRoom(room2); mocked(cliRoom2.currentState).getStateEvents.mockImplementation(testUtils.mockStateEventImplementation([ @@ -1018,55 +1020,118 @@ describe("SpaceStore", () => { }), ])); await run(); - }); + }; - it("no switch required, room is in current space", async () => { - viewRoom(room1); - store.setActiveSpace(space1, false); - viewRoom(room2); - expect(store.activeSpace).toBe(space1); - }); + describe('when includeSubSpaceRoomsInRoomList is true', () => { + beforeEach(async () => { + await setupStore(); + }); - it("switch to canonical parent space for room", async () => { - viewRoom(room1); - store.setActiveSpace(space2, false); - viewRoom(room2); - expect(store.activeSpace).toBe(space2); - }); + it("no switch required, room is in current space", async () => { + viewRoom(room1); + store.setActiveSpace(space1, false); + viewRoom(room2); + expect(store.activeSpace).toBe(space1); + }); - it("switch to first containing space for room", async () => { - viewRoom(room2); - store.setActiveSpace(space2, false); - viewRoom(room3); - expect(store.activeSpace).toBe(space1); - }); + it("switch to canonical parent space for room", async () => { + viewRoom(room1); + store.setActiveSpace(space2, false); + viewRoom(room2); + expect(store.activeSpace).toBe(space2); + }); - it("switch to other rooms for orphaned room", async () => { - viewRoom(room1); - store.setActiveSpace(space1, false); - viewRoom(orphan1); - expect(store.activeSpace).toBe(MetaSpace.Orphans); - }); + it("switch to first containing space for room", async () => { + viewRoom(room2); + store.setActiveSpace(space2, false); + viewRoom(room3); + expect(store.activeSpace).toBe(space1); + }); - it("switch to first valid space when selected metaspace is disabled", async () => { - store.setActiveSpace(MetaSpace.People, false); - expect(store.activeSpace).toBe(MetaSpace.People); - await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { - [MetaSpace.Home]: false, - [MetaSpace.Favourites]: true, - [MetaSpace.People]: false, - [MetaSpace.Orphans]: true, + it("switches to first root space containing the room", async () => { + viewRoom(room2); + store.setActiveSpace(space1, false); + // space2 > space3 > room4 + viewRoom(room4); + expect(store.activeSpace).toBe(space2); + }); + + it("switch to other rooms for orphaned room", async () => { + viewRoom(room1); + store.setActiveSpace(space1, false); + viewRoom(orphan1); + expect(store.activeSpace).toBe(MetaSpace.Orphans); + }); + + it("switch to first valid space when selected metaspace is disabled", async () => { + store.setActiveSpace(MetaSpace.People, false); + expect(store.activeSpace).toBe(MetaSpace.People); + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: false, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: false, + [MetaSpace.Orphans]: true, + }); + jest.runAllTimers(); + expect(store.activeSpace).toBe(MetaSpace.Orphans); + }); + + it("when switching rooms in the all rooms home space don't switch to related space", async () => { + await setShowAllRooms(true); + viewRoom(room2); + store.setActiveSpace(MetaSpace.Home, false); + viewRoom(room1); + expect(store.activeSpace).toBe(MetaSpace.Home); }); - jest.runAllTimers(); - expect(store.activeSpace).toBe(MetaSpace.Orphans); }); - it("when switching rooms in the all rooms home space don't switch to related space", async () => { - await setShowAllRooms(true); - viewRoom(room2); - store.setActiveSpace(MetaSpace.Home, false); - viewRoom(room1); - expect(store.activeSpace).toBe(MetaSpace.Home); + describe('when includeSubSpaceRoomsInRoomList is false', () => { + beforeAll(() => { + SettingsStore.setValue( + 'Spaces.includeSubSpaceRoomsInRoomList', undefined, SettingLevel.ACCOUNT, false, + ); + }); + + afterAll(() => { + SettingsStore.setValue( + 'Spaces.includeSubSpaceRoomsInRoomList', undefined, SettingLevel.ACCOUNT, true, + ); + }); + + beforeEach(async () => { + await setupStore(); + }); + + it("no switch required, room is in current space", async () => { + expect(SettingsStore.getValue('Spaces.includeSubSpaceRoomsInRoomList')).toEqual(false); + viewRoom(room1); + store.setActiveSpace(space1, false); + viewRoom(room2); + expect(store.activeSpace).toBe(space1); + }); + + it("switches to canonical parent space for room when viewing room", async () => { + viewRoom(room1); + store.setActiveSpace(space2, false); + viewRoom(room2); + expect(store.activeSpace).toBe(space2); + }); + + it("switches to first parent space for room when viewing room", async () => { + // differs from behaviour when includeSubSpaceRoomsInRoomList is truthy + viewRoom(room2); + store.setActiveSpace(space1, false); + // space2 > space3 > room4 + viewRoom(room4); + expect(store.activeSpace).toBe(space3); + }); + + it("switch to other rooms for orphaned room", async () => { + viewRoom(room1); + store.setActiveSpace(space1, false); + viewRoom(orphan1); + expect(store.activeSpace).toBe(MetaSpace.Orphans); + }); }); }); From a237932643e5c942ebb6711d7ab699987cf587a0 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 27 Jun 2022 21:24:14 +0200 Subject: [PATCH 14/14] add todo --- src/stores/spaces/SpaceStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index b04ee78d46a..2b64f4e2605 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -143,6 +143,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); SettingsStore.monitorSetting("Spaces.showPeopleInSpace", null); + // @TODO kerry handle switching spaces if needed when this changes? SettingsStore.monitorSetting("Spaces.includeSubSpaceRoomsInRoomList", null); }