mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-06 14:38:21 +00:00
Absorb the matrix-react-sdk repository (#28192)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: github-merge-queue <github-merge-queue@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Florian Duros <florian.duros@ormaz.fr> Co-authored-by: Kim Brose <kim.brose@nordeck.net> Co-authored-by: Florian Duros <florianduros@element.io> Co-authored-by: R Midhun Suresh <hi@midhun.dev> Co-authored-by: dbkr <986903+dbkr@users.noreply.github.com> Co-authored-by: ElementRobot <releases@riot.im> Co-authored-by: dbkr <dbkr@users.noreply.github.com> Co-authored-by: David Baker <dbkr@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: David Langley <davidl@element.io> Co-authored-by: Michael Weimann <michaelw@matrix.org> Co-authored-by: Timshel <Timshel@users.noreply.github.com> Co-authored-by: Sahil Silare <32628578+sahil9001@users.noreply.github.com> Co-authored-by: Will Hunt <will@half-shot.uk> Co-authored-by: Hubert Chathi <hubert@uhoreg.ca> Co-authored-by: Andrew Ferrazzutti <andrewf@element.io> Co-authored-by: Robin <robin@robin.town> Co-authored-by: Tulir Asokan <tulir@maunium.net>
This commit is contained in:
committed by
GitHub
parent
2b99496025
commit
c05c429803
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { JSHandle } from "@playwright/test";
|
||||
import type { MatrixEvent, ISendEventResponse, ReceiptType } from "matrix-js-sdk/src/matrix";
|
||||
import { expect } from "../../element-web-test";
|
||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||
import { Bot } from "../../pages/bot";
|
||||
import { test } from ".";
|
||||
|
||||
test.describe("Read receipts", () => {
|
||||
test.use({
|
||||
displayName: "Mae",
|
||||
botCreateOpts: { displayName: "Other User" },
|
||||
});
|
||||
|
||||
const selectedRoomName = "Selected Room";
|
||||
const otherRoomName = "Other Room";
|
||||
|
||||
let otherRoomId: string;
|
||||
let selectedRoomId: string;
|
||||
|
||||
const sendMessage = async (bot: Bot, no = 1): Promise<ISendEventResponse> => {
|
||||
return bot.sendMessage(otherRoomId, { body: `Message ${no}`, msgtype: "m.text" });
|
||||
};
|
||||
|
||||
const botSendThreadMessage = (bot: Bot, threadId: string): Promise<ISendEventResponse> => {
|
||||
return bot.sendEvent(otherRoomId, threadId, "m.room.message", { body: "Message", msgtype: "m.text" });
|
||||
};
|
||||
|
||||
const fakeEventFromSent = (
|
||||
app: ElementAppPage,
|
||||
eventResponse: ISendEventResponse,
|
||||
threadRootId: string | undefined,
|
||||
): Promise<JSHandle<MatrixEvent>> => {
|
||||
return app.client.evaluateHandle(
|
||||
(client, { otherRoomId, eventResponse, threadRootId }) => {
|
||||
return {
|
||||
getRoomId: () => otherRoomId,
|
||||
getId: () => eventResponse.event_id,
|
||||
threadRootId,
|
||||
getTs: () => 1,
|
||||
isRelation: (relType) => {
|
||||
return !relType || relType === "m.thread";
|
||||
},
|
||||
} as any as MatrixEvent;
|
||||
},
|
||||
{ otherRoomId, eventResponse, threadRootId },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a threaded receipt marking the message referred to in
|
||||
* eventResponse as read. If threadRootEventResponse is supplied, the
|
||||
* receipt will have its event_id as the thread root ID for the receipt.
|
||||
*/
|
||||
const sendThreadedReadReceipt = async (
|
||||
app: ElementAppPage,
|
||||
eventResponse: ISendEventResponse,
|
||||
threadRootEventResponse: ISendEventResponse = undefined,
|
||||
) => {
|
||||
await app.client.sendReadReceipt(
|
||||
await fakeEventFromSent(app, eventResponse, threadRootEventResponse?.event_id),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an unthreaded receipt marking the message referred to in
|
||||
* eventResponse as read.
|
||||
*/
|
||||
const sendUnthreadedReadReceipt = async (app: ElementAppPage, eventResponse: ISendEventResponse) => {
|
||||
await app.client.sendReadReceipt(
|
||||
await fakeEventFromSent(app, eventResponse, undefined),
|
||||
"m.read" as any as ReceiptType,
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page, app, user, bot }) => {
|
||||
/*
|
||||
* Create 2 rooms:
|
||||
*
|
||||
* - Selected room - this one is clicked in the UI
|
||||
* - Other room - this one contains the bot, which will send events so
|
||||
* we can check its unread state.
|
||||
*/
|
||||
selectedRoomId = await app.client.createRoom({ name: selectedRoomName });
|
||||
// Invite the bot to Other room
|
||||
otherRoomId = await app.client.createRoom({ name: otherRoomName, invite: [bot.credentials.userId] });
|
||||
|
||||
await page.goto(`/#/room/${otherRoomId}`);
|
||||
await expect(page.getByText(`${bot.credentials.displayName} joined the room`)).toBeVisible();
|
||||
|
||||
// Then go into Selected room
|
||||
await page.goto(`/#/room/${selectedRoomId}`);
|
||||
});
|
||||
|
||||
// Disabled due to flakiness: https://github.com/element-hq/element-web/issues/26895
|
||||
test.skip("With sync accumulator, considers main thread and unthreaded receipts #24629", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
}) => {
|
||||
// Details are in https://github.com/vector-im/element-web/issues/24629
|
||||
// This proves we've fixed one of the "stuck unreads" issues.
|
||||
|
||||
// Given we sent 3 events on the main thread
|
||||
await sendMessage(bot);
|
||||
const main2 = await sendMessage(bot);
|
||||
const main3 = await sendMessage(bot);
|
||||
|
||||
// (So the room starts off unread)
|
||||
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
|
||||
|
||||
// When we send a threaded receipt for the last event in main
|
||||
// And an unthreaded receipt for an earlier event
|
||||
await sendThreadedReadReceipt(app, main3);
|
||||
await sendUnthreadedReadReceipt(app, main2);
|
||||
|
||||
// (So the room has no unreads)
|
||||
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible();
|
||||
|
||||
// And we persuade the app to persist its state to indexeddb by reloading and waiting
|
||||
await page.reload();
|
||||
await expect(page.getByLabel(`${selectedRoomName}`)).toBeVisible();
|
||||
|
||||
// And we reload again, fetching the persisted state FROM indexeddb
|
||||
await page.reload();
|
||||
|
||||
// Then the room is read, because the persisted state correctly remembers both
|
||||
// receipts. (In #24629, the unthreaded receipt overwrote the main thread one,
|
||||
// meaning that the room still said it had unread messages.)
|
||||
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible();
|
||||
await expect(page.getByLabel(`${otherRoomName} Unread messages.`)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("Recognises unread messages on main thread after receiving a receipt for earlier ones", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
}) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
await sendMessage(bot);
|
||||
const main2 = await sendMessage(bot);
|
||||
await sendMessage(bot);
|
||||
|
||||
// (The room starts off unread)
|
||||
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
|
||||
|
||||
// When we send a threaded receipt for the second-last event in main
|
||||
await sendThreadedReadReceipt(app, main2);
|
||||
|
||||
// Then the room has only one unread
|
||||
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
|
||||
});
|
||||
|
||||
test("Considers room read if there is only a main thread and we have a main receipt", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
}) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
await sendMessage(bot);
|
||||
await sendMessage(bot);
|
||||
const main3 = await sendMessage(bot);
|
||||
// (The room starts off unread)
|
||||
await expect(page.getByLabel(`${otherRoomName} 3 unread messages.`)).toBeVisible();
|
||||
|
||||
// When we send a threaded receipt for the last event in main
|
||||
await sendThreadedReadReceipt(app, main3);
|
||||
|
||||
// Then the room has no unreads
|
||||
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible();
|
||||
});
|
||||
|
||||
test("Recognises unread messages on other thread after receiving a receipt for earlier ones", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
util,
|
||||
}) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
const main1 = await sendMessage(bot);
|
||||
const thread1a = await botSendThreadMessage(bot, main1.event_id);
|
||||
await botSendThreadMessage(bot, main1.event_id);
|
||||
// 1 unread on the main thread, 2 in the new thread that aren't shown
|
||||
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
|
||||
|
||||
// When we send receipts for main, and the second-last in the thread
|
||||
await sendThreadedReadReceipt(app, main1);
|
||||
await sendThreadedReadReceipt(app, thread1a, main1);
|
||||
|
||||
// Then the room has only one unread - the one in the thread
|
||||
await util.goTo(otherRoomName);
|
||||
await util.assertUnreadThread("Message 1");
|
||||
});
|
||||
|
||||
test("Considers room read if there are receipts for main and other thread", async ({ page, app, bot, util }) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
const main1 = await sendMessage(bot);
|
||||
await botSendThreadMessage(bot, main1.event_id);
|
||||
const thread1b = await botSendThreadMessage(bot, main1.event_id);
|
||||
// 1 unread on the main thread, 2 in the new thread which don't show
|
||||
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
|
||||
|
||||
// When we send receipts for main, and the last in the thread
|
||||
await sendThreadedReadReceipt(app, main1);
|
||||
await sendThreadedReadReceipt(app, thread1b, main1);
|
||||
|
||||
// Then the room has no unreads
|
||||
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible();
|
||||
await util.goTo(otherRoomName);
|
||||
await util.assertReadThread("Message 1");
|
||||
});
|
||||
|
||||
test("Recognises unread messages on a thread after receiving a unthreaded receipt for earlier ones", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
util,
|
||||
}) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
const main1 = await sendMessage(bot);
|
||||
const thread1a = await botSendThreadMessage(bot, main1.event_id);
|
||||
await botSendThreadMessage(bot, main1.event_id);
|
||||
// 1 unread on the main thread, 2 in the new thread which don't count
|
||||
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
|
||||
|
||||
// When we send an unthreaded receipt for the second-last in the thread
|
||||
await sendUnthreadedReadReceipt(app, thread1a);
|
||||
|
||||
// Then the room has only one unread - the one in the
|
||||
// thread. The one in main is read because the unthreaded
|
||||
// receipt is for a later event. The room should therefore be
|
||||
// read, and the thread unread.
|
||||
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible();
|
||||
await util.goTo(otherRoomName);
|
||||
await util.assertUnreadThread("Message 1");
|
||||
});
|
||||
|
||||
test("Recognises unread messages on main after receiving a unthreaded receipt for a thread message", async ({
|
||||
page,
|
||||
app,
|
||||
bot,
|
||||
}) => {
|
||||
// Given we sent 3 events on the main thread
|
||||
const main1 = await sendMessage(bot);
|
||||
await botSendThreadMessage(bot, main1.event_id);
|
||||
const thread1b = await botSendThreadMessage(bot, main1.event_id);
|
||||
await sendMessage(bot);
|
||||
// 2 unreads on the main thread, 2 in the new thread which don't count
|
||||
await expect(page.getByLabel(`${otherRoomName} 2 unread messages.`)).toBeVisible();
|
||||
|
||||
// When we send an unthreaded receipt for the last in the thread
|
||||
await sendUnthreadedReadReceipt(app, thread1b);
|
||||
|
||||
// Then the room has only one unread - the one in the
|
||||
// main thread, because it is later than the unthreaded
|
||||
// receipt.
|
||||
await expect(page.getByLabel(`${otherRoomName} 1 unread message.`)).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* The idea of this test is to intercept the receipt / read read_markers requests and
|
||||
* assert that the correct ones are sent.
|
||||
* Prose playbook:
|
||||
* - Another user sends enough messages that the timeline becomes scrollable
|
||||
* - The current user looks at the room and jumps directly to the first unread message
|
||||
* - At this point, a receipt for the last message in the room and
|
||||
* a fully read marker for the last visible message are expected to be sent
|
||||
* - Then the user jumps to the end of the timeline
|
||||
* - A fully read marker for the last message in the room is expected to be sent
|
||||
*/
|
||||
test("Should send the correct receipts", async ({ page, bot }) => {
|
||||
const uriEncodedOtherRoomId = encodeURIComponent(otherRoomId);
|
||||
|
||||
const receiptRequestPromise = page.waitForRequest(
|
||||
new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/receipt/m\\.read/.+`),
|
||||
);
|
||||
|
||||
const numberOfMessages = 20;
|
||||
const sendMessageResponses: ISendEventResponse[] = [];
|
||||
|
||||
for (let i = 1; i <= numberOfMessages; i++) {
|
||||
sendMessageResponses.push(await sendMessage(bot, i));
|
||||
}
|
||||
|
||||
const lastMessageId = sendMessageResponses.at(-1).event_id;
|
||||
const uriEncodedLastMessageId = encodeURIComponent(lastMessageId);
|
||||
|
||||
// wait until all messages have been received
|
||||
await expect(page.getByLabel(`${otherRoomName} ${sendMessageResponses.length} unread messages.`)).toBeVisible();
|
||||
|
||||
// switch to the room with the messages
|
||||
await page.goto(`/#/room/${otherRoomId}`);
|
||||
|
||||
const receiptRequest = await receiptRequestPromise;
|
||||
// assert the read receipt for the last message in the room
|
||||
expect(receiptRequest.url()).toContain(uriEncodedLastMessageId);
|
||||
expect(receiptRequest.postDataJSON()).toEqual({
|
||||
thread_id: "main",
|
||||
});
|
||||
|
||||
// the following code tests the fully read marker somewhere in the middle of the room
|
||||
const readMarkersRequestPromise = page.waitForRequest(
|
||||
new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`),
|
||||
);
|
||||
|
||||
await page.getByRole("button", { name: "Jump to first unread message." }).click();
|
||||
|
||||
const readMarkersRequest = await readMarkersRequestPromise;
|
||||
// since this is not pixel perfect,
|
||||
// the fully read marker should be +/- 1 around the last visible message
|
||||
expect([
|
||||
sendMessageResponses[11].event_id,
|
||||
sendMessageResponses[12].event_id,
|
||||
sendMessageResponses[13].event_id,
|
||||
]).toContain(readMarkersRequest.postDataJSON()["m.fully_read"]);
|
||||
|
||||
// the following code tests the fully read marker at the bottom of the room
|
||||
const readMarkersRequestPromise2 = page.waitForRequest(
|
||||
new RegExp(`http://localhost:\\d+/_matrix/client/v3/rooms/${uriEncodedOtherRoomId}/read_markers`),
|
||||
);
|
||||
|
||||
await page.getByRole("button", { name: "Scroll to most recent messages" }).click();
|
||||
|
||||
const readMarkersRequest2 = await readMarkersRequestPromise2;
|
||||
expect(readMarkersRequest2.postDataJSON()).toEqual({
|
||||
["m.fully_read"]: sendMessageResponses.at(-1).event_id,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user