diff --git a/desktop/src/features/messages/ui/MessageReactions.tsx b/desktop/src/features/messages/ui/MessageReactions.tsx
index 2250b3931f..cbcb873f5b 100644
--- a/desktop/src/features/messages/ui/MessageReactions.tsx
+++ b/desktop/src/features/messages/ui/MessageReactions.tsx
@@ -126,7 +126,10 @@ function ReactionPopoverContent({ reaction }: { reaction: TimelineReaction }) {
{userText} reacted with
-
@@ -504,7 +507,7 @@ function ReactionPill({
align="start"
side="top"
sideOffset={6}
- className="w-auto min-w-56 max-w-72 rounded-xl p-3"
+ className="w-72 rounded-xl p-3"
onMouseEnter={handleMouseEnter}
onMouseLeave={scheduleClose}
onOpenAutoFocus={(e) => e.preventDefault()}
diff --git a/desktop/tests/e2e/fixtures/reaction-popover/bob-avatar.jpg b/desktop/tests/e2e/fixtures/reaction-popover/bob-avatar.jpg
new file mode 100644
index 0000000000..a63c5f0af5
Binary files /dev/null and b/desktop/tests/e2e/fixtures/reaction-popover/bob-avatar.jpg differ
diff --git a/desktop/tests/e2e/fixtures/reaction-popover/teammate-alice.jpg b/desktop/tests/e2e/fixtures/reaction-popover/teammate-alice.jpg
new file mode 100644
index 0000000000..55bc501448
Binary files /dev/null and b/desktop/tests/e2e/fixtures/reaction-popover/teammate-alice.jpg differ
diff --git a/desktop/tests/e2e/reaction-names.spec.ts b/desktop/tests/e2e/reaction-names.spec.ts
index 512b913dff..8d9a1d97d1 100644
--- a/desktop/tests/e2e/reaction-names.spec.ts
+++ b/desktop/tests/e2e/reaction-names.spec.ts
@@ -1,11 +1,41 @@
import { expect, test } from "@playwright/test";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { fileURLToPath } from "node:url";
+import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
const REACTION_TARGET_CONTENT = "React to me with a custom emoji";
const REACTION_TARGET_EVENT_ID = "d".repeat(64);
const BOB_PUBKEY =
"bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260";
+const MAX_REACTION_NAME =
+ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl";
+const MAX_REACTION_AVATAR_URL =
+ "https://cdn.example.test/avatars/teammate-alice.jpg";
+const SHORT_REACTION_AVATAR_URL =
+ "https://cdn.example.test/avatars/bob-avatar.jpg";
+const AVATAR_FIXTURES = new Map([
+ [
+ MAX_REACTION_AVATAR_URL,
+ fileURLToPath(
+ new URL(
+ "./fixtures/reaction-popover/teammate-alice.jpg",
+ import.meta.url,
+ ),
+ ),
+ ],
+ [
+ SHORT_REACTION_AVATAR_URL,
+ fileURLToPath(
+ new URL("./fixtures/reaction-popover/bob-avatar.jpg", import.meta.url),
+ ),
+ ],
+]);
+const SCREENSHOT_DIR =
+ process.env.REACTION_POPOVER_SCREENSHOT_DIR ??
+ "test-results/reaction-popover-screenshots";
function reactionTargetRow(page: import("@playwright/test").Page) {
return page
@@ -14,8 +44,56 @@ function reactionTargetRow(page: import("@playwright/test").Page) {
.last();
}
+async function waitForImage(
+ image: import("@playwright/test").Locator,
+): Promise {
+ await expect(image).toBeVisible();
+ await expect
+ .poll(() =>
+ image.evaluate(
+ (element) =>
+ element instanceof HTMLImageElement &&
+ element.complete &&
+ element.naturalWidth > 0,
+ ),
+ )
+ .toBe(true);
+}
+
+async function capturePopover(
+ page: import("@playwright/test").Page,
+ popover: import("@playwright/test").Locator,
+ filename: string,
+): Promise {
+ fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
+ await waitForAnimations(page);
+ await popover.screenshot({
+ animations: "disabled",
+ path: path.join(SCREENSHOT_DIR, filename),
+ });
+}
+
test.beforeEach(async ({ page }) => {
- await installMockBridge(page);
+ await page.route("https://cdn.example.test/avatars/**", async (route) => {
+ const fixturePath = AVATAR_FIXTURES.get(route.request().url());
+ if (!fixturePath) {
+ await route.abort("blockedbyclient");
+ return;
+ }
+ await route.fulfill({
+ body: fs.readFileSync(fixturePath),
+ contentType: "image/jpeg",
+ });
+ });
+ await installMockBridge(page, {
+ searchProfiles: [
+ {
+ pubkey: BOB_PUBKEY,
+ displayName: "bob",
+ avatarUrl: SHORT_REACTION_AVATAR_URL,
+ },
+ ],
+ });
});
test("reaction popover resolves a reactor with no authored message in the window", async ({
@@ -33,21 +111,87 @@ test("reaction popover resolves a reactor with no authored message in the window
);
await page.evaluate(
- ({ pubkey, targetId }) => {
+ ({ pubkey, targetId, avatarUrl }) => {
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
channelName: "general",
- content: "🎉",
- extraTags: [["e", targetId]],
+ content: ":react:",
+ extraTags: [
+ ["e", targetId],
+ ["emoji", "react", avatarUrl],
+ ],
kind: 7,
pubkey,
});
},
- { pubkey: BOB_PUBKEY, targetId: REACTION_TARGET_EVENT_ID },
+ {
+ avatarUrl: SHORT_REACTION_AVATAR_URL,
+ pubkey: BOB_PUBKEY,
+ targetId: REACTION_TARGET_EVENT_ID,
+ },
);
const row = reactionTargetRow(page);
- const pill = row.getByRole("button", { name: "Toggle 🎉 reaction" });
+ const pill = row.getByRole("button", { name: "Toggle :react: reaction" });
await expect(pill).toBeVisible();
await pill.hover();
await expect(page.getByText("bob reacted with")).toBeVisible();
+ const popover = page
+ .locator("[data-radix-popper-content-wrapper]")
+ .filter({ hasText: "bob reacted with" });
+ const avatar = popover.locator(`img[src="${SHORT_REACTION_AVATAR_URL}"]`);
+ await waitForImage(avatar);
+ await capturePopover(page, popover, "short-name-after.png");
+});
+
+test("maximum-length reaction name wraps inside a fixed-width popover", async ({
+ page,
+}) => {
+ await page.goto("/");
+ await page.getByTestId("channel-general").click();
+ await expect(page.getByTestId("chat-title")).toHaveText("general");
+ await page.waitForFunction(
+ () =>
+ window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
+ channelName: "general",
+ kind: 7,
+ }) === true,
+ );
+
+ const reaction = `:${MAX_REACTION_NAME}:`;
+ await page.evaluate(
+ ({ content, targetId, avatarUrl }) => {
+ window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
+ channelName: "general",
+ content,
+ extraTags: [
+ ["e", targetId],
+ ["emoji", content.slice(1, -1), avatarUrl],
+ ],
+ kind: 7,
+ });
+ },
+ {
+ avatarUrl: MAX_REACTION_AVATAR_URL,
+ content: reaction,
+ targetId: REACTION_TARGET_EVENT_ID,
+ },
+ );
+
+ const pill = reactionTargetRow(page).getByRole("button", {
+ name: `Toggle ${reaction} reaction`,
+ });
+ await expect(pill).toBeVisible();
+ await pill.focus();
+
+ const popover = page
+ .locator("[data-radix-popper-content-wrapper]")
+ .filter({ hasText: reaction });
+ await expect(popover).toBeVisible();
+ await expect(popover).toHaveCSS("width", "288px");
+ const reactionName = popover.getByTestId("reaction-popover-name");
+ await expect(reactionName).toHaveCSS("word-break", "break-all");
+ await expect(reactionName).toHaveText(reaction);
+ const avatar = popover.locator(`img[src="${MAX_REACTION_AVATAR_URL}"]`);
+ await waitForImage(avatar);
+ await capturePopover(page, popover, "max-length-after.png");
});