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
-
+
{displayName}
@@ -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"); });