diff --git a/cypress/e2e/read-receipts/high-level.spec.ts b/cypress/e2e/read-receipts/high-level.spec.ts
index 76da807a4c..bcce3b3d44 100644
--- a/cypress/e2e/read-receipts/high-level.spec.ts
+++ b/cypress/e2e/read-receipts/high-level.spec.ts
@@ -18,16 +18,29 @@ limitations under the License.
///
-import type { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import {
+ assertMessageLoaded,
+ assertMessageNotLoaded,
assertRead,
+ assertReadThread,
assertStillRead,
assertUnread,
+ assertUnreadGreaterThan,
+ assertUnreadThread,
+ closeThreadsPanel,
customEvent,
goTo,
+ many,
markAsRead,
Message,
+ MessageContentSpec,
+ MessageFinder,
+ openThread,
+ openThreadList,
+ pageUp,
+ saveAndReload,
sendMessageAsClient,
} from "./read-receipts-utils";
@@ -42,12 +55,19 @@ describe("Read receipts", () => {
let alphaRoomId: string;
let bot: MatrixClient | undefined;
- /**
- * Map of message content -> event. Allows us to find e.g. edited or
- * redacted messages even if their content has changed or disappeared from
- * screen.
- */
- const messages = new Map();
+ let messageFinder: MessageFinder;
+
+ function threadedOff(rootMessage: string, newMessage: string): MessageContentSpec {
+ return messageFinder.threadedOff(rootMessage, newMessage);
+ }
+
+ function manyThreadedOff(rootMessage: string, newMessages: Array): Array {
+ return messageFinder.manyThreadedOff(rootMessage, newMessages);
+ }
+
+ function jumpTo(room: string, message: string, includeThreads = false) {
+ return messageFinder.jumpTo(room, message, includeThreads);
+ }
before(() => {
// Note: unusually for the Cypress tests in this repo, we share a single
@@ -68,7 +88,7 @@ describe("Read receipts", () => {
});
beforeEach(() => {
- messages.clear();
+ messageFinder = new MessageFinder();
// Create 2 rooms: Alpha & Beta. We join the bot to both of them
cy.initTestUser(homeserver, userName)
@@ -183,11 +203,219 @@ describe("Read receipts", () => {
});
describe("Paging up", () => {
- it.skip("Paging up through old messages after a room is read leaves the room read", () => {});
- it.skip("Paging up through old messages of an unread room leaves the room unread", () => {});
- it.skip("Paging up to find old threads that were previously read leaves the room read", () => {});
- it.skip("?? Paging up to find old threads that were never read marks the room unread", () => {});
- it.skip("After marking room as read, paging up to find old threads that were never read leaves the room read", () => {});
+ it("Paging up through old messages after a room is read leaves the room read", () => {
+ // Given lots of messages are in the room, but we have read them
+ goTo(room1);
+ receiveMessages(room2, many("Msg", 110));
+ assertUnread(room2, 110);
+ goTo(room2);
+ assertRead(room2);
+ goTo(room1);
+
+ // When we restart, so only recent messages are loaded
+ saveAndReload();
+ goTo(room2);
+ assertMessageNotLoaded("Msg0010");
+
+ // And we page up, loading in old messages
+ pageUp();
+ cy.wait(200);
+ pageUp();
+ cy.wait(200);
+ pageUp();
+ assertMessageLoaded("Msg0010");
+
+ // Then the room remains read
+ assertStillRead(room2);
+ });
+ it("Paging up through old messages of an unread room leaves the room unread", () => {
+ // Given lots of messages are in the room, and they are not read
+ goTo(room1);
+ receiveMessages(room2, many("x\ny\nz\nMsg", 40)); // newline to spread out messages
+ assertUnread(room2, 40);
+
+ // When I jump to a message in the middle and page up
+ jumpTo(room2, "x\ny\nz\nMsg0020");
+ pageUp();
+
+ // Then the room is still unread
+ assertUnreadGreaterThan(room2, 1);
+ });
+ it("Paging up to find old threads that were previously read leaves the room read", () => {
+ // Given lots of messages in threads are all read
+ goTo(room1);
+ receiveMessages(room2, [
+ "Root1",
+ "Root2",
+ "Root3",
+ ...manyThreadedOff("Root1", many("T", 20)),
+ ...manyThreadedOff("Root2", many("T", 20)),
+ ...manyThreadedOff("Root3", many("T", 20)),
+ ]);
+ goTo(room2);
+ assertUnread(room2, 60);
+ openThread("Root1");
+ assertUnread(room2, 40);
+ assertReadThread("Root1");
+ openThread("Root2");
+ assertUnread(room2, 20);
+ assertReadThread("Root2");
+ openThread("Root3");
+ assertRead(room2);
+ assertReadThread("Root3");
+
+ // When I restart and page up to load old thread roots
+ goTo(room1);
+ saveAndReload();
+ goTo(room2);
+ pageUp();
+
+ // Then the room and threads remain read
+ assertRead(room2);
+ assertReadThread("Root1");
+ assertReadThread("Root2");
+ assertReadThread("Root3");
+ });
+ it("Paging up to find old threads that were never read keeps the room unread", () => {
+ // Given lots of messages in threads that are unread
+ goTo(room1);
+ receiveMessages(room2, [
+ "Root1",
+ "Root2",
+ "Root3",
+ ...manyThreadedOff("Root1", many("T", 2)),
+ ...manyThreadedOff("Root2", many("T", 2)),
+ ...manyThreadedOff("Root3", many("T", 2)),
+ ...many("Msg", 100),
+ ]);
+ goTo(room2);
+ assertUnread(room2, 6);
+ assertUnreadThread("Root1");
+ assertUnreadThread("Root2");
+ assertUnreadThread("Root3");
+
+ // When I restart
+ closeThreadsPanel();
+ goTo(room1);
+ saveAndReload();
+
+ // Then the room remembers it's unread
+ // TODO: I (andyb) think this will fall in an encrypted room
+ assertUnread(room2, 6);
+
+ // And when I page up to load old thread roots
+ goTo(room2);
+ pageUp();
+
+ // Then the room remains unread
+ assertUnread(room2, 6);
+ assertUnreadThread("Root1");
+ assertUnreadThread("Root2");
+ assertUnreadThread("Root3");
+ });
+ it("Looking in thread view to find old threads that were never read makes the room unread", () => {
+ // Given lots of messages in threads that are unread
+ goTo(room1);
+ receiveMessages(room2, [
+ "Root1",
+ "Root2",
+ "Root3",
+ ...manyThreadedOff("Root1", many("T", 2)),
+ ...manyThreadedOff("Root2", many("T", 2)),
+ ...manyThreadedOff("Root3", many("T", 2)),
+ ...many("Msg", 100),
+ ]);
+ goTo(room2);
+ assertUnread(room2, 6);
+ assertUnreadThread("Root1");
+ assertUnreadThread("Root2");
+ assertUnreadThread("Root3");
+
+ // When I restart
+ closeThreadsPanel();
+ goTo(room1);
+ saveAndReload();
+
+ // Then the room remembers it's unread
+ // TODO: I (andyb) think this will fall in an encrypted room
+ assertUnread(room2, 6);
+
+ // And when I open the threads view
+ goTo(room2);
+ openThreadList();
+
+ // Then the room remains unread
+ assertUnread(room2, 6);
+ assertUnreadThread("Root1");
+ assertUnreadThread("Root2");
+ assertUnreadThread("Root3");
+ });
+ it("After marking room as read, paging up to find old threads that were never read leaves the room read", () => {
+ // Given lots of messages in threads that are unread but I marked as read on a main timeline message
+ goTo(room1);
+ receiveMessages(room2, [
+ "Root1",
+ "Root2",
+ "Root3",
+ ...manyThreadedOff("Root1", many("T", 2)),
+ ...manyThreadedOff("Root2", many("T", 2)),
+ ...manyThreadedOff("Root3", many("T", 2)),
+ ...many("Msg", 100),
+ ]);
+ markAsRead(room2);
+ assertRead(room2);
+
+ // When I restart
+ saveAndReload();
+
+ // Then the room remembers it's read
+ assertRead(room2);
+
+ // And when I page up to load old thread roots
+ goTo(room2);
+ pageUp();
+ pageUp();
+ pageUp();
+
+ // Then the room remains read
+ assertStillRead(room2);
+ assertReadThread("Root1");
+ assertReadThread("Root2");
+ assertReadThread("Root3");
+ });
+ // XXX: fails because we see a dot instead of an unread number - probably the server and client disagree
+ it.skip("After marking room as read based on a thread message, opening threads view to find old threads that were never read leaves the room read", () => {
+ // Given lots of messages in threads that are unread but I marked as read on a thread message
+ goTo(room1);
+ receiveMessages(room2, [
+ "Root1",
+ "Root2",
+ "Root3",
+ ...manyThreadedOff("Root1", many("T1-", 2)),
+ ...manyThreadedOff("Root2", many("T2-", 2)),
+ ...manyThreadedOff("Root3", many("T3-", 2)),
+ ...many("Msg", 100),
+ threadedOff("Msg0099", "Thread off 99"),
+ ]);
+ markAsRead(room2);
+ assertRead(room2);
+
+ // When I restart
+ saveAndReload();
+
+ // Then the room remembers it's read
+ assertRead(room2);
+
+ // And when I page up to load old thread roots
+ goTo(room2);
+ openThreadList();
+
+ // Then the room remains read
+ assertStillRead(room2);
+ assertReadThread("Root1");
+ assertReadThread("Root2");
+ assertReadThread("Root3");
+ });
});
describe("Room list order", () => {
diff --git a/cypress/e2e/read-receipts/new-messages.spec.ts b/cypress/e2e/read-receipts/new-messages.spec.ts
index 8cb3cdb140..66d9f75bef 100644
--- a/cypress/e2e/read-receipts/new-messages.spec.ts
+++ b/cypress/e2e/read-receipts/new-messages.spec.ts
@@ -176,7 +176,7 @@ describe("Read receipts", () => {
assertUnread(room2, 30);
// When I jump to one of the older messages
- jumpTo(room2, "Msg1");
+ jumpTo(room2, "Msg0001");
// Then the room is still unread, but some messages were read
assertUnreadLessThan(room2, 30);
@@ -321,7 +321,7 @@ describe("Read receipts", () => {
assertUnread(room2, 21);
// When I read an older message in the thread
- jumpTo(room2, "InThread1", true);
+ jumpTo(room2, "InThread0001", true);
assertUnreadLessThan(room2, 21);
// TODO: for some reason, we can't find the first message
// "InThread0", so I am using the second here. Also, they appear
@@ -488,7 +488,7 @@ describe("Read receipts", () => {
assertUnread(room2, 62); // Sanity
// When I jump to an old message and read the thread
- jumpTo(room2, "beforeThread0");
+ jumpTo(room2, "beforeThread0000");
openThread("ThreadRoot");
// Then the thread root is marked as read in the main timeline,
diff --git a/cypress/e2e/read-receipts/read-receipts-utils.ts b/cypress/e2e/read-receipts/read-receipts-utils.ts
index 513fd75348..db181cf171 100644
--- a/cypress/e2e/read-receipts/read-receipts-utils.ts
+++ b/cypress/e2e/read-receipts/read-receipts-utils.ts
@@ -372,7 +372,7 @@ export function pageUp() {
* @param howMany the number of strings to generate
*/
export function many(prefix: string, howMany: number): Array {
- return Array.from(Array(howMany).keys()).map((i) => prefix + i.toFixed());
+ return Array.from(Array(howMany).keys()).map((i) => prefix + i.toString().padStart(4, "0"));
}
/**