From d4796b94bb7bacab0967f9679e5d07c01fe40d86 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 29 Mar 2022 15:10:04 +0200 Subject: [PATCH 1/4] remove beacons on membership changes --- src/stores/OwnBeaconStore.ts | 169 +++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 57 deletions(-) diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index 6f7bec93dd2..b60182f3847 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -20,6 +20,9 @@ import { BeaconEvent, MatrixEvent, Room, + RoomMember, + RoomState, + RoomStateEvent, } from "matrix-js-sdk/src/matrix"; import { BeaconInfoState, makeBeaconContent, makeBeaconInfoContent, @@ -102,6 +105,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { protected async onReady(): Promise { this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness); this.matrixClient.on(BeaconEvent.New, this.onNewBeacon); + this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers); this.initialiseBeaconState(); } @@ -136,6 +140,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient { return await this.updateBeaconEvent(beacon, { live: false }); }; + /** + * Listeners + */ + private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => { if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { return; @@ -160,6 +168,30 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.emit(OwnBeaconStoreEvent.LivenessChange, this.getLiveBeaconIds()); }; + /** + * Check for changes in membership in rooms with beacons + * and stop monitoring beacons in rooms user is no longer member of + */ + private onRoomStateMembers = (_event: MatrixEvent, roomState: RoomState, member: RoomMember): void => { + // no beacons for this room, ignore + if (!this.beaconsByRoomId.has(roomState.roomId)) { + return; + } + + // TODO check powerlevels here + // in PSF-797 + + // stop watching beacons in rooms where user is no longer a member + if (member.membership === 'leave' || member.membership === 'ban') { + this.beaconsByRoomId.get(roomState.roomId).forEach(this.removeBeacon); + this.beaconsByRoomId.delete(roomState.roomId); + } + }; + + /** + * State management + */ + private initialiseBeaconState = () => { const userId = this.matrixClient.getUserId(); const visibleRooms = this.matrixClient.getVisibleRooms(); @@ -187,6 +219,21 @@ export class OwnBeaconStore extends AsyncStoreWithClient { beacon.monitorLiveness(); }; + /** + * Remove listeners for a given beacon + * remove from state + * and update liveness if changed + */ + private removeBeacon = (beaconId: string): void => { + if (!this.beacons.has(beaconId)) { + return; + } + this.beacons.get(beaconId).destroy(); + this.beacons.delete(beaconId); + + this.checkLiveness(); + }; + private checkLiveness = (): void => { const prevLiveBeaconIds = this.getLiveBeaconIds(); this.liveBeaconIds = [...this.beacons.values()] @@ -218,20 +265,9 @@ export class OwnBeaconStore extends AsyncStoreWithClient { } }; - private updateBeaconEvent = async (beacon: Beacon, update: Partial): Promise => { - const { description, timeout, timestamp, live, assetType } = { - ...beacon.beaconInfo, - ...update, - }; - - const updateContent = makeBeaconInfoContent(timeout, - live, - description, - assetType, - timestamp); - - await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent); - }; + /** + * Geolocation + */ private togglePollingLocation = () => { if (!!this.liveBeaconIds.length) { @@ -270,17 +306,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.emit(OwnBeaconStoreEvent.MonitoringLivePosition); }; - private onWatchedPosition = (position: GeolocationPosition) => { - const timedGeoPosition = mapGeolocationPositionToTimedGeo(position); - - // if this is our first position, publish immediateley - if (!this.lastPublishedPositionTimestamp) { - this.publishLocationToBeacons(timedGeoPosition); - } else { - this.debouncedPublishLocationToBeacons(timedGeoPosition); - } - }; - private stopPollingLocation = () => { clearInterval(this.locationInterval); this.locationInterval = undefined; @@ -295,26 +320,34 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.emit(OwnBeaconStoreEvent.MonitoringLivePosition); }; - /** - * Sends m.location events to all live beacons - * Sets last published beacon - */ - private publishLocationToBeacons = async (position: TimedGeoUri) => { - this.lastPublishedPositionTimestamp = Date.now(); - // TODO handle failure in individual beacon without rejecting rest - await Promise.all(this.liveBeaconIds.map(beaconId => - this.sendLocationToBeacon(this.beacons.get(beaconId), position)), - ); + private onWatchedPosition = (position: GeolocationPosition) => { + const timedGeoPosition = mapGeolocationPositionToTimedGeo(position); + + // if this is our first position, publish immediateley + if (!this.lastPublishedPositionTimestamp) { + this.publishLocationToBeacons(timedGeoPosition); + } else { + this.debouncedPublishLocationToBeacons(timedGeoPosition); + } }; - private debouncedPublishLocationToBeacons = debounce(this.publishLocationToBeacons, MOVING_UPDATE_INTERVAL); + private onGeolocationError = async (error: GeolocationError): Promise => { + this.geolocationError = error; + logger.error('Geolocation failed', this.geolocationError); - /** - * Sends m.location event to referencing given beacon - */ - private sendLocationToBeacon = async (beacon: Beacon, { geoUri, timestamp }: TimedGeoUri) => { - const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId); - await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content); + // other errors are considered non-fatal + // and self recovering + if (![ + GeolocationError.Unavailable, + GeolocationError.PermissionDenied, + ].includes(error)) { + return; + } + + this.stopPollingLocation(); + // kill live beacons when location permissions are revoked + // TODO may need adjustment when PSF-797 is done + await Promise.all(this.liveBeaconIds.map(this.stopBeacon)); }; /** @@ -332,22 +365,44 @@ export class OwnBeaconStore extends AsyncStoreWithClient { } }; - private onGeolocationError = async (error: GeolocationError): Promise => { - this.geolocationError = error; - logger.error('Geolocation failed', this.geolocationError); + /** + * MatrixClient api + */ - // other errors are considered non-fatal - // and self recovering - if (![ - GeolocationError.Unavailable, - GeolocationError.PermissionDenied, - ].includes(error)) { - return; - } + private updateBeaconEvent = async (beacon: Beacon, update: Partial): Promise => { + const { description, timeout, timestamp, live, assetType } = { + ...beacon.beaconInfo, + ...update, + }; - this.stopPollingLocation(); - // kill live beacons when location permissions are revoked - // TODO may need adjustment when PSF-797 is done - await Promise.all(this.liveBeaconIds.map(this.stopBeacon)); + const updateContent = makeBeaconInfoContent(timeout, + live, + description, + assetType, + timestamp); + + await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, beacon.beaconInfoEventType, updateContent); + }; + + /** + * Sends m.location events to all live beacons + * Sets last published beacon + */ + private publishLocationToBeacons = async (position: TimedGeoUri) => { + this.lastPublishedPositionTimestamp = Date.now(); + // TODO handle failure in individual beacon without rejecting rest + await Promise.all(this.liveBeaconIds.map(beaconId => + this.sendLocationToBeacon(this.beacons.get(beaconId), position)), + ); + }; + + private debouncedPublishLocationToBeacons = debounce(this.publishLocationToBeacons, MOVING_UPDATE_INTERVAL); + + /** + * Sends m.location event to referencing given beacon + */ + private sendLocationToBeacon = async (beacon: Beacon, { geoUri, timestamp }: TimedGeoUri) => { + const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId); + await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content); }; } From b46817ecb86cf2d6b88ea00bd3ab04e434a5ae1f Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 29 Mar 2022 16:54:48 +0200 Subject: [PATCH 2/4] add addMembershipToMockedRoom test util Signed-off-by: Kerry Archibald --- test/stores/SpaceStore-test.ts | 24 ++---------- test/test-utils/index.ts | 1 + test/test-utils/room.ts | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 test/test-utils/room.ts diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 60faa55804b..48a5d9f968c 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -38,6 +38,7 @@ import SettingsStore from "../../src/settings/SettingsStore"; import { SettingLevel } from "../../src/settings/SettingLevel"; import { Action } from "../../src/dispatcher/actions"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; +import { addMembershipToMockedRoom } from '../test-utils/room'; jest.useFakeTimers(); @@ -718,25 +719,6 @@ describe("SpaceStore", () => { client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, undefined); }; - const addMember = (spaceId, user: RoomMember) => { - const memberEvent = mkEvent({ - event: true, - type: EventType.RoomMember, - room: spaceId, - user: client.getUserId(), - skey: user.userId, - content: { membership: 'join' }, - ts: Date.now(), - }); - const spaceRoom = client.getRoom(spaceId); - mocked(spaceRoom.currentState).getStateEvents.mockImplementation( - testUtils.mockStateEventImplementation([memberEvent]), - ); - mocked(spaceRoom).getMember.mockReturnValue(user); - - client.emit(RoomStateEvent.Members, memberEvent, spaceRoom.currentState, user); - }; - it('emits events for parent spaces when child room is added', async () => { await run(); @@ -775,7 +757,7 @@ describe("SpaceStore", () => { const emitSpy = jest.spyOn(store, 'emit').mockClear(); // add into space2 - addMember(space2, dm1Partner); + addMembershipToMockedRoom(space2, dm1Partner.userId, client); expect(emitSpy).toHaveBeenCalledWith(space2); // space2 is subspace of space4 @@ -791,7 +773,7 @@ describe("SpaceStore", () => { expect(store.getSpaceFilteredUserIds(space2)).toEqual(new Set([])); // add into space2 - addMember(space2, dm1Partner); + addMembershipToMockedRoom(space2, dm1Partner.userId, client); expect(store.getSpaceFilteredUserIds(space2)).toEqual(new Set([dm1Partner.userId])); expect(store.getSpaceFilteredUserIds(space4)).toEqual(new Set([dm1Partner.userId])); diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts index 49abc51598c..b14bda3cbb6 100644 --- a/test/test-utils/index.ts +++ b/test/test-utils/index.ts @@ -2,6 +2,7 @@ export * from './beacon'; export * from './client'; export * from './location'; export * from './platform'; +export * from './room'; export * from './test-utils'; // TODO @@TR: Export voice.ts, which currently isn't exported here because it causes all tests to depend on skinning export * from './wrappers'; diff --git a/test/test-utils/room.ts b/test/test-utils/room.ts new file mode 100644 index 00000000000..c4537463f1a --- /dev/null +++ b/test/test-utils/room.ts @@ -0,0 +1,68 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { mocked } from "jest-mock"; +import { + EventType, + MatrixClient, + RoomMember, + RoomStateEvent, +} from "matrix-js-sdk/src/matrix"; + +import { mkEvent, mockStateEventImplementation } from "./test-utils"; + +/** + * Add a membership event to a mocked room and client + * - adds membership event to room.currentState.getStateEvents mock + * - creates room member + * - sets room.getMember mock return to room member + * - emits RoomStateEvent.Members from client + * + * Client must have mocked or real emit function + * Client must be setup with a mocked getRoom that returns mocked rooms + * ``` + * const client = getMockClientWithEventEmitter({ + * getRoom: jest.fn() + * }) + * const room1 = mkRoom('!room1:server.org'); + * const room2 = mkRoom('!room2:server.org'); + * const rooms = [room1, room2]; + * mocked(client).getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); + * addMembershipToMockedRoom(room1.roomId, '@test:server.org', client); + * ``` + */ +export const addMembershipToMockedRoom = ( + roomId: string, userId: string, client: MatrixClient, membership = 'join', +) => { + const memberEvent = mkEvent({ + event: true, + type: EventType.RoomMember, + room: roomId, + user: client.getUserId(), + skey: userId, + content: { membership }, + ts: Date.now(), + }); + const room = client.getRoom(roomId); + mocked(room.currentState).getStateEvents.mockImplementation( + mockStateEventImplementation([memberEvent]), + ); + const user = new RoomMember(roomId, userId); + user.setMembershipEvent(memberEvent); + mocked(room).getMember.mockReturnValue(user); + + client.emit(RoomStateEvent.Members, memberEvent, room.currentState, user); +}; From 7fbecdc1e2d681747cf007f138e0e30cfc406275 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 29 Mar 2022 17:28:58 +0200 Subject: [PATCH 3/4] test remove beacons on membership changes Signed-off-by: Kerry Archibald --- src/stores/OwnBeaconStore.ts | 5 +- test/stores/OwnBeaconStore-test.ts | 116 ++++++++++++++++++++++++++++- test/stores/SpaceStore-test.ts | 24 +++++- test/test-utils/room.ts | 60 ++++----------- 4 files changed, 153 insertions(+), 52 deletions(-) diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index b60182f3847..3d57cfecb79 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -174,7 +174,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient { */ private onRoomStateMembers = (_event: MatrixEvent, roomState: RoomState, member: RoomMember): void => { // no beacons for this room, ignore - if (!this.beaconsByRoomId.has(roomState.roomId)) { + if ( + !this.beaconsByRoomId.has(roomState.roomId) || + member.userId !== this.matrixClient.getUserId() + ) { return; } diff --git a/test/stores/OwnBeaconStore-test.ts b/test/stores/OwnBeaconStore-test.ts index 87b3c4c6023..18c145350cd 100644 --- a/test/stores/OwnBeaconStore-test.ts +++ b/test/stores/OwnBeaconStore-test.ts @@ -14,7 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Room, Beacon, BeaconEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { + Room, + Beacon, + BeaconEvent, + MatrixEvent, + RoomStateEvent, + RoomMember, +} from "matrix-js-sdk/src/matrix"; import { makeBeaconContent } from "matrix-js-sdk/src/content-helpers"; import { M_BEACON, M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { logger } from "matrix-js-sdk/src/logger"; @@ -23,6 +30,7 @@ import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../src/stores/OwnBeaconS import { advanceDateAndTime, flushPromisesWithFakeTimers, + makeMembershipEvent, resetAsyncStoreWithClient, setupAsyncStoreWithClient, } from "../test-utils"; @@ -509,6 +517,112 @@ describe('OwnBeaconStore', () => { }); }); + describe('on room membership changes', () => { + it('ignores events for rooms without beacons', async () => { + const membershipEvent = makeMembershipEvent(room2Id, aliceId); + // no beacons for room2 + const [, room2] = makeRoomsWithStateEvents([ + alicesRoom1BeaconInfo, + ]); + const store = await makeOwnBeaconStore(); + const emitSpy = jest.spyOn(store, 'emit'); + const oldLiveBeaconIds = store.getLiveBeaconIds(); + + mockClient.emit( + RoomStateEvent.Members, + membershipEvent, + room2.currentState, + new RoomMember(room2Id, aliceId), + ); + + expect(emitSpy).not.toHaveBeenCalled(); + // strictly equal + expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds); + }); + + it('ignores events for membership changes that are not current user', async () => { + // bob joins room1 + const membershipEvent = makeMembershipEvent(room1Id, bobId); + const member = new RoomMember(room1Id, bobId); + member.setMembershipEvent(membershipEvent); + + const [room1] = makeRoomsWithStateEvents([ + alicesRoom1BeaconInfo, + ]); + const store = await makeOwnBeaconStore(); + const emitSpy = jest.spyOn(store, 'emit'); + const oldLiveBeaconIds = store.getLiveBeaconIds(); + + mockClient.emit( + RoomStateEvent.Members, + membershipEvent, + room1.currentState, + member, + ); + + expect(emitSpy).not.toHaveBeenCalled(); + // strictly equal + expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds); + }); + + it('ignores events for membership changes that are not leave/ban', async () => { + // alice joins room1 + const membershipEvent = makeMembershipEvent(room1Id, aliceId); + const member = new RoomMember(room1Id, aliceId); + member.setMembershipEvent(membershipEvent); + + const [room1] = makeRoomsWithStateEvents([ + alicesRoom1BeaconInfo, + alicesRoom2BeaconInfo, + ]); + const store = await makeOwnBeaconStore(); + const emitSpy = jest.spyOn(store, 'emit'); + const oldLiveBeaconIds = store.getLiveBeaconIds(); + + mockClient.emit( + RoomStateEvent.Members, + membershipEvent, + room1.currentState, + member, + ); + + expect(emitSpy).not.toHaveBeenCalled(); + // strictly equal + expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds); + }); + + it('destroys and removes beacons when current user leaves room', async () => { + // alice leaves room1 + const membershipEvent = makeMembershipEvent(room1Id, aliceId, 'leave'); + const member = new RoomMember(room1Id, aliceId); + member.setMembershipEvent(membershipEvent); + + const [room1] = makeRoomsWithStateEvents([ + alicesRoom1BeaconInfo, + alicesRoom2BeaconInfo, + ]); + const store = await makeOwnBeaconStore(); + const room1BeaconInstance = store.beacons.get(alicesRoom1BeaconInfo.getType()); + const beaconDestroySpy = jest.spyOn(room1BeaconInstance, 'destroy'); + const emitSpy = jest.spyOn(store, 'emit'); + + mockClient.emit( + RoomStateEvent.Members, + membershipEvent, + room1.currentState, + member, + ); + + expect(emitSpy).toHaveBeenCalledWith( + OwnBeaconStoreEvent.LivenessChange, + // other rooms beacons still live + [alicesRoom2BeaconInfo.getType()], + ); + expect(beaconDestroySpy).toHaveBeenCalledTimes(1); + expect(store.getLiveBeaconIds(room1Id)).toEqual([]); + }); + }); + describe('stopBeacon()', () => { beforeEach(() => { makeRoomsWithStateEvents([ diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 48a5d9f968c..60faa55804b 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -38,7 +38,6 @@ import SettingsStore from "../../src/settings/SettingsStore"; import { SettingLevel } from "../../src/settings/SettingLevel"; import { Action } from "../../src/dispatcher/actions"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; -import { addMembershipToMockedRoom } from '../test-utils/room'; jest.useFakeTimers(); @@ -719,6 +718,25 @@ describe("SpaceStore", () => { client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, undefined); }; + const addMember = (spaceId, user: RoomMember) => { + const memberEvent = mkEvent({ + event: true, + type: EventType.RoomMember, + room: spaceId, + user: client.getUserId(), + skey: user.userId, + content: { membership: 'join' }, + ts: Date.now(), + }); + const spaceRoom = client.getRoom(spaceId); + mocked(spaceRoom.currentState).getStateEvents.mockImplementation( + testUtils.mockStateEventImplementation([memberEvent]), + ); + mocked(spaceRoom).getMember.mockReturnValue(user); + + client.emit(RoomStateEvent.Members, memberEvent, spaceRoom.currentState, user); + }; + it('emits events for parent spaces when child room is added', async () => { await run(); @@ -757,7 +775,7 @@ describe("SpaceStore", () => { const emitSpy = jest.spyOn(store, 'emit').mockClear(); // add into space2 - addMembershipToMockedRoom(space2, dm1Partner.userId, client); + addMember(space2, dm1Partner); expect(emitSpy).toHaveBeenCalledWith(space2); // space2 is subspace of space4 @@ -773,7 +791,7 @@ describe("SpaceStore", () => { expect(store.getSpaceFilteredUserIds(space2)).toEqual(new Set([])); // add into space2 - addMembershipToMockedRoom(space2, dm1Partner.userId, client); + addMember(space2, dm1Partner); expect(store.getSpaceFilteredUserIds(space2)).toEqual(new Set([dm1Partner.userId])); expect(store.getSpaceFilteredUserIds(space4)).toEqual(new Set([dm1Partner.userId])); diff --git a/test/test-utils/room.ts b/test/test-utils/room.ts index c4537463f1a..022f13e6c1a 100644 --- a/test/test-utils/room.ts +++ b/test/test-utils/room.ts @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,55 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { mocked } from "jest-mock"; import { EventType, - MatrixClient, - RoomMember, - RoomStateEvent, } from "matrix-js-sdk/src/matrix"; -import { mkEvent, mockStateEventImplementation } from "./test-utils"; +import { mkEvent } from "./test-utils"; -/** - * Add a membership event to a mocked room and client - * - adds membership event to room.currentState.getStateEvents mock - * - creates room member - * - sets room.getMember mock return to room member - * - emits RoomStateEvent.Members from client - * - * Client must have mocked or real emit function - * Client must be setup with a mocked getRoom that returns mocked rooms - * ``` - * const client = getMockClientWithEventEmitter({ - * getRoom: jest.fn() - * }) - * const room1 = mkRoom('!room1:server.org'); - * const room2 = mkRoom('!room2:server.org'); - * const rooms = [room1, room2]; - * mocked(client).getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); - * addMembershipToMockedRoom(room1.roomId, '@test:server.org', client); - * ``` - */ -export const addMembershipToMockedRoom = ( - roomId: string, userId: string, client: MatrixClient, membership = 'join', -) => { - const memberEvent = mkEvent({ - event: true, - type: EventType.RoomMember, - room: roomId, - user: client.getUserId(), - skey: userId, - content: { membership }, - ts: Date.now(), - }); - const room = client.getRoom(roomId); - mocked(room.currentState).getStateEvents.mockImplementation( - mockStateEventImplementation([memberEvent]), - ); - const user = new RoomMember(roomId, userId); - user.setMembershipEvent(memberEvent); - mocked(room).getMember.mockReturnValue(user); +export const makeMembershipEvent = ( + roomId: string, userId: string, membership = 'join', +) => mkEvent({ + event: true, + type: EventType.RoomMember, + room: roomId, + user: userId, + skey: userId, + content: { membership }, + ts: Date.now(), +}); - client.emit(RoomStateEvent.Members, memberEvent, room.currentState, user); -}; From e9c2de423362068857c0b140b7dfe700ecf25260 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 29 Mar 2022 17:33:36 +0200 Subject: [PATCH 4/4] removelistener Signed-off-by: Kerry Archibald --- src/stores/OwnBeaconStore.ts | 1 + test/stores/OwnBeaconStore-test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index 3d57cfecb79..34049982879 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -93,6 +93,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { protected async onNotReady() { this.matrixClient.removeListener(BeaconEvent.LivenessChange, this.onBeaconLiveness); this.matrixClient.removeListener(BeaconEvent.New, this.onNewBeacon); + this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers); this.beacons.forEach(beacon => beacon.destroy()); diff --git a/test/stores/OwnBeaconStore-test.ts b/test/stores/OwnBeaconStore-test.ts index 18c145350cd..d40708109e5 100644 --- a/test/stores/OwnBeaconStore-test.ts +++ b/test/stores/OwnBeaconStore-test.ts @@ -251,6 +251,7 @@ describe('OwnBeaconStore', () => { expect(removeSpy.mock.calls[0]).toEqual(expect.arrayContaining([BeaconEvent.LivenessChange])); expect(removeSpy.mock.calls[1]).toEqual(expect.arrayContaining([BeaconEvent.New])); + expect(removeSpy.mock.calls[2]).toEqual(expect.arrayContaining([RoomStateEvent.Members])); }); it('destroys beacons', async () => {