mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/

mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts

And a couple of gitignore tweaks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-02-24 13:12:17 +00:00
parent e7509c92a1
commit 91a3cb03c1
3408 changed files with 28 additions and 32 deletions
@@ -0,0 +1,268 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { type Page } from "@playwright/test";
import { test as base, expect } from "../../element-web-test";
import { type Client } from "../../pages/client";
import { type ElementAppPage } from "../../pages/ElementAppPage";
import { type Bot } from "../../pages/bot";
type RoomRef = { name: string; roomId: string };
/**
* Set up for pinned message tests.
*/
export const test = base.extend<{
room1Name?: string;
room1: { name: string; roomId: string };
util: Helpers;
}>({
displayName: "Alice",
botCreateOpts: { displayName: "Other User" },
room1Name: "Room 1",
room1: async ({ room1Name: name, app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
await bot.awaitRoomMembership(roomId);
await use({ name, roomId });
},
util: async ({ page, app, bot }, use) => {
await use(new Helpers(page, app, bot));
},
});
export class Helpers {
constructor(
private page: Page,
private app: ElementAppPage,
private bot: Bot,
) {}
/**
* 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`
*/
async receiveMessages(room: RoomRef, messages: string[]) {
await this.sendMessageAsClient(this.bot, room, messages);
}
/**
* Use the supplied client to send messages or perform actions as specified by
* the supplied {@link Message} items.
*/
private async sendMessageAsClient(cli: Client, room: RoomRef, messages: string[]) {
const roomId = room.roomId;
for (const message of messages) {
await cli.sendMessage(roomId, { body: message, msgtype: "m.text" });
// TODO: without this wait, some tests that send lots of messages flake
// from time to time. I (andyb) have done some investigation, but it
// needs more work to figure out. The messages do arrive over sync, but
// they never appear in the timeline, and they never fire a
// Room.timeline event. I think this only happens with events that refer
// to other events (e.g. replies), so it might be caused by the
// referring event arriving before the referred-to event.
await this.page.waitForTimeout(100);
}
}
/**
* Open the room with the supplied name.
*/
async goTo(room: RoomRef) {
await this.app.viewRoomByName(room.name);
}
/**
* Get the timeline tile for the given message
* @param message
*/
getEventTile(message: string) {
return this.page.locator(".mx_EventTile", { hasText: message });
}
/**
* Pin the given message from the quick actions
* @param message
* @param unpin
*/
async pinMessageFromQuickActions(message: string, unpin = false) {
const timelineMessage = this.page.locator(".mx_MTextBody", { hasText: message });
await timelineMessage.hover();
await this.page.getByRole("button", { name: unpin ? "Unpin" : "Pin", exact: true }).click();
}
/**
* Pin the given messages from the quick actions
* @param messages
* @param unpin
*/
async pinMessagesFromQuickActions(messages: string[], unpin = false) {
for (const message of messages) {
await this.pinMessageFromQuickActions(message, unpin);
}
}
/**
* Pin the given message from the contextual menu
* @param message
*/
async pinMessage(message: string) {
const timelineMessage = this.page.locator(".mx_MTextBody", { hasText: message });
await timelineMessage.click({ button: "right" });
await this.page.getByRole("menuitem", { name: "Pin", exact: true }).click();
await this.assertMessageInBanner(message);
}
/**
* Pin the given messages
* @param messages
*/
async pinMessages(messages: string[]) {
for (const message of messages) {
await this.pinMessage(message);
}
}
/**
* Open the room info panel
*/
async openRoomInfo() {
await this.page.getByRole("button", { name: "Room info" }).nth(1).click();
}
/**
* Assert that the pinned count in the room info is correct
* Open the room info and check the pinned count
* @param count
*/
async assertPinnedCountInRoomInfo(count: number) {
await expect(this.page.getByRole("menuitem", { name: "Pinned messages" })).toHaveText(
`Pinned messages${count}`,
);
}
/**
* Open the pinned messages list
*/
async openPinnedMessagesList() {
await this.page.getByRole("menuitem", { name: "Pinned messages" }).click();
}
/**
* Return the right panel
*/
public getRightPanel() {
return this.page.locator("#mx_RightPanel");
}
/**
* Assert that the pinned message list contains the given messages
* @param messages
*/
async assertPinnedMessagesList(messages: string[]) {
const rightPanel = this.getRightPanel();
await expect(rightPanel.getByRole("heading", { name: "Pinned messages" })).toHaveText(
`${messages.length} Pinned messages`,
);
const list = rightPanel.getByRole("list");
await expect(list.getByRole("listitem")).toHaveCount(messages.length);
for (const message of messages) {
await expect(list.getByText(message)).toBeVisible();
}
}
/**
* Assert that the pinned message list is empty
*/
async assertEmptyPinnedMessagesList() {
const rightPanel = this.getRightPanel();
await expect(rightPanel).toMatchScreenshot(`pinned-messages-list-empty.png`);
}
/**
* Open the unpin all dialog
*/
async openUnpinAllDialog() {
await this.openRoomInfo();
await this.openPinnedMessagesList();
await this.page.getByRole("button", { name: "Unpin all" }).click();
}
/**
* Return the unpin all dialog
*/
getUnpinAllDialog() {
return this.page.locator(".mx_Dialog", { hasText: "Unpin all messages?" });
}
/**
* Click on the Continue button of the unoin all dialog
*/
async confirmUnpinAllDialog() {
await this.getUnpinAllDialog().getByRole("button", { name: "Continue" }).click();
}
/**
* Go back from the pinned messages list
*/
async backPinnedMessagesList() {
await this.page.locator("#mx_RightPanel").getByTestId("base-card-back-button").click();
}
/**
* Open the contextual menu of a message in the pin message list and click on unpin
* @param message
*/
async unpinMessageFromMessageList(message: string) {
const item = this.getRightPanel().getByRole("list").getByRole("listitem").filter({
hasText: message,
});
await item.getByRole("button").click();
await this.page.getByRole("menu", { name: "Open menu" }).getByRole("menuitem", { name: "Unpin" }).click();
}
/**
* Return the banner
* @private
*/
public getBanner() {
return this.page.getByTestId("pinned-message-banner");
}
/**
* Assert that the banner contains the given message
* @param msg
*/
async assertMessageInBanner(msg: string) {
await expect(this.getBanner().getByText(msg)).toBeVisible();
}
/**
* Return the view all button
*/
public getViewAllButton() {
return this.page.getByRole("button", { name: "View all" });
}
/**
* Return the close list button
*/
public getCloseListButton() {
return this.page.getByRole("button", { name: "Close list" });
}
}
export { expect };
@@ -0,0 +1,163 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { test } from "./index";
import { expect } from "../../element-web-test";
test.describe("Pinned messages", () => {
test(
"should show the empty state when there are no pinned messages",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.openRoomInfo();
await util.assertPinnedCountInRoomInfo(0);
await util.openPinnedMessagesList();
await util.assertEmptyPinnedMessagesList();
},
);
test(
"should pin one message and to have the pinned message badge in the timeline",
{ tag: "@screenshot" },
async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
const tile = util.getEventTile("Msg1");
await expect(tile).toMatchScreenshot("pinned-message-Msg1.png", {
mask: [tile.locator(".mx_MessageTimestamp")],
// Hide the jump to bottom button in the timeline to avoid flakiness
css: `
.mx_JumpToBottomButton {
display: none !important;
}
`,
});
},
);
test("should pin messages and show them in the room info panel", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
await util.openRoomInfo();
await util.assertPinnedCountInRoomInfo(3);
});
test("should pin messages and show them in the pinned message panel", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
// Pin the messages
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
await util.openRoomInfo();
await util.openPinnedMessagesList();
await util.assertPinnedMessagesList(["Msg1", "Msg2", "Msg4"]);
});
test("should unpin one message", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
await util.openRoomInfo();
await util.openPinnedMessagesList();
await util.unpinMessageFromMessageList("Msg2");
await util.assertPinnedMessagesList(["Msg1", "Msg4"]);
await util.backPinnedMessagesList();
await util.assertPinnedCountInRoomInfo(2);
});
test("should unpin all messages", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg4"]);
await util.openUnpinAllDialog();
await expect(util.getUnpinAllDialog()).toMatchScreenshot("unpin-all-dialog.png");
await util.confirmUnpinAllDialog();
await util.assertEmptyPinnedMessagesList();
await util.backPinnedMessagesList();
await util.assertPinnedCountInRoomInfo(0);
});
test("should be able to pin and unpin from the quick actions", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessagesFromQuickActions(["Msg1"]);
await util.openRoomInfo();
await util.assertPinnedCountInRoomInfo(1);
await util.pinMessagesFromQuickActions(["Msg1"], true);
await util.assertPinnedCountInRoomInfo(0);
});
test("should display one message in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1"]);
await util.pinMessages(["Msg1"]);
await util.assertMessageInBanner("Msg1");
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-1-Msg1.png");
});
test("should display 2 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2"]);
await util.pinMessages(["Msg1", "Msg2"]);
await util.assertMessageInBanner("Msg2");
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png");
await util.getBanner().click();
await util.assertMessageInBanner("Msg1");
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png");
await util.getBanner().click();
await util.assertMessageInBanner("Msg2");
await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png");
});
test("should display 4 messages in the banner", { tag: "@screenshot" }, async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]);
await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]);
for (const msg of ["Msg4", "Msg3", "Msg2", "Msg1"]) {
await util.assertMessageInBanner(msg);
await expect(util.getBanner()).toMatchScreenshot(`pinned-message-banner-4-${msg}.png`);
await util.getBanner().click();
}
});
test("should open the pinned messages list from the banner", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2"]);
await util.pinMessages(["Msg1", "Msg2"]);
await util.getViewAllButton().click();
await util.assertPinnedMessagesList(["Msg1", "Msg2"]);
await expect(util.getCloseListButton()).toBeVisible();
});
test("banner should listen to pinned message list", async ({ page, app, room1, util }) => {
await util.goTo(room1);
await util.receiveMessages(room1, ["Msg1", "Msg2"]);
await util.pinMessages(["Msg1", "Msg2"]);
await expect(util.getViewAllButton()).toBeVisible();
await util.openRoomInfo();
await util.openPinnedMessagesList();
await expect(util.getCloseListButton()).toBeVisible();
});
});