mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## Summary - restore three learned quick-reaction shortcuts to the desktop message action rail - keep Add reaction (`SmilePlus`, `h-4 w-4`) immediately after shortcuts and left of the restored divider - preserve Reply, Copy link, More, custom emoji rendering, accessibility labels, and responsive behavior - hide shortcuts and the divider below `sm`, while keeping Add reaction available ## Tests At `24c4732c2341b846dccf4ae8fdc2455f700fc44d`: - `pnpm exec biome check src/features/messages/ui/MessageActionBar.tsx tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts` - `pnpm exec tsc --noEmit` - `pnpm test` — 5,567 passed - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts --project=smoke` — 19 passed - `pnpm exec playwright test tests/e2e/reaction-order.spec.ts --project=smoke` — 2 passed - pre-push: file-size-check, desktop-check, desktop-typecheck, desktop-test Vogue design review: **SHIP**. ## Screenshot Restored desktop message action rail: three learned quick reactions, Add reaction (`SmilePlus`), divider, Reply, Copy link, and More.  Signed-off-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz> Co-authored-by: Rivet <a08d9a8418c7ff03afe19964724c8fd87bf1776ab9e9b9cafb8cc920edd02a6e@buzz.block.builderlab.xyz>
203 lines
6.5 KiB
TypeScript
203 lines
6.5 KiB
TypeScript
import { expect, test, type Locator, type Page } from "@playwright/test";
|
|
|
|
import { KIND_HUDDLE_STARTED } from "../../src/shared/constants/kinds";
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
|
|
const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
|
|
|
async function latestClipboardWrite(page: Page) {
|
|
return page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
|
({ command }) => command === "copy_text_to_clipboard",
|
|
),
|
|
);
|
|
}
|
|
|
|
async function expectCopyLinkUnavailable(row: Locator, messageId: string) {
|
|
await row.hover();
|
|
await expect(row.getByTestId(`copy-link-message-${messageId}`)).toHaveCount(
|
|
0,
|
|
);
|
|
|
|
const moreActions = row.getByTestId(`more-actions-${messageId}`);
|
|
if (await moreActions.count()) {
|
|
await moreActions.click({ force: true });
|
|
await expect(
|
|
row.page().getByTestId(`copy-message-link-${messageId}`),
|
|
).toHaveCount(0);
|
|
await row.page().keyboard.press("Escape");
|
|
}
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.context().grantPermissions(["clipboard-read", "clipboard-write"], {
|
|
origin: "http://127.0.0.1:4173",
|
|
});
|
|
await installMockBridge(page);
|
|
});
|
|
|
|
test("message action rail copies the same canonical thread link as More", async ({
|
|
page,
|
|
}) => {
|
|
await page.setViewportSize({ width: 900, height: 700 });
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function",
|
|
),
|
|
)
|
|
.toBe(true);
|
|
|
|
const { replyId, rootId } = await page.evaluate(() => {
|
|
const emit = window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__;
|
|
if (!emit) throw new Error("Mock message emitter is unavailable.");
|
|
const root = emit({
|
|
channelName: "general",
|
|
content: "Copy-link regression root",
|
|
id: "a".repeat(64),
|
|
});
|
|
const reply = emit({
|
|
channelName: "general",
|
|
content: "Copy-link regression reply",
|
|
id: "b".repeat(64),
|
|
parentEventId: root.id,
|
|
});
|
|
return { replyId: reply.id, rootId: root.id };
|
|
});
|
|
|
|
await page
|
|
.locator(
|
|
`[data-testid="message-thread-summary"][data-thread-head-id="${rootId}"]`,
|
|
)
|
|
.click();
|
|
const threadPanel = page.getByTestId("message-thread-panel");
|
|
const replyRow = threadPanel.locator(`[data-message-id="${replyId}"]`);
|
|
await expect(replyRow).toContainText("Copy-link regression reply");
|
|
await replyRow.hover();
|
|
|
|
const actionBar = replyRow.getByTestId(`message-action-bar-${replyId}`);
|
|
const orderedActionNames = await actionBar
|
|
.getByRole("button")
|
|
.evaluateAll((buttons) =>
|
|
buttons.map((button) => button.getAttribute("aria-label")),
|
|
);
|
|
expect(orderedActionNames).toEqual([
|
|
"React with :+1:",
|
|
"React with :heart:",
|
|
"React with :joy:",
|
|
"Open reactions",
|
|
"Reply",
|
|
"Copy link",
|
|
"More actions",
|
|
]);
|
|
|
|
const pickerButton = actionBar.getByRole("button", {
|
|
name: "Open reactions",
|
|
});
|
|
await expect(pickerButton.locator("svg")).toHaveClass(/lucide-smile-plus/);
|
|
|
|
const divider = actionBar.getByTestId("message-action-divider");
|
|
await expect(divider).toBeVisible();
|
|
const [pickerBox, dividerBox, replyBox] = await Promise.all([
|
|
actionBar.getByRole("button", { name: "Open reactions" }).boundingBox(),
|
|
divider.boundingBox(),
|
|
actionBar.getByRole("button", { name: "Reply" }).boundingBox(),
|
|
]);
|
|
expect(pickerBox).not.toBeNull();
|
|
expect(dividerBox).not.toBeNull();
|
|
expect(replyBox).not.toBeNull();
|
|
if (!pickerBox || !dividerBox || !replyBox) {
|
|
throw new Error("Message action order bounds missing.");
|
|
}
|
|
expect(pickerBox.x + pickerBox.width).toBeLessThan(dividerBox.x);
|
|
expect(dividerBox.x + dividerBox.width).toBeLessThan(replyBox.x);
|
|
|
|
const copyLink = actionBar.getByTestId(`copy-link-message-${replyId}`);
|
|
await expect(copyLink).toHaveAccessibleName("Copy link");
|
|
await copyLink.hover();
|
|
await expect(page.getByRole("tooltip", { name: "Copy link" })).toBeVisible();
|
|
|
|
const expectedLink = `buzz://message?channel=${GENERAL_CHANNEL_ID}&id=${replyId}&thread=${rootId}`;
|
|
await copyLink.click();
|
|
await expect
|
|
.poll(async () => (await latestClipboardWrite(page))?.payload.text)
|
|
.toBe(expectedLink);
|
|
await expect(
|
|
page.locator("[data-sonner-toast]").filter({
|
|
hasText: "Link copied to clipboard",
|
|
}),
|
|
).toBeVisible();
|
|
|
|
await actionBar.getByTestId(`more-actions-${replyId}`).click();
|
|
await page.getByTestId(`copy-message-link-${replyId}`).click();
|
|
await expect
|
|
.poll(async () => {
|
|
const writes = (await page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
|
({ command }) => command === "copy_text_to_clipboard",
|
|
),
|
|
)) as Array<{ payload: unknown }>;
|
|
return writes.map(({ payload }) => payload);
|
|
})
|
|
.toEqual([{ text: expectedLink }, { text: expectedLink }]);
|
|
|
|
const [barBox, panelBox] = await Promise.all([
|
|
actionBar.boundingBox(),
|
|
threadPanel.boundingBox(),
|
|
]);
|
|
expect(barBox).not.toBeNull();
|
|
expect(panelBox).not.toBeNull();
|
|
if (!barBox || !panelBox) throw new Error("Message action bounds missing.");
|
|
expect(barBox.x).toBeGreaterThanOrEqual(panelBox.x);
|
|
expect(barBox.x + barBox.width).toBeLessThanOrEqual(
|
|
panelBox.x + panelBox.width,
|
|
);
|
|
});
|
|
|
|
test("pending and huddle rows omit both copy-link surfaces", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function",
|
|
),
|
|
)
|
|
.toBe(true);
|
|
|
|
const { huddleId, pendingId } = await page.evaluate((huddleKind) => {
|
|
const emit = window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__;
|
|
if (!emit) throw new Error("Mock message emitter is unavailable.");
|
|
const pending = emit({
|
|
channelName: "general",
|
|
content: "Pending copy-link regression",
|
|
id: "c".repeat(64),
|
|
pending: true,
|
|
});
|
|
const huddle = emit({
|
|
channelName: "general",
|
|
content: JSON.stringify({
|
|
ephemeral_channel_id: "10000000-0000-4000-8000-000000000001",
|
|
}),
|
|
id: "d".repeat(64),
|
|
kind: huddleKind,
|
|
});
|
|
return { huddleId: huddle.id, pendingId: pending.id };
|
|
}, KIND_HUDDLE_STARTED);
|
|
|
|
await expectCopyLinkUnavailable(
|
|
page.locator(`[data-message-id="${pendingId}"]`),
|
|
pendingId,
|
|
);
|
|
await expectCopyLinkUnavailable(
|
|
page.locator(`[data-message-id="${huddleId}"]`),
|
|
huddleId,
|
|
);
|
|
});
|