diff --git a/spec/unit/sync-accumulator.spec.ts b/spec/unit/sync-accumulator.spec.ts index 1287cd98f2..d95c8d056b 100644 --- a/spec/unit/sync-accumulator.spec.ts +++ b/spec/unit/sync-accumulator.spec.ts @@ -16,7 +16,19 @@ limitations under the License. */ import { ReceiptType } from "../../src/@types/read_receipts"; -import { IJoinedRoom, IKnockedRoom, IStrippedState, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator"; +import { + IJoinedRoom, + IInvitedRoom, + IKnockedRoom, + IKnockState, + ILeftRoom, + IRoomEvent, + IStateEvent, + IStrippedState, + ISyncResponse, + SyncAccumulator, + IInviteState, +} from "../../src/sync-accumulator"; import { IRoomSummary } from "../../src"; import * as utils from "../test-utils/test-utils"; @@ -295,12 +307,71 @@ describe("SyncAccumulator", function () { expect(sa.getJSON().accountData[0]).toEqual(acc2); }); + it("should delete invite room when invite request is rejected", () => { + const initInviteState: IInviteState = { + events: [ + { + content: { + membership: "invite", + }, + state_key: "bob", + sender: "alice", + type: "m.room.member", + }, + ], + }; + + sa.accumulate( + syncSkeleton( + {}, + { + "!invite:bar": { + invite_state: initInviteState, + }, + }, + ), + ); + expect(sa.getJSON().roomsData.invite["!invite:bar"].invite_state).toBe(initInviteState); + + const rejectMemberEvent: IStateEvent = { + event_id: "$" + Math.random(), + content: { + membership: "leave", + }, + origin_server_ts: 123456789, + state_key: "bob", + sender: "bob", + type: "m.room.member", + unsigned: { + prev_content: { + membership: "invite", + }, + }, + }; + const leftRoomState = leftRoomSkeleton([rejectMemberEvent]); + + // bob rejects invite + sa.accumulate( + syncSkeleton( + {}, + {}, + { + "!invite:bar": leftRoomState, + }, + ), + ); + + expect(sa.getJSON().roomsData.invite["!invite:bar"]).toBeUndefined(); + }); + it("should accumulate knock state", () => { const initKnockState = { events: [member("alice", "knock")], }; sa.accumulate( syncSkeleton( + {}, + {}, {}, { knock_state: initKnockState, @@ -311,6 +382,8 @@ describe("SyncAccumulator", function () { sa.accumulate( syncSkeleton( + {}, + {}, {}, { knock_state: { @@ -336,6 +409,8 @@ describe("SyncAccumulator", function () { sa.accumulate( syncSkeleton( + {}, + {}, {}, { knock_state: { @@ -360,6 +435,140 @@ describe("SyncAccumulator", function () { ).toEqual("Room 2"); }); + it("should delete knocked room when knock request is approved", () => { + const initKnockState = makeKnockState(); + sa.accumulate( + syncSkeleton( + {}, + {}, + {}, + { + knock_state: initKnockState, + }, + ), + ); + expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState); + + // alice approves bob's knock request + const inviteStateEvents = [ + { + content: { + membership: "invite", + }, + state_key: "bob", + sender: "alice", + type: "m.room.member", + }, + ]; + sa.accumulate( + syncSkeleton( + {}, + { + "!knock:bar": { + invite_state: { + events: inviteStateEvents, + }, + }, + }, + ), + ); + + expect(sa.getJSON().roomsData.knock["!knock:bar"]).toBeUndefined(); + expect(sa.getJSON().roomsData.invite["!knock:bar"].invite_state.events).toEqual(inviteStateEvents); + }); + + it("should delete knocked room when knock request is cancelled by user himself", () => { + // bob cancels his knock state + const memberEvent: IStateEvent = { + event_id: "$" + Math.random(), + content: { + membership: "leave", + }, + origin_server_ts: 123456789, + state_key: "bob", + sender: "bob", + type: "m.room.member", + unsigned: { + prev_content: { + membership: "knock", + }, + }, + }; + const leftRoomState = leftRoomSkeleton([memberEvent]); + + const initKnockState = makeKnockState(); + sa.accumulate( + syncSkeleton( + {}, + {}, + {}, + { + knock_state: initKnockState, + }, + ), + ); + expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState); + + // bob cancels his knock request + sa.accumulate( + syncSkeleton( + {}, + {}, + { + "!knock:bar": leftRoomState, + }, + ), + ); + + expect(sa.getJSON().roomsData.knock["!knock:bar"]).toBeUndefined(); + }); + + it("should delete knocked room when knock request is denied by another user", () => { + // alice denies bob knock state + const memberEvent: IStateEvent = { + event_id: "$" + Math.random(), + content: { + membership: "leave", + }, + origin_server_ts: 123456789, + state_key: "bob", + sender: "alice", + type: "m.room.member", + unsigned: { + prev_content: { + membership: "knock", + }, + }, + }; + const leftRoomState = leftRoomSkeleton([memberEvent]); + + const initKnockState = makeKnockState(); + sa.accumulate( + syncSkeleton( + {}, + {}, + {}, + { + knock_state: initKnockState, + }, + ), + ); + expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState); + + // alice denies bob's knock request + sa.accumulate( + syncSkeleton( + {}, + {}, + { + "!knock:bar": leftRoomState, + }, + ), + ); + + expect(sa.getJSON().roomsData.knock["!knock:bar"]).toBeUndefined(); + }); + it("should accumulate read receipts", () => { const receipt1 = { type: "m.receipt", @@ -674,7 +883,12 @@ describe("SyncAccumulator", function () { }); }); -function syncSkeleton(joinObj: Partial, knockObj?: Partial): ISyncResponse { +function syncSkeleton( + joinObj: Partial, + invite?: Record, + leave?: Record, + knockObj?: Partial, +): ISyncResponse { joinObj = joinObj || {}; return { next_batch: "abc", @@ -682,6 +896,8 @@ function syncSkeleton(joinObj: Partial, knockObj?: Partial, knockObj?: Partial = []): ILeftRoom { + return { + state: { + events: [], + }, + timeline: { + events: timelineEvents, + prev_batch: "something", + }, + account_data: { + events: [], + }, + }; +} + +function makeKnockState(): IKnockState { + return { + events: [ + utils.mkEvent({ + user: "alice", + room: "!knock:bar", + type: "m.room.name", + content: { + name: "Room", + }, + }) as IStrippedState, + member("bob", "knock"), + ], + }; +} + function msg(localpart: string, text: string) { return { event_id: "$" + Math.random(), diff --git a/src/sync-accumulator.ts b/src/sync-accumulator.ts index 5f4f452ec2..67d5e1d680 100644 --- a/src/sync-accumulator.ts +++ b/src/sync-accumulator.ts @@ -283,6 +283,10 @@ export class SyncAccumulator { // * equivalent to "no state" switch (category) { case Category.Invite: // (5) + if (this.knockRooms[roomId]) { + // was previously knock, now invite, need to delete knock state + delete this.knockRooms[roomId]; + } this.accumulateInviteState(roomId, data as IInvitedRoom); break; @@ -303,7 +307,10 @@ export class SyncAccumulator { break; case Category.Leave: - if (this.inviteRooms[roomId]) { + if (this.knockRooms[roomId]) { + // delete knock state on leave + delete this.knockRooms[roomId]; + } else if (this.inviteRooms[roomId]) { // (4) delete this.inviteRooms[roomId]; } else {