diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 34d84bddaed..f7dc6875492 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -168,6 +168,7 @@ describe("SlidingSyncSdk", () => { const roomD = "!d_with_notif_count:localhost"; const roomE = "!e_with_invite:localhost"; const roomF = "!f_calc_room_name:localhost"; + const roomG = "!g_join_invite_counts:localhost"; const data: Record = { [roomA]: { name: "A", @@ -261,12 +262,25 @@ describe("SlidingSyncSdk", () => { ], initial: true, }, + [roomG]: { + name: "G", + required_state: [], + timeline: [ + mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), + mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), + ], + joined_count: 5, + invited_count: 2, + initial: true, + }, }; it("can be created with required_state and timeline", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.name).toEqual(data[roomA].name); expect(gotRoom.getMyMembership()).toEqual("join"); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline); @@ -276,6 +290,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]); const gotRoom = client.getRoom(roomB); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.name).toEqual(data[roomB].name); expect(gotRoom.getMyMembership()).toEqual("join"); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline); @@ -285,6 +300,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]); const gotRoom = client.getRoom(roomC); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight), ).toEqual(data[roomC].highlight_count); @@ -294,15 +310,26 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]); const gotRoom = client.getRoom(roomD); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Total), ).toEqual(data[roomD].notification_count); }); + it("can be created with an invited/joined_count", () => { + mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]); + const gotRoom = client.getRoom(roomG); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } + expect(gotRoom.getInvitedMemberCount()).toEqual(data[roomG].invited_count); + expect(gotRoom.getJoinedMemberCount()).toEqual(data[roomG].joined_count); + }); + it("can be created with invite_state", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]); const gotRoom = client.getRoom(roomE); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getMyMembership()).toEqual("invite"); expect(gotRoom.currentState.getJoinRule()).toEqual(JoinRule.Invite); }); @@ -311,6 +338,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]); const gotRoom = client.getRoom(roomF); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.name, ).toEqual(data[roomF].name); @@ -326,6 +354,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } const newTimeline = data[roomA].timeline; newTimeline.push(newEvent); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-3), newTimeline); @@ -333,6 +362,8 @@ describe("SlidingSyncSdk", () => { it("can update with a new required_state event", async () => { let gotRoom = client.getRoom(roomB); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getJoinRule()).toEqual(JoinRule.Invite); // default mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, { required_state: [ @@ -343,6 +374,7 @@ describe("SlidingSyncSdk", () => { }); gotRoom = client.getRoom(roomB); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getJoinRule()).toEqual(JoinRule.Restricted); }); @@ -355,6 +387,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomC); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight), ).toEqual(1); @@ -369,11 +402,25 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomD); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Total), ).toEqual(1); }); + it("can update with a new joined_count", () => { + mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, { + name: data[roomD].name, + required_state: [], + timeline: [], + joined_count: 1, + }); + const gotRoom = client.getRoom(roomG); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } + expect(gotRoom.getJoinedMemberCount()).toEqual(1); + }); + // Regression test for a bug which caused the timeline entries to be out-of-order // when the same room appears twice with different timeline limits. E.g appears in // the list with timeline_limit:1 then appears again as a room subscription with @@ -394,6 +441,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } logger.log("want:", oldTimeline.map((e) => (e.type + " : " + (e.content || {}).body))); logger.log("got:", gotRoom.getLiveTimeline().getEvents().map( diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 99f33c9e5a1..21d2af63f0d 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -293,13 +293,16 @@ export class SlidingSyncSdk { this.processRoomData(this.client, room, roomData); } - private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err?: Error): void { + private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null): void { if (err) { logger.debug("onLifecycle", state, err); } switch (state) { case SlidingSyncState.Complete: this.purgeNotifications(); + if (!resp) { + break; + } // Element won't stop showing the initial loading spinner unless we fire SyncState.Prepared if (!this.lastPos) { this.updateSyncState(SyncState.Prepared, { @@ -472,6 +475,13 @@ export class SlidingSyncSdk { } } + if (Number.isInteger(roomData.invited_count)) { + room.currentState.setInvitedMemberCount(roomData.invited_count!); + } + if (Number.isInteger(roomData.joined_count)) { + room.currentState.setJoinedMemberCount(roomData.joined_count!); + } + if (roomData.invite_state) { const inviteStateEvents = mapEvents(this.client, room.roomId, roomData.invite_state); this.processRoomEvents(room, inviteStateEvents); diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index 5b5ec6a65d6..da6419c9676 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -84,6 +84,8 @@ export interface MSC3575RoomData { timeline: (IRoomEvent | IStateEvent)[]; notification_count?: number; highlight_count?: number; + joined_count?: number; + invited_count?: number; invite_state?: IStateEvent[]; initial?: boolean; limited?: boolean; @@ -320,7 +322,9 @@ export enum SlidingSyncEvent { export type SlidingSyncEventHandlerMap = { [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void; - [SlidingSyncEvent.Lifecycle]: (state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err: Error) => void; + [SlidingSyncEvent.Lifecycle]: ( + state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null, + ) => void; [SlidingSyncEvent.List]: ( listIndex: number, joinedCount: number, roomIndexToRoomId: Record, ) => void;