From 1b5f06b16f9b1f39c72bbcee9b3f140c1b16393c Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Date: Thu, 12 Jan 2023 10:38:41 +0000
Subject: [PATCH] Add a Cypress test for displaying edited events (#9886)
MSC3925 is changing this, so let's make sure it keeps working.
---
 cypress/e2e/editing/editing.spec.ts | 90 ++++++++++++++++++++++++++++-
 1 file changed, 87 insertions(+), 3 deletions(-)
diff --git a/cypress/e2e/editing/editing.spec.ts b/cypress/e2e/editing/editing.spec.ts
index f8d7dd1e3f..ae334ecf92 100644
--- a/cypress/e2e/editing/editing.spec.ts
+++ b/cypress/e2e/editing/editing.spec.ts
@@ -16,9 +16,9 @@ limitations under the License.
 
 /// 
 
-import type { MsgType } from "matrix-js-sdk/src/@types/event";
+import type { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
 import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
-import type { EventType } from "matrix-js-sdk/src/@types/event";
+import type { IContent } from "matrix-js-sdk/src/models/event";
 import { HomeserverInstance } from "../../plugins/utils/homeserver";
 import Chainable = Cypress.Chainable;
 
@@ -29,6 +29,16 @@ const sendEvent = (roomId: string): Chainable => {
     });
 };
 
+/** generate a message event which will take up some room on the page. */
+function mkPadding(n: number): IContent {
+    return {
+        msgtype: "m.text" as MsgType,
+        body: `padding ${n}`,
+        format: "org.matrix.custom.html",
+        formatted_body: `Test event ${n}
\n`.repeat(10),
+    };
+}
+
 describe("Editing", () => {
     let homeserver: HomeserverInstance;
 
@@ -37,7 +47,6 @@ describe("Editing", () => {
             homeserver = data;
             cy.initTestUser(homeserver, "Edith").then(() => {
                 cy.injectAxe();
-                return cy.createRoom({ name: "Test room" }).as("roomId");
             });
         });
     });
@@ -47,6 +56,8 @@ describe("Editing", () => {
     });
 
     it("should close the composer when clicking save after making a change and undoing it", () => {
+        cy.createRoom({ name: "Test room" }).as("roomId");
+
         cy.get("@roomId").then((roomId) => {
             sendEvent(roomId);
             cy.visit("/#/room/" + roomId);
@@ -64,4 +75,77 @@ describe("Editing", () => {
         // Assert that the edit composer has gone away
         cy.get(".mx_EditMessageComposer").should("not.exist");
     });
+
+    it("should correctly display events which are edited, where we lack the edit event", () => {
+        // This tests the behaviour when a message has been edited some time after it has been sent, and we
+        // jump back in room history to view the event, but do not have the actual edit event.
+        //
+        // In that scenario, we rely on the server to replace the content (pre-MSC3925), or do it ourselves based on
+        // the bundled edit event (post-MSC3925).
+        //
+        // To test it, we need to have a room with lots of events in, so we can jump around the timeline without
+        // paginating in the event itself. Hence, we create a bot user which creates the room and populates it before
+        // we join.
+
+        let testRoomId: string;
+        let originalEventId: string;
+        let editEventId: string;
+
+        // create a second user
+        const bobChainable = cy.getBot(homeserver, { displayName: "Bob", userIdPrefix: "bob_" });
+
+        cy.all([cy.window({ log: false }), bobChainable]).then(async ([win, bob]) => {
+            // "bob" now creates the room, and sends a load of events in it. Note that all of this happens via calls on
+            // the js-sdk rather than Cypress commands, so uses regular async/await.
+
+            const room = await bob.createRoom({ name: "TestRoom", visibility: win.matrixcs.Visibility.Public });
+            testRoomId = room.room_id;
+            cy.log(`Bot user created room ${room.room_id}`);
+
+            originalEventId = (await bob.sendMessage(room.room_id, { body: "original", msgtype: "m.text" })).event_id;
+            cy.log(`Bot user sent original event ${originalEventId}`);
+
+            // send a load of padding events. We make them large, so that they fill the whole screen
+            // and the client doesn't end up paginating into the event we want.
+            let i = 0;
+            while (i < 20) {
+                await bob.sendMessage(room.room_id, mkPadding(i++));
+            }
+
+            // ... then the edit ...
+            editEventId = (
+                await bob.sendMessage(room.room_id, {
+                    "m.new_content": { body: "Edited body", msgtype: "m.text" },
+                    "m.relates_to": {
+                        rel_type: "m.replace",
+                        event_id: originalEventId,
+                    },
+                    "body": "* edited",
+                    "msgtype": "m.text",
+                })
+            ).event_id;
+            cy.log(`Bot user sent edit event ${editEventId}`);
+
+            // ... then a load more padding ...
+            while (i < 40) {
+                await bob.sendMessage(room.room_id, mkPadding(i++));
+            }
+        });
+
+        cy.getClient().then((cli) => {
+            // now have the cypress user join the room, jump to the original event, and wait for the event to be
+            // visible
+            cy.joinRoom(testRoomId);
+            cy.viewRoomByName("TestRoom");
+            cy.visit(`#/room/${testRoomId}/${originalEventId}`);
+            cy.get(`[data-event-id="${originalEventId}"]`).should((messageTile) => {
+                // at this point, the edit event should still be unknown
+                expect(cli.getRoom(testRoomId).getTimelineForEvent(editEventId)).to.be.null;
+
+                // nevertheless, the event should be updated
+                expect(messageTile.find(".mx_EventTile_body").text()).to.eq("Edited body");
+                expect(messageTile.find(".mx_EventTile_edited")).to.exist;
+            });
+        });
+    });
 });