diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index 83844fe8670..7afee718967 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -24,13 +24,10 @@ import { MatrixClient, MatrixEvent, MatrixEventEvent, - RelationType, Room, - RoomEvent, } from "../../src"; -import { FeatureSupport, Thread } from "../../src/models/thread"; +import { Thread } from "../../src/models/thread"; import { ReEmitter } from "../../src/ReEmitter"; -import { eventMapperFor } from "../../src/event-mapper"; describe("EventTimelineSet", () => { const roomId = "!foo:bar"; @@ -206,108 +203,6 @@ describe("EventTimelineSet", () => { expect(liveTimeline.getEvents().length).toStrictEqual(0); }); - describe("When the server supports threads", () => { - let previousThreadHasServerSideSupport: FeatureSupport; - - beforeAll(() => { - previousThreadHasServerSideSupport = Thread.hasServerSideSupport; - Thread.hasServerSideSupport = FeatureSupport.Stable; - }); - - afterAll(() => { - Thread.hasServerSideSupport = previousThreadHasServerSideSupport; - }); - - it("should allow edits to be added to thread timeline", async () => { - jest.spyOn(client, "supportsThreads").mockReturnValue(true); - jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); - - const sender = "@alice:matrix.org"; - - const root = utils.mkEvent({ - event: true, - content: { - body: "Thread root", - }, - type: EventType.RoomMessage, - sender, - }); - room.addLiveEvents([root]); - - const threadReply = utils.mkEvent({ - event: true, - content: { - "body": "Thread reply", - "m.relates_to": { - event_id: root.getId()!, - rel_type: RelationType.Thread, - }, - }, - type: EventType.RoomMessage, - sender, - }); - - root.setUnsigned({ - "m.relations": { - [RelationType.Thread]: { - count: 1, - latest_event: { - content: threadReply.getContent(), - origin_server_ts: 5, - room_id: room.roomId, - sender, - type: EventType.RoomMessage, - event_id: threadReply.getId()!, - user_id: sender, - age: 1, - }, - current_user_participated: true, - }, - }, - }); - - const editToThreadReply = utils.mkEvent({ - event: true, - content: { - "body": " * edit", - "m.new_content": { - "body": "edit", - "msgtype": "m.text", - "org.matrix.msc1767.text": "edit", - }, - "m.relates_to": { - event_id: threadReply.getId()!, - rel_type: RelationType.Replace, - }, - }, - type: EventType.RoomMessage, - sender, - }); - - // Mock methods that call out to HTTP endpoints - jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); - jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); - jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); - - // Create a thread and wait for it to be initialised - const thread = room.createThread(root.getId()!, root, [], false); - await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); - - // When a message and an edit are added to the thread - await thread.addEvent(threadReply, false); - await thread.addEvent(editToThreadReply, false); - - // Then both events end up in the timeline - const lastEvent = thread.timeline.at(-1)!; - const secondLastEvent = thread.timeline.at(-2)!; - expect(lastEvent).toBe(editToThreadReply); - expect(secondLastEvent).toBe(threadReply); - - // And the first message has been edited - expect(secondLastEvent.getContent().body).toEqual("edit"); - }); - }); - describe("non-room timeline", () => { it("Adds event to timeline", () => { const nonRoomEventTimelineSet = new EventTimelineSet( diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index bd17f00c8aa..cd0340cb199 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -17,12 +17,12 @@ limitations under the License. import { mocked } from "jest-mock"; import { MatrixClient, PendingEventOrdering } from "../../../src/client"; -import { Room } from "../../../src/models/room"; +import { Room, RoomEvent } from "../../../src/models/room"; import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../../../src/models/thread"; import { mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; -import { emitPromise, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; -import { Direction, EventStatus, MatrixEvent } from "../../../src"; +import { emitPromise, mkEvent, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; +import { Direction, EventStatus, EventType, MatrixEvent, RelationType } from "../../../src"; import { ReceiptType } from "../../../src/@types/read_receipts"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client"; import { ReEmitter } from "../../../src/ReEmitter"; @@ -474,4 +474,102 @@ describe("Thread", () => { return client; } }); + + describe("Editing events", () => { + it("should allow edits to be added to thread timeline", async () => { + const roomId = "!foo:bar"; + const userA = "@alice:bar"; + const client = mock(MatrixClient, "MatrixClient"); + client.reEmitter = mock(ReEmitter, "ReEmitter"); + client.canSupport = new Map(); + const room = new Room(roomId, client, userA); + jest.spyOn(client, "supportsThreads").mockReturnValue(true); + jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); + Thread.hasServerSideSupport = FeatureSupport.Stable; + + const sender = "@alice:matrix.org"; + + const root = mkEvent({ + event: true, + content: { + body: "Thread root", + }, + type: EventType.RoomMessage, + sender, + }); + room.addLiveEvents([root]); + + const threadReply = mkEvent({ + event: true, + content: { + "body": "Thread reply", + "m.relates_to": { + event_id: root.getId()!, + rel_type: RelationType.Thread, + }, + }, + type: EventType.RoomMessage, + sender, + }); + + root.setUnsigned({ + "m.relations": { + [RelationType.Thread]: { + count: 1, + latest_event: { + content: threadReply.getContent(), + origin_server_ts: 5, + room_id: room.roomId, + sender, + type: EventType.RoomMessage, + event_id: threadReply.getId()!, + user_id: sender, + age: 1, + }, + current_user_participated: true, + }, + }, + }); + + const editToThreadReply = mkEvent({ + event: true, + content: { + "body": " * edit", + "m.new_content": { + "body": "edit", + "msgtype": "m.text", + "org.matrix.msc1767.text": "edit", + }, + "m.relates_to": { + event_id: threadReply.getId()!, + rel_type: RelationType.Replace, + }, + }, + type: EventType.RoomMessage, + sender, + }); + + // Mock methods that call out to HTTP endpoints + jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); + jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); + jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); + + // Create a thread and wait for it to be initialised + const thread = room.createThread(root.getId()!, root, [], false); + await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); + + // When a message and an edit are added to the thread + await thread.addEvent(threadReply, false); + await thread.addEvent(editToThreadReply, false); + + // Then both events end up in the timeline + const lastEvent = thread.timeline.at(-1)!; + const secondLastEvent = thread.timeline.at(-2)!; + expect(lastEvent).toBe(editToThreadReply); + expect(secondLastEvent).toBe(threadReply); + + // And the first message has been edited + expect(secondLastEvent.getContent().body).toEqual("edit"); + }); + }); });