mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
308 lines
11 KiB
TypeScript
308 lines
11 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
import { openSettings } from "../helpers/settings";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await installMockBridge(page);
|
|
});
|
|
|
|
test("send a message and see it in timeline", async ({ page }) => {
|
|
const message = `Hello timeline ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await page.getByTestId("message-input").fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
await expect(page.getByTestId("message-row").last()).toContainText(
|
|
"npub1mock...",
|
|
);
|
|
});
|
|
|
|
test("send multiple messages in sequence", async ({ page }) => {
|
|
const ts = Date.now();
|
|
const messages = [
|
|
`First message ${ts}`,
|
|
`Second message ${ts}`,
|
|
`Third message ${ts}`,
|
|
];
|
|
const input = page.getByTestId("message-input");
|
|
const sendButton = page.getByTestId("send-message");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
for (const message of messages) {
|
|
await input.fill(message);
|
|
await sendButton.click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
}
|
|
|
|
const timeline = page.getByTestId("message-timeline");
|
|
for (const message of messages) {
|
|
await expect(timeline).toContainText(message);
|
|
}
|
|
});
|
|
|
|
test("message input clears after send", async ({ page }) => {
|
|
const message = `Clear after send ${Date.now()}`;
|
|
const input = page.getByTestId("message-input");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await input.fill(message);
|
|
await expect(input).toHaveValue(message);
|
|
await page.getByTestId("send-message").click();
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
await expect(input).toHaveValue("");
|
|
});
|
|
|
|
test("emoji picker inserts emoji into the draft and keeps focus in the composer", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
const input = page.getByTestId("message-input");
|
|
await input.fill("Ship");
|
|
|
|
await page.getByTestId("composer-emoji-button").click();
|
|
|
|
// emoji-mart renders inside a Shadow DOM web component — use the search
|
|
// input to find the rocket emoji, then click it.
|
|
const pickerEl = page.locator("em-emoji-picker");
|
|
const searchInput = pickerEl.locator("input[type='search']");
|
|
await searchInput.fill("rocket");
|
|
await pickerEl.locator("button[aria-label='🚀']").first().click();
|
|
|
|
await expect(input).toHaveValue("Ship🚀");
|
|
await expect(input).toBeFocused();
|
|
|
|
await input.pressSequentially(" now");
|
|
await expect(input).toHaveValue("Ship🚀 now");
|
|
});
|
|
|
|
test("empty message cannot be sent", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
const sendButton = page.getByTestId("send-message");
|
|
await expect(sendButton).toBeDisabled();
|
|
});
|
|
|
|
test("send message with Enter key", async ({ page }) => {
|
|
const message = `Enter key send ${Date.now()}`;
|
|
const input = page.getByTestId("message-input");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await input.fill(message);
|
|
await input.press("Enter");
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
});
|
|
|
|
test("messages persist across channel switches", async ({ page }) => {
|
|
const message = `Persist across switch ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await page.getByTestId("message-input").fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
|
|
await page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
});
|
|
|
|
test("draft is preserved when switching channels", async ({ page }) => {
|
|
const draft = `Unsent draft ${Date.now()}`;
|
|
const input = page.getByTestId("message-input");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
// Type a draft but do not send it
|
|
await input.fill(draft);
|
|
await expect(input).toHaveValue(draft);
|
|
|
|
// Switch to another channel — composer should be empty
|
|
await page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
await expect(input).toHaveValue("");
|
|
|
|
// Switch back — the draft should still be there
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(input).toHaveValue(draft);
|
|
});
|
|
|
|
test("sending a message clears the draft", async ({ page }) => {
|
|
const message = `Sent message ${Date.now()}`;
|
|
const input = page.getByTestId("message-input");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
// Type and send a message
|
|
await input.fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
|
|
// Switch away and back — composer should be empty, not restored from draft
|
|
await page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(input).toHaveValue("");
|
|
});
|
|
|
|
test("different channels have independent messages", async ({ page }) => {
|
|
const ts = Date.now();
|
|
const generalMessage = `General only ${ts}`;
|
|
const randomMessage = `Random only ${ts}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await page.getByTestId("message-input").fill(generalMessage);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
generalMessage,
|
|
);
|
|
|
|
await page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
|
generalMessage,
|
|
);
|
|
|
|
await page.getByTestId("message-input").fill(randomMessage);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
randomMessage,
|
|
);
|
|
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
generalMessage,
|
|
);
|
|
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
|
randomMessage,
|
|
);
|
|
});
|
|
|
|
test("day divider appears in timeline", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
"Welcome to #general",
|
|
);
|
|
await expect(page.getByTestId("message-timeline-day-divider")).toBeVisible();
|
|
});
|
|
|
|
test("send message to DM channel", async ({ page }) => {
|
|
const message = `DM message ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-alice-tyler").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler");
|
|
|
|
await page.getByTestId("message-input").fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
});
|
|
|
|
test("shows your avatar on your own message when profile avatar is set", async ({
|
|
page,
|
|
}) => {
|
|
const message = `Avatar message ${Date.now()}`;
|
|
const avatarUrl =
|
|
'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"%3E%3Crect width="16" height="16" rx="4" fill="%2300a36c"/%3E%3C/svg%3E';
|
|
|
|
await page.goto("/");
|
|
await openSettings(page, "profile");
|
|
await page.getByTestId("profile-avatar-url").fill(avatarUrl);
|
|
await page.getByTestId("profile-save").click();
|
|
await page.getByTestId("settings-close").click();
|
|
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await page.getByTestId("message-input").fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
|
|
const lastMessage = page.getByTestId("message-row").last();
|
|
await expect(lastMessage).toContainText(message);
|
|
await expect(lastMessage.getByTestId("message-avatar-image")).toHaveAttribute(
|
|
"src",
|
|
avatarUrl,
|
|
);
|
|
});
|
|
|
|
test("supports nested replies with visible indentation", async ({ page }) => {
|
|
const firstReply = `First threaded reply ${Date.now()}`;
|
|
const nestedReply = `Nested threaded reply ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
"Welcome to #general",
|
|
);
|
|
|
|
const rows = page.getByTestId("message-row");
|
|
const replyButtons = page.locator('[data-testid^="reply-message-"]');
|
|
|
|
await rows.first().hover();
|
|
await replyButtons.first().click();
|
|
await expect(page.getByTestId("reply-target")).toContainText("Replying to");
|
|
await page.getByTestId("message-input").fill(firstReply);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(rows.last()).toContainText(firstReply);
|
|
await expect(rows.last()).not.toContainText("Welcome to #general");
|
|
|
|
await rows.last().hover();
|
|
await replyButtons.last().click();
|
|
await expect(page.getByTestId("reply-target")).toContainText(firstReply);
|
|
await page.getByTestId("message-input").fill(nestedReply);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(rows.last()).toContainText(nestedReply);
|
|
await expect(rows.last()).not.toContainText(firstReply);
|
|
|
|
const rootBox = await rows.nth(0).boundingBox();
|
|
const firstReplyBox = await rows.nth(1).boundingBox();
|
|
const nestedReplyBox = await rows.nth(2).boundingBox();
|
|
|
|
if (!rootBox || !firstReplyBox || !nestedReplyBox) {
|
|
throw new Error("Expected reply rows to be rendered.");
|
|
}
|
|
|
|
expect(firstReplyBox.x).toBeGreaterThan(rootBox.x + 8);
|
|
expect(nestedReplyBox.x).toBeGreaterThan(firstReplyBox.x + 8);
|
|
});
|