Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit c841026

Browse files
committed
Tests for RoomListStore's predecessor handling
1 parent 8161da1 commit c841026

File tree

2 files changed

+139
-36
lines changed

2 files changed

+139
-36
lines changed

src/stores/room-list/RoomListStore.ts

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
3939
import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
4040
import { UPDATE_EVENT } from "../AsyncStore";
4141
import { SdkContextClass } from "../../contexts/SDKContext";
42+
import { RoomState } from "../../../../matrix-js-sdk";
4243

4344
interface IState {
4445
// state is tracked in underlying classes
@@ -267,44 +268,55 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
267268
}
268269
this.updateFn.trigger();
269270
} else if (payload.action === "MatrixActions.Room.myMembership") {
270-
const membershipPayload = <any>payload; // TODO: Type out the dispatcher types
271-
const oldMembership = getEffectiveMembership(membershipPayload.oldMembership);
272-
const newMembership = getEffectiveMembership(membershipPayload.membership);
273-
if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
274-
// If we're joining an upgraded room, we'll want to make sure we don't proliferate
275-
// the dead room in the list.
276-
const createEvent = membershipPayload.room.currentState.getStateEvents(EventType.RoomCreate, "");
277-
if (createEvent && createEvent.getContent()["predecessor"]) {
278-
const prevRoom = this.matrixClient.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
279-
if (prevRoom) {
280-
const isSticky = this.algorithm.stickyRoom === prevRoom;
281-
if (isSticky) {
282-
this.algorithm.setStickyRoom(null);
283-
}
284-
285-
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
286-
// avoid redundant updates.
287-
this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
271+
this.onDispatchMyMembership(<any>payload);
272+
return;
273+
}
274+
}
275+
276+
/**
277+
* Handle a MatrixActions.Room.myMembership event from the dispatcher.
278+
*
279+
* Public for test.
280+
*/
281+
public async onDispatchMyMembership(membershipPayload: any): Promise<void> {
282+
// TODO: Type out the dispatcher types so membershipPayload is not any
283+
const oldMembership = getEffectiveMembership(membershipPayload.oldMembership);
284+
const newMembership = getEffectiveMembership(membershipPayload.membership);
285+
if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
286+
// If we're joining an upgraded room, we'll want to make sure we don't proliferate
287+
// the dead room in the list.
288+
const roomState: RoomState = membershipPayload.room.currentState;
289+
const createEvent = roomState.getStateEvents(EventType.RoomCreate, "");
290+
if (createEvent && createEvent.getContent()["predecessor"]) {
291+
const prevRoom = this.matrixClient.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
292+
if (prevRoom) {
293+
const isSticky = this.algorithm.stickyRoom === prevRoom;
294+
if (isSticky) {
295+
this.algorithm.setStickyRoom(null);
288296
}
289-
}
290297

291-
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
292-
this.updateFn.trigger();
293-
return;
298+
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
299+
// avoid redundant updates.
300+
this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
301+
}
294302
}
295303

296-
if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) {
297-
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
298-
this.updateFn.trigger();
299-
return;
300-
}
304+
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
305+
this.updateFn.trigger();
306+
return;
307+
}
301308

302-
// If it's not a join, it's transitioning into a different list (possibly historical)
303-
if (oldMembership !== newMembership) {
304-
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange);
305-
this.updateFn.trigger();
306-
return;
307-
}
309+
if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) {
310+
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
311+
this.updateFn.trigger();
312+
return;
313+
}
314+
315+
// If it's not a join, it's transitioning into a different list (possibly historical)
316+
if (oldMembership !== newMembership) {
317+
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange);
318+
this.updateFn.trigger();
319+
return;
308320
}
309321
}
310322

test/stores/room-list/RoomListStore-test.ts

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,55 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
18+
19+
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
1720
import { ListAlgorithm, SortAlgorithm } from "../../../src/stores/room-list/algorithms/models";
18-
import { OrderedDefaultTagIDs } from "../../../src/stores/room-list/models";
21+
import { OrderedDefaultTagIDs, RoomUpdateCause } from "../../../src/stores/room-list/models";
1922
import RoomListStore, { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore";
20-
import { stubClient } from "../../test-utils";
23+
import { stubClient, upsertRoomStateEvents } from "../../test-utils";
2124

2225
describe("RoomListStore", () => {
26+
const client = stubClient();
27+
const roomWithCreatePredecessorId = "!roomid:example.com";
28+
const roomNoPredecessorId = "!roomnopreid:example.com";
29+
const oldRoomId = "!oldroomid:example.com";
30+
const userId = "@user:example.com";
31+
const createWithPredecessor = new MatrixEvent({
32+
type: EventType.RoomCreate,
33+
sender: userId,
34+
room_id: roomWithCreatePredecessorId,
35+
content: {
36+
predecessor: { room_id: oldRoomId, event_id: "tombstone_event_id" },
37+
},
38+
event_id: "$create",
39+
state_key: "",
40+
});
41+
const createNoPredecessor = new MatrixEvent({
42+
type: EventType.RoomCreate,
43+
sender: userId,
44+
room_id: roomWithCreatePredecessorId,
45+
content: {},
46+
event_id: "$create",
47+
state_key: "",
48+
});
49+
const roomWithCreatePredecessor = new Room(roomWithCreatePredecessorId, client, userId, {});
50+
upsertRoomStateEvents(roomWithCreatePredecessor, [createWithPredecessor]);
51+
const roomNoPredecessor = new Room(roomNoPredecessorId, client, userId, {});
52+
upsertRoomStateEvents(roomNoPredecessor, [createNoPredecessor]);
53+
const oldRoom = new Room(oldRoomId, client, userId, {});
54+
client.getRoom = jest.fn().mockImplementation((roomId) => {
55+
switch (roomId) {
56+
case roomWithCreatePredecessorId:
57+
return roomWithCreatePredecessor;
58+
case oldRoomId:
59+
return oldRoom;
60+
default:
61+
return null;
62+
}
63+
});
64+
2365
beforeAll(async () => {
24-
const client = stubClient();
2566
await (RoomListStore.instance as RoomListStoreClass).makeReady(client);
2667
});
2768

@@ -32,4 +73,54 @@ describe("RoomListStore", () => {
3273
it.each(OrderedDefaultTagIDs)("defaults to activity ordering for %s=", (tagId) => {
3374
expect(RoomListStore.instance.getListOrder(tagId)).toBe(ListAlgorithm.Importance);
3475
});
76+
77+
function createStore(): { store: RoomListStoreClass; handleRoomUpdate: jest.Mock<any, any> } {
78+
const fakeDispatcher = { register: jest.fn() } as unknown as MatrixDispatcher;
79+
const store = new RoomListStoreClass(fakeDispatcher);
80+
// @ts-ignore accessing private member to set client
81+
store.readyStore.matrixClient = client;
82+
const handleRoomUpdate = jest.fn();
83+
// @ts-ignore accessing private member to mock it
84+
store.algorithm.handleRoomUpdate = handleRoomUpdate;
85+
86+
return { store, handleRoomUpdate };
87+
}
88+
89+
it("Removes old room if it finds a predecessor in the create event", () => {
90+
// Given a store we can spy on
91+
const { store, handleRoomUpdate } = createStore();
92+
93+
// When we tell it we joined a new room that has an old room as
94+
// predecessor in the create event
95+
const payload = {
96+
oldMembership: "invite",
97+
membership: "join",
98+
room: roomWithCreatePredecessor,
99+
};
100+
store.onDispatchMyMembership(payload);
101+
102+
// Then the old room is removed
103+
expect(handleRoomUpdate).toHaveBeenCalledWith(oldRoom, RoomUpdateCause.RoomRemoved);
104+
105+
// And the new room is added
106+
expect(handleRoomUpdate).toHaveBeenCalledWith(oldRoom, RoomUpdateCause.RoomRemoved);
107+
});
108+
109+
it("Does not remove old room if there is no predecessor in the create event", () => {
110+
// Given a store we can spy on
111+
const { store, handleRoomUpdate } = createStore();
112+
113+
// When we tell it we joined a new room with no predecessor
114+
const payload = {
115+
oldMembership: "invite",
116+
membership: "join",
117+
room: roomNoPredecessor,
118+
};
119+
store.onDispatchMyMembership(payload);
120+
121+
// Then the new room is added
122+
expect(handleRoomUpdate).toHaveBeenCalledWith(roomNoPredecessor, RoomUpdateCause.NewRoom);
123+
// And no other updates happen
124+
expect(handleRoomUpdate).toHaveBeenCalledTimes(1);
125+
});
35126
});

0 commit comments

Comments
 (0)