Wire up more high level read receipt tests (#11408)
* Improve existing tests * Wire up more tests * Wire up more tests * Wire up more tests * Wire up more tests
This commit is contained in:
parent
65b518dfe1
commit
beafe686a9
1 changed files with 222 additions and 44 deletions
|
@ -68,11 +68,15 @@ describe("Read receipts", () => {
|
||||||
cy.stopHomeserver(homeserver);
|
cy.stopHomeserver(homeserver);
|
||||||
});
|
});
|
||||||
|
|
||||||
abstract class MessageSpec {
|
abstract class MessageContentSpec {
|
||||||
public abstract getContent(room: Room): Promise<Record<string, unknown>>;
|
public abstract getContent(room: Room): Promise<Record<string, unknown>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message = string | MessageSpec;
|
abstract class BotActionSpec {
|
||||||
|
public abstract performAction(cli: MatrixClient, room: Room): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message = string | MessageContentSpec | BotActionSpec;
|
||||||
|
|
||||||
function goTo(room: string) {
|
function goTo(room: string) {
|
||||||
cy.viewRoomByName(room);
|
cy.viewRoomByName(room);
|
||||||
|
@ -95,24 +99,39 @@ describe("Read receipts", () => {
|
||||||
cy.get(".mx_ThreadView_timelinePanelWrapper", { log: false }).should("have.length", 1);
|
cy.get(".mx_ThreadView_timelinePanelWrapper", { log: false }).should("have.length", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function sendMessageAsClient(cli: MatrixClient, room: string, messages: Message[]) {
|
||||||
* Sends messages into given room as a bot
|
|
||||||
* @param room - the name of the room to send messages into
|
|
||||||
* @param messages - the list of messages to send, these can be strings or implementations of MessageSpace like `editOf`
|
|
||||||
*/
|
|
||||||
function receiveMessages(room: string, messages: Message[]) {
|
|
||||||
findRoomByName(room).then(async ({ roomId }) => {
|
findRoomByName(room).then(async ({ roomId }) => {
|
||||||
const room = bot.getRoom(roomId);
|
const room = cli.getRoom(roomId);
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
if (typeof message === "string") {
|
if (typeof message === "string") {
|
||||||
await bot.sendTextMessage(roomId, message);
|
await cli.sendTextMessage(roomId, message);
|
||||||
|
} else if (message instanceof MessageContentSpec) {
|
||||||
|
await cli.sendMessage(roomId, await message.getContent(room));
|
||||||
} else {
|
} else {
|
||||||
await bot.sendMessage(roomId, await message.getContent(room));
|
await message.performAction(cli, room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends messages into given room as a bot
|
||||||
|
* @param room - the name of the room to send messages into
|
||||||
|
* @param messages - the list of messages to send, these can be strings or implementations of MessageSpec like `editOf`
|
||||||
|
*/
|
||||||
|
function receiveMessages(room: string, messages: Message[]) {
|
||||||
|
sendMessageAsClient(bot, room, messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends messages into given room as the currently logged-in user
|
||||||
|
* @param room - the name of the room to send messages into
|
||||||
|
* @param messages - the list of messages to send, these can be strings or implementations of MessageSpec like `editOf`
|
||||||
|
*/
|
||||||
|
function sendMessages(room: string, messages: Message[]) {
|
||||||
|
cy.getClient().then((cli) => sendMessageAsClient(cli, room, messages));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to find a MatrixEvent by its body content
|
* Utility to find a MatrixEvent by its body content
|
||||||
* @param room - the room to search for the event in
|
* @param room - the room to search for the event in
|
||||||
|
@ -140,12 +159,12 @@ describe("Read receipts", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageSpec to send an edit into a room
|
* MessageContentSpec to send an edit into a room
|
||||||
* @param originalMessage - the body of the message to edit
|
* @param originalMessage - the body of the message to edit
|
||||||
* @param newMessage - the message body to send in the edit
|
* @param newMessage - the message body to send in the edit
|
||||||
*/
|
*/
|
||||||
function editOf(originalMessage: string, newMessage: string): MessageSpec {
|
function editOf(originalMessage: string, newMessage: string): MessageContentSpec {
|
||||||
return new (class extends MessageSpec {
|
return new (class extends MessageContentSpec {
|
||||||
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
||||||
const ev = await getMessage(room, originalMessage, true);
|
const ev = await getMessage(room, originalMessage, true);
|
||||||
|
|
||||||
|
@ -163,12 +182,12 @@ describe("Read receipts", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageSpec to send a reply into a room
|
* MessageContentSpec to send a reply into a room
|
||||||
* @param targetMessage - the body of the message to reply to
|
* @param targetMessage - the body of the message to reply to
|
||||||
* @param newMessage - the message body to send into the reply
|
* @param newMessage - the message body to send into the reply
|
||||||
*/
|
*/
|
||||||
function replyTo(targetMessage: string, newMessage: string): MessageSpec {
|
function replyTo(targetMessage: string, newMessage: string): MessageContentSpec {
|
||||||
return new (class extends MessageSpec {
|
return new (class extends MessageContentSpec {
|
||||||
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
||||||
const ev = await getMessage(room, targetMessage);
|
const ev = await getMessage(room, targetMessage);
|
||||||
|
|
||||||
|
@ -186,12 +205,12 @@ describe("Read receipts", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageSpec to send a threaded response into a room
|
* MessageContentSpec to send a threaded response into a room
|
||||||
* @param rootMessage - the body of the thread root message to send a response to
|
* @param rootMessage - the body of the thread root message to send a response to
|
||||||
* @param newMessage - the message body to send into the thread response
|
* @param newMessage - the message body to send into the thread response
|
||||||
*/
|
*/
|
||||||
function threadedOff(rootMessage: string, newMessage: string): MessageSpec {
|
function threadedOff(rootMessage: string, newMessage: string): MessageContentSpec {
|
||||||
return new (class extends MessageSpec {
|
return new (class extends MessageContentSpec {
|
||||||
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
public async getContent(room: Room): Promise<Record<string, unknown>> {
|
||||||
const ev = await getMessage(room, rootMessage);
|
const ev = await getMessage(room, rootMessage);
|
||||||
|
|
||||||
|
@ -208,6 +227,53 @@ describe("Read receipts", () => {
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BotActionSpec to send a reaction to an existing event into a room
|
||||||
|
* @param targetMessage - the body of the message to send a reaction to
|
||||||
|
* @param reaction - the key of the reaction to send into the room
|
||||||
|
*/
|
||||||
|
function reactionTo(targetMessage: string, reaction: string): BotActionSpec {
|
||||||
|
return new (class extends BotActionSpec {
|
||||||
|
public async performAction(cli: MatrixClient, room: Room): Promise<void> {
|
||||||
|
const ev = await getMessage(room, targetMessage, true);
|
||||||
|
const threadId = !ev.isThreadRoot ? ev.threadRootId : undefined;
|
||||||
|
await cli.sendEvent(room.roomId, threadId ?? null, "m.reaction", {
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: "m.annotation",
|
||||||
|
event_id: ev.getId(),
|
||||||
|
key: reaction,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BotActionSpec to send a custom event
|
||||||
|
* @param eventType - the type of the event to send
|
||||||
|
* @param content - the event content to send
|
||||||
|
*/
|
||||||
|
function customEvent(eventType: string, content: Record<string, any>): BotActionSpec {
|
||||||
|
return new (class extends BotActionSpec {
|
||||||
|
public async performAction(cli: MatrixClient, room: Room): Promise<void> {
|
||||||
|
await cli.sendEvent(room.roomId, null, eventType, content);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BotActionSpec to send a redaction into a room
|
||||||
|
* @param targetMessage - the body of the message to send a redaction to
|
||||||
|
*/
|
||||||
|
function redactionOf(targetMessage: string): BotActionSpec {
|
||||||
|
return new (class extends BotActionSpec {
|
||||||
|
public async performAction(cli: MatrixClient, room: Room): Promise<void> {
|
||||||
|
const ev = await getMessage(room, targetMessage, true);
|
||||||
|
await cli.redactEvent(room.roomId, ev.threadRootId, ev.getId());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
function getRoomListTile(room: string) {
|
function getRoomListTile(room: string) {
|
||||||
return cy.findByRole("treeitem", { name: new RegExp("^" + room), log: false });
|
return cy.findByRole("treeitem", { name: new RegExp("^" + room), log: false });
|
||||||
}
|
}
|
||||||
|
@ -246,7 +312,7 @@ describe("Read receipts", () => {
|
||||||
cy.log("Open thread list");
|
cy.log("Open thread list");
|
||||||
cy.findByTestId("threadsButton", { log: false }).then(($button) => {
|
cy.findByTestId("threadsButton", { log: false }).then(($button) => {
|
||||||
if ($button?.attr("aria-current") !== "true") {
|
if ($button?.attr("aria-current") !== "true") {
|
||||||
$button.trigger("click");
|
cy.findByTestId("threadsButton", { log: false }).click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -296,7 +362,7 @@ describe("Read receipts", () => {
|
||||||
|
|
||||||
describe("new messages", () => {
|
describe("new messages", () => {
|
||||||
describe("in the main timeline", () => {
|
describe("in the main timeline", () => {
|
||||||
it("Sending a message makes a room unread", () => {
|
it("Receiving a message makes a room unread", () => {
|
||||||
goTo(room1);
|
goTo(room1);
|
||||||
assertRead(room2);
|
assertRead(room2);
|
||||||
|
|
||||||
|
@ -323,7 +389,7 @@ describe("Read receipts", () => {
|
||||||
markAsRead(room2);
|
markAsRead(room2);
|
||||||
assertRead(room2);
|
assertRead(room2);
|
||||||
});
|
});
|
||||||
it("Sending a new message after marking as read makes it unread", () => {
|
it("Receiving a new message after marking as read makes it unread", () => {
|
||||||
goTo(room1);
|
goTo(room1);
|
||||||
assertRead(room2);
|
assertRead(room2);
|
||||||
receiveMessages(room2, ["Msg1"]);
|
receiveMessages(room2, ["Msg1"]);
|
||||||
|
@ -356,6 +422,16 @@ describe("Read receipts", () => {
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
assertRead(room2);
|
assertRead(room2);
|
||||||
});
|
});
|
||||||
|
it.skip("Sending a message from a different client marks room as read", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, ["Msg1"]);
|
||||||
|
assertUnread(room2, 1);
|
||||||
|
|
||||||
|
sendMessages(room2, ["Msg2"]);
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("in threads", () => {
|
describe("in threads", () => {
|
||||||
|
@ -527,7 +603,7 @@ describe("Read receipts", () => {
|
||||||
// Given a thread exists
|
// Given a thread exists
|
||||||
goTo(room1);
|
goTo(room1);
|
||||||
receiveMessages(room2, ["Msg1", threadedOff("Msg1", "Resp1")]);
|
receiveMessages(room2, ["Msg1", threadedOff("Msg1", "Resp1")]);
|
||||||
assertUnread(room2, 1); // (Sanity)
|
assertUnread(room2, 2); // (Sanity)
|
||||||
|
|
||||||
// When I read the main timeline
|
// When I read the main timeline
|
||||||
goTo(room2);
|
goTo(room2);
|
||||||
|
@ -801,24 +877,78 @@ describe("Read receipts", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("reactions", () => {
|
describe("reactions", () => {
|
||||||
// Justification for this section: edits an reactions are similar, so we
|
|
||||||
// might choose to miss this section, but I have included it because
|
|
||||||
// edits replace the content of the original event in our code and
|
|
||||||
// reactions don't, so it seems possible that bugs could creep in that
|
|
||||||
// affect only one or the other.
|
|
||||||
|
|
||||||
describe("in the main timeline", () => {
|
describe("in the main timeline", () => {
|
||||||
it.skip("Reacting to a message makes a room unread", () => {});
|
it("Receiving a reaction to a message does not make a room unread", () => {
|
||||||
it.skip("Reading a reaction makes the room read", () => {});
|
goTo(room1);
|
||||||
it.skip("Marking a room as read after a reaction makes it read", () => {});
|
assertRead(room2);
|
||||||
it.skip("Reacting to a message after marking as read makes the room unread", () => {});
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
it.skip("A room with a reaction is still unread after restart", () => {});
|
assertUnread(room2, 2);
|
||||||
it.skip("A room where all reactions are read is still read after restart", () => {});
|
|
||||||
|
// When I read the main timeline
|
||||||
|
goTo(room2);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
goTo(room1);
|
||||||
|
receiveMessages(room2, [reactionTo("Msg2", "🪿")]);
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
|
it("Reacting to a message after marking as read does not make the room unread", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
markAsRead(room2);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, [reactionTo("Msg2", "🪿")]);
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
|
it("A room with an unread reaction is still read after restart", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
markAsRead(room2);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, [reactionTo("Msg2", "🪿")]);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
saveAndReload();
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
|
it("A room where all reactions are read is still read after restart", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2", reactionTo("Msg2", "🪿")]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
markAsRead(room2);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
saveAndReload();
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("in threads", () => {
|
describe("in threads", () => {
|
||||||
it.skip("A reaction to a threaded message makes the room unread", () => {});
|
it("A reaction to a threaded message does not make the room unread", () => {
|
||||||
it.skip("Reading a reaction to a threaded message makes the room read", () => {});
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", threadedOff("Msg1", "Reply1")]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
goTo(room2);
|
||||||
|
openThread("Msg1");
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
goTo(room1);
|
||||||
|
receiveMessages(room2, [reactionTo("Reply1", "🪿")]);
|
||||||
|
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
it.skip("Marking a room as read after a reaction in a thread makes it read", () => {});
|
it.skip("Marking a room as read after a reaction in a thread makes it read", () => {});
|
||||||
it.skip("Reacting to a thread message after marking as read makes the room unread", () => {});
|
it.skip("Reacting to a thread message after marking as read makes the room unread", () => {});
|
||||||
it.skip("A room with a reaction to a threaded message is still unread after restart", () => {});
|
it.skip("A room with a reaction to a threaded message is still unread after restart", () => {});
|
||||||
|
@ -826,7 +956,21 @@ describe("Read receipts", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("thread roots", () => {
|
describe("thread roots", () => {
|
||||||
it.skip("A reaction to a thread root makes the room unread", () => {});
|
it("A reaction to a thread root does not make the room unread", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", threadedOff("Msg1", "Reply1")]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
goTo(room2);
|
||||||
|
openThread("Msg1");
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
goTo(room1);
|
||||||
|
receiveMessages(room2, [reactionTo("Msg1", "🪿")]);
|
||||||
|
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
it.skip("Reading a reaction to a thread root makes the room read", () => {});
|
it.skip("Reading a reaction to a thread root makes the room read", () => {});
|
||||||
it.skip("Marking a room as read after a reaction to a thread root makes it read", () => {});
|
it.skip("Marking a room as read after a reaction to a thread root makes it read", () => {});
|
||||||
it.skip("Reacting to a thread root after marking as read makes the room unread but not the thread", () => {});
|
it.skip("Reacting to a thread root after marking as read makes the room unread but not the thread", () => {});
|
||||||
|
@ -835,9 +979,20 @@ describe("Read receipts", () => {
|
||||||
|
|
||||||
describe("redactions", () => {
|
describe("redactions", () => {
|
||||||
describe("in the main timeline", () => {
|
describe("in the main timeline", () => {
|
||||||
// One of the following two must be right:
|
it("Redacting the message pointed to by my receipt leaves the room read", () => {
|
||||||
it.skip("Redacting the message pointed to by my receipt leaves the room read", () => {});
|
goTo(room1);
|
||||||
it.skip("Redacting a message after it was read makes the room unread", () => {});
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
|
assertUnread(room2, 1);
|
||||||
|
|
||||||
|
// When I read the main timeline
|
||||||
|
goTo(room2);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
goTo(room1);
|
||||||
|
receiveMessages(room2, [redactionOf("Msg2")]);
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
|
|
||||||
it.skip("Reading an unread room after a redaction of the latest message makes it read", () => {});
|
it.skip("Reading an unread room after a redaction of the latest message makes it read", () => {});
|
||||||
it.skip("Reading an unread room after a redaction of an older message makes it read", () => {});
|
it.skip("Reading an unread room after a redaction of an older message makes it read", () => {});
|
||||||
|
@ -949,8 +1104,31 @@ describe("Read receipts", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Ignored events", () => {
|
describe("Ignored events", () => {
|
||||||
it.skip("If all events after receipt are unimportant, the room is read", () => {});
|
it("If all events after receipt are unimportant, the room is read", () => {
|
||||||
it.skip("Sending an important event after unimportant ones makes the room unread", () => {});
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
markAsRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, [customEvent("org.custom.event", { body: "foobar" })]);
|
||||||
|
assertRead(room2);
|
||||||
|
});
|
||||||
|
it("Sending an important event after unimportant ones makes the room unread", () => {
|
||||||
|
goTo(room1);
|
||||||
|
assertRead(room2);
|
||||||
|
receiveMessages(room2, ["Msg1", "Msg2"]);
|
||||||
|
assertUnread(room2, 2);
|
||||||
|
|
||||||
|
markAsRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, [customEvent("org.custom.event", { body: "foobar" })]);
|
||||||
|
assertRead(room2);
|
||||||
|
|
||||||
|
receiveMessages(room2, ["Hello"]);
|
||||||
|
assertUnread(room2, 1);
|
||||||
|
});
|
||||||
it.skip("A receipt for the last unimportant event makes the room read, even if all are unimportant", () => {});
|
it.skip("A receipt for the last unimportant event makes the room read, even if all are unimportant", () => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue