feat: show call participants in room list (Discord-style)
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 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, expect } from "../../element-web-test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";
|
||||
|
||||
test.describe("Composer", () => {
|
||||
test.use({
|
||||
displayName: "Janet",
|
||||
botCreateOpts: {
|
||||
displayName: "Bob",
|
||||
},
|
||||
});
|
||||
|
||||
test.use({
|
||||
room: async ({ app, user }, use) => {
|
||||
const roomId = await app.client.createRoom({ name: "Composing Room" });
|
||||
await app.viewRoomByName("Composing Room");
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ room }) => {}); // trigger room fixture
|
||||
|
||||
test.describe("CIDER", () => {
|
||||
test("sends a message when you click send or press Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
// Type a message
|
||||
await composer.pressSequentially("my message 0");
|
||||
// It has not been sent yet
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible();
|
||||
|
||||
// Click send
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
// It has been sent
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 0" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Type another and press Enter afterward
|
||||
await composer.pressSequentially("my message 1");
|
||||
await composer.press("Enter");
|
||||
// It was sent
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 1" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("can write formatted text", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
await composer.pressSequentially("my bold");
|
||||
await composer.press(`${CtrlOrMeta}+KeyB`);
|
||||
await composer.pressSequentially(" message");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
// Note: both "bold" and "message" are bold, which is probably surprising
|
||||
await expect(page.locator(".mx_EventTile_body strong", { hasText: "bold message" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("should allow user to input emoji via graphical picker", async ({ page, app }) => {
|
||||
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
|
||||
|
||||
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
|
||||
|
||||
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
|
||||
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("render emoji picker with larger viewport height", async () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
|
||||
// Mask the background of the screenshot to avoid failing the test just because some
|
||||
// other component have changed its rendering.
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker.png", {
|
||||
css: `
|
||||
.mx_ContextualMenu_background {
|
||||
background-color: magenta !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("render emoji picker with small viewport height", async () => {
|
||||
test.use({ viewport: { width: 1280, height: 360 } });
|
||||
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
|
||||
// Mask the background of the screenshot to avoid failing the test just because some
|
||||
// other component have changed its rendering.
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker-small.png", {
|
||||
css: `
|
||||
.mx_ContextualMenu_background {
|
||||
background-color: magenta !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should have focus lock in emoji picker", async ({ page, app }) => {
|
||||
const emojiButton = app.getComposer(false).getByRole("button", { name: "Emoji" });
|
||||
|
||||
// Open emoji picker by clicking the button
|
||||
await emojiButton.click();
|
||||
|
||||
// Wait for emoji picker to be visible
|
||||
const emojiPicker = page.getByTestId("mx_EmojiPicker");
|
||||
await expect(emojiPicker).toBeVisible();
|
||||
|
||||
// Get initial focused element (should be search input)
|
||||
const searchInput = emojiPicker.getByRole("textbox", { name: "Search" });
|
||||
await expect(searchInput).toBeFocused();
|
||||
|
||||
// Try to tab multiple times - focus should stay within emoji picker
|
||||
await page.keyboard.press("Tab");
|
||||
await page.keyboard.press("Tab");
|
||||
await page.keyboard.press("Tab");
|
||||
await page.keyboard.press("Tab");
|
||||
await page.keyboard.press("Tab");
|
||||
|
||||
// Verify we're still within the emoji picker (not back to composer)
|
||||
const focusedElement = await page.evaluate(() => document.activeElement?.closest(".mx_EmojiPicker"));
|
||||
expect(focusedElement).not.toBeNull();
|
||||
|
||||
// Close with Escape key
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
// Verify emoji picker is closed
|
||||
await expect(emojiPicker).not.toBeVisible();
|
||||
|
||||
// Verify focus returns to emoji button
|
||||
await expect(emojiButton).toBeFocused();
|
||||
});
|
||||
|
||||
test.describe("when Control+Enter is required to send", () => {
|
||||
test.beforeEach(async ({ app }) => {
|
||||
await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
test("only sends when you press Control+Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
// Type a message and press Enter
|
||||
await composer.pressSequentially("my message 3");
|
||||
await composer.press("Enter");
|
||||
// It has not been sent yet
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "my message 3" })).not.toBeVisible();
|
||||
|
||||
// Press Control+Enter
|
||||
await composer.press(`${CtrlOrMeta}+Enter`);
|
||||
// It was sent
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 3" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("can send mention", { tag: "@screenshot" }, async ({ page, bot, app }) => {
|
||||
// Set up a private room so we have another user to mention
|
||||
await app.client.createRoom({
|
||||
is_direct: true,
|
||||
invite: [bot.credentials.userId],
|
||||
});
|
||||
await app.viewRoomByName("Bob");
|
||||
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await composer.pressSequentially("@bob");
|
||||
|
||||
// Note that we include the user ID here as the room tile is also an 'option' role
|
||||
// with text 'Bob'
|
||||
await page.getByRole("option", { name: `Bob ${bot.credentials.userId}` }).click();
|
||||
await expect(composer.getByText("Bob")).toBeVisible();
|
||||
await expect(composer).toMatchScreenshot("mention.png");
|
||||
await composer.press("Enter");
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "Bob" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("renders emoji autocomplete", { tag: "@screenshot" }, async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
// Type ":+1" to trigger emoji autocomplete
|
||||
await composer.pressSequentially(":+1");
|
||||
|
||||
// Wait for autocomplete to appear
|
||||
const autocomplete = page.locator("#mx_Autocomplete");
|
||||
await expect(autocomplete).toBeVisible();
|
||||
|
||||
// Take a screenshot of the autocomplete
|
||||
await expect(autocomplete).toMatchScreenshot("emoji-autocomplete.png");
|
||||
});
|
||||
|
||||
test("can paste a file", async ({ page, bot, app }) => {
|
||||
// Set up a private room so we have another user to mention
|
||||
await app.client.createRoom({
|
||||
is_direct: true,
|
||||
invite: [bot.credentials.userId],
|
||||
});
|
||||
await app.viewRoomByName("Bob");
|
||||
await app.composerDragAndPasteFile("room", getSampleFilePath("riot.png"), "image/png");
|
||||
await expect(page.locator(".mx_MImageBody")).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 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, expect } from "../../element-web-test";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";
|
||||
|
||||
test.describe("Composer", () => {
|
||||
test.use({
|
||||
displayName: "Janet",
|
||||
});
|
||||
|
||||
test.use({
|
||||
room: async ({ app, user }, use) => {
|
||||
const roomId = await app.client.createRoom({ name: "Composing Room" });
|
||||
await app.viewRoomByName("Composing Room");
|
||||
await use({ roomId });
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ room }) => {}); // trigger room fixture
|
||||
|
||||
test.describe("Rich text editor", () => {
|
||||
test.use({
|
||||
labsFlags: ["feature_wysiwyg_composer"],
|
||||
});
|
||||
|
||||
test.describe("Commands", () => {
|
||||
// TODO add tests for rich text mode
|
||||
|
||||
test.describe("Plain text mode", () => {
|
||||
test("autocomplete behaviour tests", async ({ page }) => {
|
||||
// Select plain text mode after composer is ready
|
||||
await expect(page.locator("div[contenteditable=true]")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Hide formatting" }).click();
|
||||
|
||||
// Typing a single / displays the autocomplete menu and contents
|
||||
await page.getByRole("textbox").press("/");
|
||||
|
||||
// Check that the autocomplete options are visible and there are more than 0 items
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();
|
||||
|
||||
// Entering `//` or `/ ` hides the autocomplete contents
|
||||
// Add an extra slash for `//`
|
||||
await page.getByRole("textbox").press("/");
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
|
||||
// Remove the extra slash to go back to `/`
|
||||
await page.getByRole("textbox").press("Backspace");
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();
|
||||
// Add a trailing space for `/ `
|
||||
await page.getByRole("textbox").press(" ");
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
|
||||
|
||||
// Typing a command that takes no arguments (/devtools) and selecting by click works
|
||||
await page.getByRole("textbox").press("Backspace");
|
||||
await page.getByRole("textbox").pressSequentially("dev");
|
||||
await page.getByTestId("autocomplete-wrapper").getByText("/devtools").click();
|
||||
// Check it has closed the autocomplete and put the text into the composer
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).not.toBeVisible();
|
||||
await expect(page.getByRole("textbox").getByText("/devtools")).toBeVisible();
|
||||
// Send the message and check the devtools dialog appeared, then close it
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
await expect(page.getByRole("dialog").getByText("Developer Tools")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Close dialog" }).click();
|
||||
|
||||
// Typing a command that takes arguments (/spoiler) and selecting with enter works
|
||||
await page.getByRole("textbox").pressSequentially("/spoil");
|
||||
await expect(page.getByTestId("autocomplete-wrapper").getByText("/spoiler")).toBeVisible();
|
||||
await page.getByRole("textbox").press("Enter");
|
||||
// Check it has closed the autocomplete and put the text into the composer
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).not.toBeVisible();
|
||||
await expect(page.getByRole("textbox").getByText("/spoiler")).toBeVisible();
|
||||
// Enter some more text, then send the message
|
||||
await page.getByRole("textbox").pressSequentially("this is the spoiler text ");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
// Check that a spoiler item has appeared in the timeline and contains the spoiler text
|
||||
await expect(page.locator("button.mx_EventTile_spoiler")).toHaveText("this is the spoiler text");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Mentions", () => {
|
||||
// TODO add tests for rich text mode
|
||||
|
||||
test.describe("Plain text mode", () => {
|
||||
test.use({
|
||||
botCreateOpts: {
|
||||
displayName: "Bob",
|
||||
},
|
||||
});
|
||||
|
||||
test("autocomplete behaviour tests", async ({ page, app, bot: bob }) => {
|
||||
// Set up a private room so we have another user to mention
|
||||
await app.client.createRoom({
|
||||
is_direct: true,
|
||||
invite: [bob.credentials.userId],
|
||||
});
|
||||
await app.viewRoomByName("Bob");
|
||||
|
||||
// Select plain text mode after composer is ready
|
||||
await expect(page.locator("div[contenteditable=true]")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Hide formatting" }).click();
|
||||
|
||||
// Typing a single @ does not display the autocomplete menu and contents
|
||||
await page.getByRole("textbox").press("@");
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
|
||||
|
||||
// Entering the first letter of the other user's name opens the autocomplete...
|
||||
await page.getByRole("textbox").pressSequentially(bob.credentials.displayName.slice(0, 1));
|
||||
// ...with the other user name visible, and clicking that username...
|
||||
await page.getByTestId("autocomplete-wrapper").getByText(bob.credentials.displayName).click();
|
||||
// ...inserts the username into the composer
|
||||
const pill = page.getByRole("textbox").getByText(bob.credentials.displayName, { exact: false });
|
||||
await expect(pill).toHaveAttribute("contenteditable", "false");
|
||||
await expect(pill).toHaveAttribute("data-mention-type", "user");
|
||||
|
||||
// Send the message to clear the composer
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Typing an @, then other user's name, then trailing space closes the autocomplete
|
||||
await page.getByRole("textbox").pressSequentially(`@${bob.credentials.displayName} `);
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
|
||||
|
||||
// Send the message to clear the composer
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Moving the cursor back to an "incomplete" mention opens the autocomplete
|
||||
await page
|
||||
.getByRole("textbox")
|
||||
.pressSequentially(`initial text @${bob.credentials.displayName.slice(0, 1)} abc`);
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).toBeEmpty();
|
||||
// Move the cursor left by 4 to put it to: `@B| abc`, check autocomplete displays
|
||||
await page.getByRole("textbox").press("ArrowLeft");
|
||||
await page.getByRole("textbox").press("ArrowLeft");
|
||||
await page.getByRole("textbox").press("ArrowLeft");
|
||||
await page.getByRole("textbox").press("ArrowLeft");
|
||||
await expect(page.getByTestId("autocomplete-wrapper")).not.toBeEmpty();
|
||||
|
||||
// Selecting the autocomplete option using Enter inserts it into the composer
|
||||
await page.getByRole("textbox").press("Enter");
|
||||
const pill2 = page.getByRole("textbox").getByText(bob.credentials.displayName, { exact: false });
|
||||
await expect(pill2).toHaveAttribute("contenteditable", "false");
|
||||
await expect(pill2).toHaveAttribute("data-mention-type", "user");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("sends a message when you click send or press Enter", async ({ page }) => {
|
||||
// Type a message
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
|
||||
// It has not been sent yet
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible();
|
||||
|
||||
// Click send
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
// It has been sent
|
||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 0")).toBeVisible();
|
||||
|
||||
// Type another
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 1");
|
||||
// Send message
|
||||
await page.locator("div[contenteditable=true]").press("Enter");
|
||||
// It was sent
|
||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 1")).toBeVisible();
|
||||
});
|
||||
|
||||
test("sends only one message when you press Enter multiple times", async ({ page }) => {
|
||||
// Type a message
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
|
||||
// It has not been sent yet
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible();
|
||||
|
||||
// Click send
|
||||
await page.locator("div[contenteditable=true]").press("Enter");
|
||||
await page.locator("div[contenteditable=true]").press("Enter");
|
||||
await page.locator("div[contenteditable=true]").press("Enter");
|
||||
// It has been sent
|
||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 0")).toBeVisible();
|
||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body")).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("can write formatted text", async ({ page }) => {
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my ");
|
||||
await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+KeyB`);
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("bold");
|
||||
await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+KeyB`);
|
||||
await page.locator("div[contenteditable=true]").pressSequentially(" message");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
await expect(page.locator(".mx_EventTile_body strong").getByText("bold")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can paste a file", async ({ page, bot, app }) => {
|
||||
await app.composerDragAndPasteFile("room", getSampleFilePath("riot.png"), "image/png");
|
||||
await expect(page.locator(".mx_MImageBody")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can paste a file in a thread", async ({ page, app }) => {
|
||||
// Send a message
|
||||
const composer = page.locator("div[contenteditable=true]");
|
||||
await composer.pressSequentially("my first message");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Click reply
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply in thread" }).click();
|
||||
|
||||
await app.composerDragAndPasteFile("thread", getSampleFilePath("riot.png"), "image/png");
|
||||
await expect(page.locator(".mx_MImageBody")).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe("when Control+Enter is required to send", () => {
|
||||
test.beforeEach(async ({ app }) => {
|
||||
await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
test("only sends when you press Control+Enter", async ({ page }) => {
|
||||
// Type a message and press Enter
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 3");
|
||||
await page.locator("div[contenteditable=true]").press("Enter");
|
||||
// It has not been sent yet
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "my message 3" })).not.toBeVisible();
|
||||
|
||||
// Press Control+Enter
|
||||
await page.locator("div[contenteditable=true]").press("Control+Enter");
|
||||
// It was sent
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my message 3"),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("links", () => {
|
||||
test("create link with a forward selection", async ({ page }) => {
|
||||
// Type a message
|
||||
await page.locator("div[contenteditable=true]").pressSequentially("my message 0");
|
||||
await page.locator("div[contenteditable=true]").press(`${CtrlOrMeta}+A`);
|
||||
|
||||
// Open link modal
|
||||
await page.getByRole("button", { name: "Link" }).click();
|
||||
// Fill the link field
|
||||
await page.getByRole("textbox", { name: "Link" }).pressSequentially("https://matrix.org/");
|
||||
// Click on save
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
// Send the message
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// It was sent
|
||||
await expect(page.locator(".mx_EventTile_body a").getByText("my message 0")).toBeVisible();
|
||||
await expect(page.locator(".mx_EventTile_body a")).toHaveAttribute(
|
||||
"href",
|
||||
new RegExp("https://matrix.org/"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drafts", () => {
|
||||
test("drafts with rich and plain text", async ({ page, app }) => {
|
||||
// Set up a second room to swtich to, to test drafts
|
||||
const firstRoomname = "Composing Room";
|
||||
const secondRoomname = "Second Composing Room";
|
||||
await app.client.createRoom({ name: secondRoomname });
|
||||
|
||||
// Composer is visible
|
||||
const composer = page.locator("div[contenteditable=true]");
|
||||
await expect(composer).toBeVisible();
|
||||
|
||||
// Type some formatted text
|
||||
await composer.pressSequentially("my ");
|
||||
await composer.press(`${CtrlOrMeta}+KeyB`);
|
||||
await composer.pressSequentially("bold");
|
||||
|
||||
// Change to plain text mode
|
||||
await page.getByRole("button", { name: "Hide formatting" }).click();
|
||||
|
||||
// Change to another room and back again
|
||||
await app.viewRoomByName(secondRoomname);
|
||||
await app.viewRoomByName(firstRoomname);
|
||||
|
||||
// assert the markdown
|
||||
await expect(page.locator("div[contenteditable=true]", { hasText: "my __bold__" })).toBeVisible();
|
||||
|
||||
// Change to plain text mode and assert the markdown
|
||||
await page.getByRole("button", { name: "Show formatting" }).click();
|
||||
|
||||
// Change to another room and back again
|
||||
await app.viewRoomByName(secondRoomname);
|
||||
await app.viewRoomByName(firstRoomname);
|
||||
|
||||
// Send the message and assert the message
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my bold")).toBeVisible();
|
||||
});
|
||||
|
||||
test("draft with replies", async ({ page, app }) => {
|
||||
// Set up a second room to swtich to, to test drafts
|
||||
const firstRoomname = "Composing Room";
|
||||
const secondRoomname = "Second Composing Room";
|
||||
await app.client.createRoom({ name: secondRoomname });
|
||||
|
||||
// Composer is visible
|
||||
const composer = page.locator("div[contenteditable=true]");
|
||||
await expect(composer).toBeVisible();
|
||||
|
||||
// Send a message
|
||||
await composer.pressSequentially("my first message");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Click reply
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
|
||||
// Type reply text
|
||||
await composer.pressSequentially("my reply");
|
||||
|
||||
// Change to another room and back again
|
||||
await app.viewRoomByName(secondRoomname);
|
||||
await app.viewRoomByName(firstRoomname);
|
||||
|
||||
// Assert reply mode and reply text
|
||||
await expect(page.getByText("Replying")).toBeVisible();
|
||||
await expect(page.locator("div[contenteditable=true]", { hasText: "my reply" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("draft in threads", async ({ page, app }) => {
|
||||
// Set up a second room to swtich to, to test drafts
|
||||
const firstRoomname = "Composing Room";
|
||||
const secondRoomname = "Second Composing Room";
|
||||
await app.client.createRoom({ name: secondRoomname });
|
||||
|
||||
// Composer is visible
|
||||
const composer = page.locator("div[contenteditable=true]");
|
||||
await expect(composer).toBeVisible();
|
||||
|
||||
// Send a message
|
||||
await composer.pressSequentially("my first message");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Click reply
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply in thread" }).click();
|
||||
|
||||
const thread = page.locator(".mx_ThreadView");
|
||||
const threadComposer = thread.locator("div[contenteditable=true]");
|
||||
|
||||
// Type threaded text
|
||||
await threadComposer.pressSequentially("my threaded message");
|
||||
|
||||
// Change to another room and back again
|
||||
await app.viewRoomByName(secondRoomname);
|
||||
await app.viewRoomByName(firstRoomname);
|
||||
|
||||
// Assert threaded draft
|
||||
await expect(
|
||||
thread.locator("div[contenteditable=true]", { hasText: "my threaded message" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user