mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
331 lines
11 KiB
TypeScript
331 lines
11 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
|
|
async function getTimelineMetrics(page: import("@playwright/test").Page) {
|
|
return page.getByTestId("message-timeline").evaluate((element) => {
|
|
const timeline = element as HTMLDivElement;
|
|
|
|
return {
|
|
clientHeight: timeline.clientHeight,
|
|
scrollHeight: timeline.scrollHeight,
|
|
scrollTop: timeline.scrollTop,
|
|
distanceFromBottom:
|
|
timeline.scrollHeight - timeline.clientHeight - timeline.scrollTop,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function ensureTimelineScrollable(
|
|
page: import("@playwright/test").Page,
|
|
prefix: string,
|
|
) {
|
|
const input = page.getByTestId("message-input");
|
|
const sendButton = page.getByTestId("send-message");
|
|
|
|
for (let index = 0; index < 24; index += 1) {
|
|
const metrics = await getTimelineMetrics(page);
|
|
if (metrics.scrollHeight > metrics.clientHeight + 160) {
|
|
return;
|
|
}
|
|
|
|
const message = `${prefix} seed ${index}`;
|
|
|
|
await input.fill(message);
|
|
await sendButton.click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
}
|
|
|
|
const metrics = await getTimelineMetrics(page);
|
|
expect(metrics.scrollHeight).toBeGreaterThan(metrics.clientHeight + 160);
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await installMockBridge(page);
|
|
});
|
|
|
|
test("loads the app shell with mocked channels", async ({ page }) => {
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("app-sidebar")).toBeVisible();
|
|
await expect(page.getByTestId("stream-list")).toContainText("general");
|
|
await expect(page.getByTestId("forum-list")).toContainText("watercooler");
|
|
await expect(page.getByTestId("dm-list")).toContainText("alice-tyler");
|
|
});
|
|
|
|
test("creates a new mocked stream", async ({ page }) => {
|
|
const channelName = `release-notes-${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByRole("button", { name: "Create a stream" }).click();
|
|
await page.getByTestId("create-stream-name").fill(channelName);
|
|
await page
|
|
.getByTestId("create-stream-description")
|
|
.fill("Release coordination");
|
|
await page
|
|
.getByTestId("create-stream-form")
|
|
.getByRole("button", { name: "Create" })
|
|
.click();
|
|
|
|
await expect(page.getByTestId("stream-list")).toContainText(channelName);
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelName);
|
|
});
|
|
|
|
test("create agent supports parallelism and system prompt overrides", async ({
|
|
page,
|
|
}) => {
|
|
const agentName = `Parallel agent ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("open-agents-view").click();
|
|
await page.getByRole("button", { name: "Create agent" }).click();
|
|
|
|
await page.getByTestId("agent-name-input").fill(agentName);
|
|
await page.getByRole("button", { name: "Advanced setup" }).click();
|
|
await page.getByTestId("agent-parallelism-input").fill("3");
|
|
await page
|
|
.getByTestId("agent-system-prompt-input")
|
|
.fill("You are concise and parallelize independent work.");
|
|
await page.getByTestId("create-agent-submit").click();
|
|
|
|
await expect(
|
|
page.getByRole("heading", { name: "Agent created" }),
|
|
).toBeVisible();
|
|
await page.getByRole("button", { name: "Done" }).click();
|
|
|
|
await expect(page.getByTestId("managed-agents-table")).toContainText(
|
|
agentName,
|
|
);
|
|
const inlineLog = page
|
|
.getByTestId("managed-agents-table")
|
|
.getByTestId("managed-agent-log-content");
|
|
|
|
await expect(inlineLog).toContainText("parallelism=3");
|
|
await expect(inlineLog).toContainText("system prompt override configured");
|
|
});
|
|
|
|
test("opens a mocked channel from the home feed", async ({ page }) => {
|
|
const mentionsSection = page.locator("section").filter({
|
|
has: page.getByRole("heading", { name: "Mentions" }),
|
|
});
|
|
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("chat-title")).toHaveText("Home");
|
|
await expect(page.getByRole("heading", { name: "Mentions" })).toBeVisible();
|
|
await expect(
|
|
page.getByText("Please review the release checklist."),
|
|
).toBeVisible();
|
|
|
|
await mentionsSection.getByRole("button", { name: "Open general" }).click();
|
|
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
"Welcome to #general",
|
|
);
|
|
});
|
|
|
|
test("home feed renders resolved author labels", async ({ page }) => {
|
|
const mentionsSection = page.locator("section").filter({
|
|
has: page.getByRole("heading", { name: "Mentions" }),
|
|
});
|
|
|
|
await page.goto("/");
|
|
|
|
await expect(mentionsSection).toContainText("alice");
|
|
await expect(mentionsSection).not.toContainText("You");
|
|
});
|
|
|
|
test("opens relay-backed search from the sidebar and loads the exact result", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("open-search")).toBeVisible();
|
|
await page.keyboard.press(
|
|
process.platform === "darwin" ? "Meta+K" : "Control+K",
|
|
);
|
|
await expect(page.getByTestId("search-dialog")).toBeVisible();
|
|
|
|
await page.getByTestId("search-input").fill("shipped");
|
|
await expect(page.getByTestId("search-results")).toContainText(
|
|
"Engineering shipped the desktop build.",
|
|
);
|
|
|
|
await page
|
|
.getByTestId("search-results")
|
|
.getByText("Engineering shipped the desktop build.")
|
|
.click();
|
|
|
|
await expect(page).toHaveURL(
|
|
/#\/channels\/1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9\?messageId=mock-engineering-shipped$/,
|
|
);
|
|
await expect(page.getByTestId("chat-title")).toHaveText("engineering");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
"Engineering shipped the desktop build.",
|
|
);
|
|
});
|
|
|
|
test("search results use your resolved profile label instead of You", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
|
|
await page.keyboard.press(
|
|
process.platform === "darwin" ? "Meta+K" : "Control+K",
|
|
);
|
|
await expect(page.getByTestId("search-dialog")).toBeVisible();
|
|
|
|
await page.getByTestId("search-input").fill("welcome");
|
|
const results = page.getByTestId("search-results");
|
|
|
|
await expect(results).toContainText("Welcome to #general");
|
|
await expect(results).toContainText("npub1mock...");
|
|
await expect(results).not.toContainText("You");
|
|
});
|
|
|
|
test("opens accessible unjoined channels from search in read-only mode", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
|
|
await page.keyboard.press(
|
|
process.platform === "darwin" ? "Meta+K" : "Control+K",
|
|
);
|
|
await expect(page.getByTestId("search-dialog")).toBeVisible();
|
|
|
|
await page.getByTestId("search-input").fill("critique");
|
|
const results = page.getByTestId("search-results");
|
|
|
|
await expect(results).toContainText(
|
|
"Design critique notes for the browse flow.",
|
|
);
|
|
await results.getByText("Design critique notes for the browse flow.").click();
|
|
|
|
await expect(page.getByTestId("chat-title")).toHaveText("design");
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
"Design critique notes for the browse flow.",
|
|
);
|
|
await expect(page.getByTestId("message-input")).toBeDisabled();
|
|
|
|
await page.getByTestId("channel-management-trigger").click();
|
|
await expect(page.getByTestId("channel-management-sheet")).toBeVisible();
|
|
await expect(page.getByTestId("channel-management-join")).toBeVisible();
|
|
});
|
|
|
|
test("replaces the channel pane when switching channels", 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 page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
await expect(page.getByTestId("message-empty")).toBeVisible();
|
|
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
|
"Welcome to #general",
|
|
);
|
|
await expect(page.getByTestId("message-timeline")).toHaveCount(1);
|
|
await expect(page.getByTestId("message-timeline-day-divider")).toHaveCount(0);
|
|
|
|
await page.getByTestId("channel-engineering").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("engineering");
|
|
await expect(page.getByTestId("message-empty")).toBeVisible();
|
|
await expect(page.getByTestId("message-timeline")).toHaveCount(1);
|
|
await expect(page.getByTestId("message-timeline-day-divider")).toHaveCount(0);
|
|
});
|
|
|
|
test("sends a mocked channel message", async ({ page }) => {
|
|
const message = `Smoke message ${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);
|
|
});
|
|
|
|
test("supports multiline drafts with Ctrl+Enter and sends with Enter", async ({
|
|
page,
|
|
}) => {
|
|
const firstLine = `Shortcut smoke line one ${Date.now()}`;
|
|
const restOfLines = [
|
|
"Shortcut smoke line two",
|
|
"Shortcut smoke line three",
|
|
"Shortcut smoke line four",
|
|
"Shortcut smoke line five",
|
|
];
|
|
const input = page.getByTestId("message-input");
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(page.getByTestId("send-message")).toContainText("Send");
|
|
const initialInputHeight = await input.evaluate(
|
|
(element) => (element as HTMLTextAreaElement).clientHeight,
|
|
);
|
|
expect(initialInputHeight).toBeLessThan(40);
|
|
await input.fill(firstLine);
|
|
for (const line of restOfLines) {
|
|
await input.press("Control+Enter");
|
|
await input.type(line);
|
|
}
|
|
await expect(input).toHaveValue([firstLine, ...restOfLines].join("\n"));
|
|
const expandedInputHeight = await input.evaluate(
|
|
(element) => (element as HTMLTextAreaElement).clientHeight,
|
|
);
|
|
expect(expandedInputHeight).toBeLessThanOrEqual(100);
|
|
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
|
firstLine,
|
|
);
|
|
await input.press("Enter");
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(firstLine);
|
|
await expect(page.getByTestId("message-timeline")).toContainText(
|
|
restOfLines[restOfLines.length - 1],
|
|
);
|
|
});
|
|
|
|
test("does not shift the timeline when the composer grows", async ({
|
|
page,
|
|
}) => {
|
|
const input = page.getByTestId("message-input");
|
|
const prefix = `Composer growth ${Date.now()}`;
|
|
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await ensureTimelineScrollable(page, prefix);
|
|
await page.waitForTimeout(400);
|
|
await page.getByTestId("message-timeline").evaluate((element) => {
|
|
const timeline = element as HTMLDivElement;
|
|
timeline.scrollTop = 0;
|
|
timeline.dispatchEvent(new Event("scroll"));
|
|
});
|
|
await expect
|
|
.poll(async () => (await getTimelineMetrics(page)).distanceFromBottom)
|
|
.toBeGreaterThan(160);
|
|
const before = await getTimelineMetrics(page);
|
|
|
|
await input.fill("Composer growth line one");
|
|
await input.press("Control+Enter");
|
|
await input.type("Composer growth line two");
|
|
await input.press("Control+Enter");
|
|
await input.type("Composer growth line three");
|
|
await input.press("Control+Enter");
|
|
await input.type("Composer growth line four");
|
|
|
|
await page.waitForTimeout(1200);
|
|
|
|
const after = await getTimelineMetrics(page);
|
|
expect(after.clientHeight).toBeLessThan(before.clientHeight);
|
|
expect(Math.abs(after.scrollTop - before.scrollTop)).toBeLessThanOrEqual(2);
|
|
expect(after.distanceFromBottom).toBeGreaterThan(160);
|
|
});
|