diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 647300f99c..a3c44084d5 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Room, RelationType, MatrixEvent, Thread, M_POLL_START } from "matrix-js-sdk/src/matrix"; +import { Room, RelationType, MatrixEvent, Thread, M_POLL_START, RoomEvent } from "matrix-js-sdk/src/matrix"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { ActionPayload } from "../../dispatcher/payloads"; @@ -186,7 +186,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { } private async generatePreview(room: Room, tagId?: TagID): Promise { - const events = [...room.getLiveTimeline().getEvents()]; + const events = [...room.getLiveTimeline().getEvents(), ...room.getPendingEvents()]; // add last reply from each thread room.getThreads().forEach((thread: Thread): void => { @@ -279,4 +279,19 @@ export class MessagePreviewStore extends AsyncStoreWithClient { await this.generatePreview(room, TAG_ANY); } } + + protected async onReady(): Promise { + if (!this.matrixClient) return; + this.matrixClient.on(RoomEvent.LocalEchoUpdated, this.onLocalEchoUpdated); + } + + protected async onNotReady(): Promise { + if (!this.matrixClient) return; + this.matrixClient.off(RoomEvent.LocalEchoUpdated, this.onLocalEchoUpdated); + } + + protected onLocalEchoUpdated = async (ev: MatrixEvent, room: Room): Promise => { + if (!this.previews.has(room.roomId)) return; + await this.generatePreview(room, TAG_ANY); + }; } diff --git a/test/stores/room-list/MessagePreviewStore-test.ts b/test/stores/room-list/MessagePreviewStore-test.ts index cbb045e6e9..5be2c65fd1 100644 --- a/test/stores/room-list/MessagePreviewStore-test.ts +++ b/test/stores/room-list/MessagePreviewStore-test.ts @@ -15,7 +15,16 @@ limitations under the License. */ import { Mocked, mocked } from "jest-mock"; -import { EventTimeline, EventType, MatrixClient, MatrixEvent, RelationType, Room } from "matrix-js-sdk/src/matrix"; +import { + EventStatus, + EventTimeline, + EventType, + MatrixClient, + MatrixEvent, + PendingEventOrdering, + RelationType, + Room, +} from "matrix-js-sdk/src/matrix"; import { MessagePreviewStore } from "../../../src/stores/room-list/MessagePreviewStore"; import { mkEvent, mkMessage, mkReaction, setupAsyncStoreWithClient, stubClient } from "../../test-utils"; @@ -25,6 +34,7 @@ import { mkThread } from "../../test-utils/threads"; describe("MessagePreviewStore", () => { let client: Mocked; let room: Room; + let nonRenderedRoom: Room; let store: MessagePreviewStore; async function addEvent( @@ -46,9 +56,35 @@ describe("MessagePreviewStore", () => { } } + async function addPendingEvent( + store: MessagePreviewStore, + room: Room, + event: MatrixEvent, + fireAction = true, + ): Promise { + room.addPendingEvent(event, "txid"); + if (fireAction) { + // @ts-ignore private access + await store.onLocalEchoUpdated(event, room); + } + } + + async function updatePendingEvent(event: MatrixEvent, eventStatus: EventStatus, fireAction = true): Promise { + room.updatePendingEvent(event, eventStatus); + if (fireAction) { + // @ts-ignore private access + await store.onLocalEchoUpdated(event, room); + } + } + beforeEach(async () => { client = mocked(stubClient()); - room = new Room("!roomId:server", client, client.getSafeUserId()); + room = new Room("!roomId:server", client, client.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + nonRenderedRoom = new Room("!roomId2:server", client, client.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); mocked(client.getRoom).mockReturnValue(room); store = MessagePreviewStore.testInstance(); @@ -286,4 +322,63 @@ describe("MessagePreviewStore", () => { expect(preview?.isThreadReply).toBe(false); expect(preview?.text).toContain("You reacted 🙃 to root event message"); }); + + it("should handle local echos correctly", async () => { + const firstMessage = mkMessage({ + user: "@sender:server", + event: true, + room: room.roomId, + msg: "First message", + }); + + await addEvent(store, room, firstMessage); + + expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot( + `"@sender:server: First message"`, + ); + + const secondMessage = mkMessage({ + user: "@sender:server", + event: true, + room: room.roomId, + msg: "Second message", + }); + secondMessage.status = EventStatus.NOT_SENT; + + await addPendingEvent(store, room, secondMessage); + + expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot( + `"@sender:server: Second message"`, + ); + + await updatePendingEvent(secondMessage, EventStatus.CANCELLED); + + expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot( + `"@sender:server: First message"`, + ); + }); + + it("should not generate previews for rooms not rendered", async () => { + const firstMessage = mkMessage({ + user: "@sender:server", + event: true, + room: nonRenderedRoom.roomId, + msg: "First message", + }); + + await addEvent(store, room, firstMessage); + + const secondMessage = mkMessage({ + user: "@sender:server", + event: true, + room: nonRenderedRoom.roomId, + msg: "Second message", + }); + secondMessage.status = EventStatus.NOT_SENT; + + await addPendingEvent(store, room, secondMessage); + + // @ts-ignore private access + expect(store.previews.has(nonRenderedRoom.roomId)).toBeFalsy(); + }); });