368 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
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 { Page, Request } from "@playwright/test";
 | 
						|
 | 
						|
import { test, expect } from "../../element-web-test";
 | 
						|
import type { ElementAppPage } from "../../pages/ElementAppPage";
 | 
						|
import type { Bot } from "../../pages/bot";
 | 
						|
 | 
						|
test.describe("Sliding Sync", () => {
 | 
						|
    let roomId: string;
 | 
						|
 | 
						|
    test.beforeEach(async ({ slidingSyncProxy, page, user, app }) => {
 | 
						|
        roomId = await app.client.createRoom({ name: "Test Room" });
 | 
						|
    });
 | 
						|
 | 
						|
    const checkOrder = async (wantOrder: string[], page: Page) => {
 | 
						|
        await expect(page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile_title")).toHaveText(wantOrder);
 | 
						|
    };
 | 
						|
 | 
						|
    const bumpRoom = async (roomId: string, app: ElementAppPage) => {
 | 
						|
        // Send a message into the given room, this should bump the room to the top
 | 
						|
        console.log("sendEvent", app.client.sendEvent);
 | 
						|
        await app.client.sendEvent(roomId, null, "m.room.message", {
 | 
						|
            body: "Hello world",
 | 
						|
            msgtype: "m.text",
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    const createAndJoinBot = async (app: ElementAppPage, bot: Bot): Promise<Bot> => {
 | 
						|
        await bot.prepareClient();
 | 
						|
        const bobUserId = await bot.evaluate((client) => client.getUserId());
 | 
						|
        await app.client.evaluate(
 | 
						|
            async (client, { bobUserId, roomId }) => {
 | 
						|
                await client.invite(roomId, bobUserId);
 | 
						|
            },
 | 
						|
            { bobUserId, roomId },
 | 
						|
        );
 | 
						|
        await bot.joinRoom(roomId);
 | 
						|
        return bot;
 | 
						|
    };
 | 
						|
 | 
						|
    test.skip("should render the Rooms list in reverse chronological order by default and allowing sorting A-Z", async ({
 | 
						|
        page,
 | 
						|
        app,
 | 
						|
    }) => {
 | 
						|
        // create rooms and check room names are correct
 | 
						|
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
 | 
						|
            await app.client.createRoom({ name: fruit });
 | 
						|
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
 | 
						|
        }
 | 
						|
 | 
						|
        // Check count, 3 fruits + 1 room created in beforeEach = 4
 | 
						|
        await expect(page.locator(".mx_RoomSublist_tiles").getByRole("treeitem")).toHaveCount(4);
 | 
						|
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
 | 
						|
 | 
						|
        const locator = page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_headerContainer");
 | 
						|
        await locator.hover();
 | 
						|
        await locator.getByRole("button", { name: "List options" }).click();
 | 
						|
 | 
						|
        // force click as the radio button's size is zero
 | 
						|
        await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
 | 
						|
        await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();
 | 
						|
 | 
						|
        await page.pause();
 | 
						|
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should move rooms around as new events arrive", async ({ page, app }) => {
 | 
						|
        // create rooms and check room names are correct
 | 
						|
        const roomIds: string[] = [];
 | 
						|
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
 | 
						|
            const id = await app.client.createRoom({ name: fruit });
 | 
						|
            roomIds.push(id);
 | 
						|
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
 | 
						|
        }
 | 
						|
 | 
						|
        // Select the Test Room
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room" }).click();
 | 
						|
        const [apple, pineapple, orange] = roomIds;
 | 
						|
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
 | 
						|
        await bumpRoom(apple, app);
 | 
						|
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
 | 
						|
        await bumpRoom(orange, app);
 | 
						|
        await checkOrder(["Orange", "Apple", "Pineapple", "Test Room"], page);
 | 
						|
        await bumpRoom(orange, app);
 | 
						|
        await checkOrder(["Orange", "Apple", "Pineapple", "Test Room"], page);
 | 
						|
        await bumpRoom(pineapple, app);
 | 
						|
        await checkOrder(["Pineapple", "Orange", "Apple", "Test Room"], page);
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should not move the selected room: it should be sticky", async ({ page, app }) => {
 | 
						|
        // create rooms and check room names are correct
 | 
						|
        const roomIds: string[] = [];
 | 
						|
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
 | 
						|
            const id = await app.client.createRoom({ name: fruit });
 | 
						|
            roomIds.push(id);
 | 
						|
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
 | 
						|
        }
 | 
						|
 | 
						|
        // Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
 | 
						|
        // turn into Apple, Pineapple, Orange - the index position of Pineapple never changes even though the list should technically
 | 
						|
        // be Apple, Orange Pineapple - only when you click on a different room do things reshuffle.
 | 
						|
 | 
						|
        // Select the Pineapple room
 | 
						|
        await page.getByRole("treeitem", { name: "Pineapple" }).click();
 | 
						|
        await checkOrder(["Orange", "Pineapple", "Apple", "Test Room"], page);
 | 
						|
 | 
						|
        // Move Apple
 | 
						|
        await bumpRoom(roomIds[0], app);
 | 
						|
        await checkOrder(["Apple", "Pineapple", "Orange", "Test Room"], page);
 | 
						|
 | 
						|
        // Select the Test Room
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room" }).click();
 | 
						|
 | 
						|
        // the rooms reshuffle to match reality
 | 
						|
        await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should show the right unread notifications", async ({ page, app, user, bot }) => {
 | 
						|
        const bob = await createAndJoinBot(app, bot);
 | 
						|
 | 
						|
        // send a message in the test room: unread notification count should increment
 | 
						|
        await bob.sendMessage(roomId, "Hello World");
 | 
						|
 | 
						|
        const treeItemLocator1 = page.getByRole("treeitem", { name: "Test Room 1 unread message." });
 | 
						|
        await expect(treeItemLocator1.locator(".mx_NotificationBadge_count")).toHaveText("1");
 | 
						|
        // await expect(page.locator(".mx_NotificationBadge")).not.toHaveClass("mx_NotificationBadge_highlighted");
 | 
						|
        await expect(treeItemLocator1.locator(".mx_NotificationBadge")).not.toHaveClass(
 | 
						|
            /mx_NotificationBadge_highlighted/,
 | 
						|
        );
 | 
						|
 | 
						|
        // send an @mention: highlight count (red) should be 2.
 | 
						|
        await bob.sendMessage(roomId, `Hello ${user.displayName}`);
 | 
						|
        const treeItemLocator2 = page.getByRole("treeitem", {
 | 
						|
            name: "Test Room 2 unread messages including mentions.",
 | 
						|
        });
 | 
						|
        await expect(treeItemLocator2.locator(".mx_NotificationBadge_count")).toHaveText("2");
 | 
						|
        await expect(treeItemLocator2.locator(".mx_NotificationBadge")).toHaveClass(/mx_NotificationBadge_highlighted/);
 | 
						|
 | 
						|
        // click on the room, the notif counts should disappear
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room 2 unread messages including mentions." }).click();
 | 
						|
        await expect(
 | 
						|
            page.getByRole("treeitem", { name: "Test Room" }).locator("mx_NotificationBadge_count"),
 | 
						|
        ).not.toBeAttached();
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should not show unread indicators", async ({ page, app, bot }) => {
 | 
						|
        // TODO: for now. Later we should.
 | 
						|
        await createAndJoinBot(app, bot);
 | 
						|
 | 
						|
        // disable notifs in this room (TODO: CS API call?)
 | 
						|
        const locator = page.getByRole("treeitem", { name: "Test Room" });
 | 
						|
        await locator.hover();
 | 
						|
        await locator.getByRole("button", { name: "Notification options" }).click();
 | 
						|
        await page.getByRole("menuitemradio", { name: "Mute room" }).click();
 | 
						|
 | 
						|
        // create a new room so we know when the message has been received as it'll re-shuffle the room list
 | 
						|
        await app.client.createRoom({ name: "Dummy" });
 | 
						|
 | 
						|
        await checkOrder(["Dummy", "Test Room"], page);
 | 
						|
 | 
						|
        await bot.sendMessage(roomId, "Do you read me?");
 | 
						|
 | 
						|
        // wait for this message to arrive, tell by the room list resorting
 | 
						|
        await checkOrder(["Test Room", "Dummy"], page);
 | 
						|
 | 
						|
        await expect(
 | 
						|
            page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge"),
 | 
						|
        ).not.toBeAttached();
 | 
						|
    });
 | 
						|
 | 
						|
    test("should update user settings promptly", async ({ page, app }) => {
 | 
						|
        await app.settings.openUserSettings("Preferences");
 | 
						|
        const locator = page.locator(".mx_SettingsFlag").filter({ hasText: "Show timestamps in 12 hour format" });
 | 
						|
        expect(locator).toBeVisible();
 | 
						|
        expect(locator.locator(".mx_ToggleSwitch_on")).not.toBeAttached();
 | 
						|
        await locator.locator(".mx_ToggleSwitch_ball").click();
 | 
						|
        expect(locator.locator(".mx_ToggleSwitch_on")).toBeAttached();
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should show and be able to accept/reject/rescind invites", async ({ page, app, bot }) => {
 | 
						|
        await createAndJoinBot(app, bot);
 | 
						|
 | 
						|
        const clientUserId = await app.client.evaluate((client) => client.getUserId());
 | 
						|
 | 
						|
        // invite bot into 3 rooms:
 | 
						|
        // - roomJoin: will join this room
 | 
						|
        // - roomReject: will reject the invite
 | 
						|
        // - roomRescind: will make Bob rescind the invite
 | 
						|
        const roomNames = ["Room to Join", "Room to Reject", "Room to Rescind"];
 | 
						|
        const roomRescind = await bot.evaluate(
 | 
						|
            async (client, { roomNames, clientUserId }) => {
 | 
						|
                const rooms = await Promise.all(roomNames.map((name) => client.createRoom({ name })));
 | 
						|
                await Promise.all(rooms.map((room) => client.invite(room.room_id, clientUserId)));
 | 
						|
                return rooms[2].room_id;
 | 
						|
            },
 | 
						|
            { roomNames, clientUserId },
 | 
						|
        );
 | 
						|
 | 
						|
        await expect(
 | 
						|
            page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
 | 
						|
        ).toHaveCount(3);
 | 
						|
 | 
						|
        // Select the room to join
 | 
						|
        await page.getByRole("treeitem", { name: "Room to Join" }).click();
 | 
						|
 | 
						|
        // Accept the invite
 | 
						|
        await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click();
 | 
						|
 | 
						|
        await checkOrder(["Room to Join", "Test Room"], page);
 | 
						|
 | 
						|
        // Select the room to reject
 | 
						|
        await page.getByRole("treeitem", { name: "Room to Reject" }).click();
 | 
						|
 | 
						|
        // Reject the invite
 | 
						|
        await page.locator(".mx_RoomView").getByRole("button", { name: "Reject", exact: true }).click();
 | 
						|
 | 
						|
        await expect(
 | 
						|
            page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
 | 
						|
        ).toHaveCount(2);
 | 
						|
 | 
						|
        // check the lists are correct
 | 
						|
        await checkOrder(["Room to Join", "Test Room"], page);
 | 
						|
 | 
						|
        const titleLocator = page.getByRole("group", { name: "Invites" }).locator(".mx_RoomTile_title");
 | 
						|
        await expect(titleLocator).toHaveCount(1);
 | 
						|
        await expect(titleLocator).toHaveText("Room to Rescind");
 | 
						|
 | 
						|
        // now rescind the invite
 | 
						|
        await bot.evaluate(
 | 
						|
            async (client, { roomRescind, clientUserId }) => {
 | 
						|
                client.kick(roomRescind, clientUserId);
 | 
						|
            },
 | 
						|
            { roomRescind, clientUserId },
 | 
						|
        );
 | 
						|
 | 
						|
        // Wait for the rescind to take effect and check the joined list once more
 | 
						|
        await expect(
 | 
						|
            page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
 | 
						|
        ).toHaveCount(2);
 | 
						|
 | 
						|
        await checkOrder(["Room to Join", "Test Room"], page);
 | 
						|
    });
 | 
						|
 | 
						|
    test("should show a favourite DM only in the favourite sublist", async ({ page, app }) => {
 | 
						|
        const roomId = await app.client.createRoom({
 | 
						|
            name: "Favourite DM",
 | 
						|
            is_direct: true,
 | 
						|
        });
 | 
						|
        await app.client.evaluate(async (client, roomId) => {
 | 
						|
            client.setRoomTag(roomId, "m.favourite", { order: 0.5 });
 | 
						|
        }, roomId);
 | 
						|
        await expect(page.getByRole("group", { name: "Favourites" }).getByText("Favourite DM")).toBeVisible();
 | 
						|
        await expect(page.getByRole("group", { name: "People" }).getByText("Favourite DM")).not.toBeAttached();
 | 
						|
    });
 | 
						|
 | 
						|
    // Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
 | 
						|
    // This ensures we are setting RoomViewStore state correctly.
 | 
						|
    test.skip("should clear the reply to field when swapping rooms", async ({ page, app }) => {
 | 
						|
        await app.client.createRoom({ name: "Other Room" });
 | 
						|
        await expect(page.getByRole("treeitem", { name: "Other Room" })).toBeVisible();
 | 
						|
        await app.client.sendMessage(roomId, "Hello world");
 | 
						|
 | 
						|
        // select the room
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room" }).click();
 | 
						|
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
 | 
						|
 | 
						|
        // click reply-to on the Hello World message
 | 
						|
        const locator = page.locator(".mx_EventTile_last");
 | 
						|
        await locator.getByText("Hello world").hover();
 | 
						|
        await locator.getByRole("button", { name: "Reply", exact: true }).click({});
 | 
						|
 | 
						|
        // check it's visible
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
 | 
						|
 | 
						|
        // now click Other Room
 | 
						|
        await page.getByRole("treeitem", { name: "Other Room" }).click();
 | 
						|
 | 
						|
        // ensure the reply-to disappears
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
 | 
						|
 | 
						|
        // click back
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room" }).click();
 | 
						|
 | 
						|
        // ensure the reply-to reappears
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
 | 
						|
    });
 | 
						|
 | 
						|
    // Regression test for https://github.com/vector-im/element-web/issues/21462
 | 
						|
    test.skip("should not cancel replies when permalinks are clicked", async ({ page, app }) => {
 | 
						|
        // we require a first message as you cannot click the permalink text with the avatar in the way
 | 
						|
        await app.client.sendMessage(roomId, "First message");
 | 
						|
        await app.client.sendMessage(roomId, "Permalink me");
 | 
						|
        await app.client.sendMessage(roomId, "Reply to me");
 | 
						|
 | 
						|
        // select the room
 | 
						|
        await page.getByRole("treeitem", { name: "Test Room" }).click();
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).not.toBeAttached();
 | 
						|
 | 
						|
        // click reply-to on the Reply to me message
 | 
						|
        const locator = page.locator(".mx_EventTile").last();
 | 
						|
        await locator.getByText("Reply to me").hover();
 | 
						|
        await locator.getByRole("button", { name: "Reply", exact: true }).click();
 | 
						|
 | 
						|
        // check it's visible
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
 | 
						|
 | 
						|
        // now click on the permalink for Permalink me
 | 
						|
        await page.locator(".mx_EventTile").filter({ hasText: "Permalink me" }).locator("a").dispatchEvent("click");
 | 
						|
 | 
						|
        // make sure it is now selected with the little green |
 | 
						|
        await expect(page.locator(".mx_EventTile_selected").filter({ hasText: "Permalink me" })).toBeVisible();
 | 
						|
 | 
						|
        // ensure the reply-to does not disappear
 | 
						|
        await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
 | 
						|
    });
 | 
						|
 | 
						|
    test.skip("should send unsubscribe_rooms for every room switch", async ({ page, app }) => {
 | 
						|
        // create rooms and check room names are correct
 | 
						|
        const roomIds: string[] = [];
 | 
						|
        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
 | 
						|
            const id = await app.client.createRoom({ name: fruit });
 | 
						|
            roomIds.push(id);
 | 
						|
            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
 | 
						|
        }
 | 
						|
        const [roomAId, roomPId] = roomIds;
 | 
						|
 | 
						|
        const assertUnsubExists = (request: Request, subRoomId: string, unsubRoomId: string) => {
 | 
						|
            const body = request.postDataJSON();
 | 
						|
            // There may be a request without a txn_id, ignore it, as there won't be any subscription changes
 | 
						|
            if (body.txn_id === undefined) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            expect(body.unsubscribe_rooms).toEqual([unsubRoomId]);
 | 
						|
            expect(body.room_subscriptions).not.toHaveProperty(unsubRoomId);
 | 
						|
            expect(body.room_subscriptions).toHaveProperty(subRoomId);
 | 
						|
        };
 | 
						|
 | 
						|
        let promise = page.waitForRequest(/sync/);
 | 
						|
 | 
						|
        // Select the Test Room
 | 
						|
        await page.getByRole("treeitem", { name: "Apple", exact: true }).click();
 | 
						|
 | 
						|
        // and wait for playwright to get the request
 | 
						|
        const roomSubscriptions = (await promise).postDataJSON().room_subscriptions;
 | 
						|
        expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
 | 
						|
 | 
						|
        // Switch to another room
 | 
						|
        promise = page.waitForRequest(/sync/);
 | 
						|
        await page.getByRole("treeitem", { name: "Pineapple", exact: true }).click();
 | 
						|
        assertUnsubExists(await promise, roomPId, roomAId);
 | 
						|
 | 
						|
        // And switch to even another room
 | 
						|
        promise = page.waitForRequest(/sync/);
 | 
						|
        await page.getByRole("treeitem", { name: "Apple", exact: true }).click();
 | 
						|
        assertUnsubExists(await promise, roomPId, roomAId);
 | 
						|
 | 
						|
        // TODO: Add tests for encrypted rooms
 | 
						|
    });
 | 
						|
});
 |