Skip to content

Commit

Permalink
MSC3946 Dynamic predecessors for getVisibleRooms
Browse files Browse the repository at this point in the history
  • Loading branch information
andybalaam committed Jan 10, 2023
1 parent 999e355 commit f665afc
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 3 deletions.
123 changes: 123 additions & 0 deletions spec/unit/matrix-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2234,6 +2234,20 @@ describe("MatrixClient", function () {
});
}

function predecessorEvent(newRoomId: string, predecessorRoomId: string): MatrixEvent {
return new MatrixEvent({
content: {
predecessor_room_id: predecessorRoomId,
},
event_id: `predecessor_event_id_pred_${predecessorRoomId}`,
origin_server_ts: 1432735824653,
room_id: newRoomId,
sender: "@daryl:alexandria.example.com",
state_key: "",
type: "org.matrix.msc3946.room_predecessor",
});
}

it("Returns an empty list if there are no rooms", () => {
client.store = new StubStore();
client.store.getRooms = () => [];
Expand Down Expand Up @@ -2274,5 +2288,114 @@ describe("MatrixClient", function () {
expect(rooms).toContain(room1);
expect(rooms).toContain(room2);
});

function setUpReplacedRooms(): {
room1: Room;
room2: Room;
replacedByCreate1: Room;
replacedByCreate2: Room;
replacedByDynamicPredecessor1: Room;
replacedByDynamicPredecessor2: Room;
} {
const room1 = new Room("room1", client, "@carol:alexandria.example.com");
const replacedByCreate1 = new Room("replacedByCreate1", client, "@carol:alexandria.example.com");
const replacedByCreate2 = new Room("replacedByCreate2", client, "@carol:alexandria.example.com");
const replacedByDynamicPredecessor1 = new Room("dyn1", client, "@carol:alexandria.example.com");
const replacedByDynamicPredecessor2 = new Room("dyn2", client, "@carol:alexandria.example.com");
const room2 = new Room("room2", client, "@daryl:alexandria.example.com");
client.store = new StubStore();
client.store.getRooms = () => [
room1,
replacedByCreate1,
replacedByCreate2,
replacedByDynamicPredecessor1,
replacedByDynamicPredecessor2,
room2,
];
room1.addLiveEvents(
[
roomCreateEvent(room1.roomId, replacedByCreate1.roomId),
predecessorEvent(room1.roomId, replacedByDynamicPredecessor1.roomId),
],
{},
);
room2.addLiveEvents(
[
roomCreateEvent(room2.roomId, replacedByCreate2.roomId),
predecessorEvent(room2.roomId, replacedByDynamicPredecessor2.roomId),
],
{},
);
replacedByCreate1.addLiveEvents([tombstoneEvent(room1.roomId, replacedByCreate1.roomId)], {});
replacedByCreate2.addLiveEvents([tombstoneEvent(room2.roomId, replacedByCreate2.roomId)], {});
replacedByDynamicPredecessor1.addLiveEvents(
[tombstoneEvent(room1.roomId, replacedByDynamicPredecessor1.roomId)],
{},
);
replacedByDynamicPredecessor2.addLiveEvents(
[tombstoneEvent(room2.roomId, replacedByDynamicPredecessor2.roomId)],
{},
);

return {
room1,
room2,
replacedByCreate1,
replacedByCreate2,
replacedByDynamicPredecessor1,
replacedByDynamicPredecessor2,
};
}

it("Considers rooms replaced with m.predecessor events to be replaced", () => {
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
// replaced by create events, but are now NOT replaced, because an
// m.predecessor event has changed the room's predecessor.
const {
room1,
room2,
replacedByCreate1,
replacedByCreate2,
replacedByDynamicPredecessor1,
replacedByDynamicPredecessor2,
} = setUpReplacedRooms();

// When we ask for the visible rooms
const useMsc3946 = true;
const rooms = client.getVisibleRooms(useMsc3946);

// Then we only get the ones that have not been replaced
expect(rooms).not.toContain(replacedByDynamicPredecessor1);
expect(rooms).not.toContain(replacedByDynamicPredecessor2);
expect(rooms).toContain(replacedByCreate1);
expect(rooms).toContain(replacedByCreate2);
expect(rooms).toContain(room1);
expect(rooms).toContain(room2);
});

it("Ignores m.predecessor if we don't ask to use it", () => {
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
// replaced by create events, but are now NOT replaced, because an
// m.predecessor event has changed the room's predecessor.
const {
room1,
room2,
replacedByCreate1,
replacedByCreate2,
replacedByDynamicPredecessor1,
replacedByDynamicPredecessor2,
} = setUpReplacedRooms();

// When we ask for the visible rooms
const rooms = client.getVisibleRooms(); // Don't supply msc3946ProcessDynamicPredecessor

// Then we only get the ones that have not been replaced
expect(rooms).not.toContain(replacedByCreate1);
expect(rooms).not.toContain(replacedByCreate2);
expect(rooms).toContain(replacedByDynamicPredecessor1);
expect(rooms).toContain(replacedByDynamicPredecessor2);
expect(rooms).toContain(room1);
expect(rooms).toContain(room2);
});
});
});
46 changes: 46 additions & 0 deletions spec/unit/room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3257,6 +3257,20 @@ describe("Room", function () {
});
}

function predecessorEvent(newRoomId: string, predecessorRoomId: string): MatrixEvent {
return new MatrixEvent({
content: {
predecessor_room_id: predecessorRoomId,
},
event_id: `predecessor_event_id_pred_${predecessorRoomId}`,
origin_server_ts: 1432735824653,
room_id: newRoomId,
sender: "@daryl:alexandria.example.com",
state_key: "",
type: "org.matrix.msc3946.room_predecessor",
});
}

it("Returns null if there is no create event", () => {
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
expect(room.findPredecessorRoomId()).toBeNull();
Expand All @@ -3273,5 +3287,37 @@ describe("Room", function () {
room.addLiveEvents([roomCreateEvent("roomid", "replacedroomid")]);
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
});

it("Prefers the m.predecessor event if one exists", () => {
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
room.addLiveEvents([
roomCreateEvent("roomid", "replacedroomid"),
predecessorEvent("roomid", "otherreplacedroomid"),
]);
const useMsc3946 = true;
expect(room.findPredecessorRoomId(useMsc3946)).toBe("otherreplacedroomid");
});

it("Ignores the m.predecessor event if we don't ask to use it", () => {
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
room.addLiveEvents([
roomCreateEvent("roomid", "replacedroomid"),
predecessorEvent("roomid", "otherreplacedroomid"),
]);
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
// we should ignore the predecessor event.
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
});

it("Ignores the m.predecessor event and returns null if we don't ask to use it", () => {
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
room.addLiveEvents([
roomCreateEvent("roomid", null), // Create event has no predecessor
predecessorEvent("roomid", "otherreplacedroomid"),
]);
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
// we should ignore the predecessor event.
expect(room.findPredecessorRoomId()).toBeNull();
});
});
});
1 change: 1 addition & 0 deletions src/@types/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum EventType {
RoomGuestAccess = "m.room.guest_access",
RoomServerAcl = "m.room.server_acl",
RoomTombstone = "m.room.tombstone",
RoomPredecessor = "org.matrix.msc3946.room_predecessor",

SpaceChild = "m.space.child",
SpaceParent = "m.space.parent",
Expand Down
8 changes: 6 additions & 2 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3763,14 +3763,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* This is essentially getRooms() with some rooms filtered out, eg. old versions
* of rooms that have been replaced or (in future) other rooms that have been
* marked at the protocol level as not to be displayed to the user.
*
* @param msc3946ProcessDynamicPredecessor - if true, look for an
* m.room.predecessor state event and
* use it if found (MSC3946).
* @returns A list of rooms, or an empty list if there is no data store.
*/
public getVisibleRooms(): Room[] {
public getVisibleRooms(msc3946ProcessDynamicPredecessor = false): Room[] {
const allRooms = this.store.getRooms();

const replacedRooms = new Set();
for (const r of allRooms) {
const predecessor = r.findPredecessorRoomId();
const predecessor = r.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
if (predecessor) {
replacedRooms.add(predecessor);
}
Expand Down
14 changes: 13 additions & 1 deletion src/models/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2970,14 +2970,26 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}

/**
* @param msc3946ProcessDynamicPredecessor - if true, look for an
* m.room.predecessor state event and
* use it if found (MSC3946).
* @returns the ID of the room that was this room's predecessor, or null if
* this room has no predecessor.
*/
public findPredecessorRoomId(): string | null {
public findPredecessorRoomId(msc3946ProcessDynamicPredecessor = false): string | null {
const currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
if (!currentState) {
return null;
}
if (msc3946ProcessDynamicPredecessor) {
const predecessorEvent = currentState.getStateEvents(EventType.RoomPredecessor, "");
if (predecessorEvent) {
const roomId = predecessorEvent.getContent()["predecessor_room_id"];
if (roomId) {
return roomId;
}
}
}

const createEvent = currentState.getStateEvents(EventType.RoomCreate, "");
if (createEvent) {
Expand Down

0 comments on commit f665afc

Please sign in to comment.