diff --git a/desktop/src/features/messages/ui/MessageReactions.tsx b/desktop/src/features/messages/ui/MessageReactions.tsx index cbcb873f5b..d4bec8db6c 100644 --- a/desktop/src/features/messages/ui/MessageReactions.tsx +++ b/desktop/src/features/messages/ui/MessageReactions.tsx @@ -17,7 +17,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; const REACTION_PILL_BASE_CLASSES = "inline-flex h-7 items-center rounded-full border text-xs font-medium leading-none transition-colors"; -const REACTION_CUSTOM_GLYPH_CLASSES = "h-3.5 w-3.5 -translate-y-[0.5px]"; +const REACTION_CUSTOM_GLYPH_CLASSES = "h-3.5 w-3.5"; const REACTION_NATIVE_GLYPH_CLASSES = "h-3 w-3 text-xs"; const REACTION_COUNT_CLASSES = "text-muted-foreground"; const REACTION_NATIVE_COUNT_CLASSES = diff --git a/desktop/tests/e2e/custom-emoji.spec.ts b/desktop/tests/e2e/custom-emoji.spec.ts index ae20aaef25..a48799bcb2 100644 --- a/desktop/tests/e2e/custom-emoji.spec.ts +++ b/desktop/tests/e2e/custom-emoji.spec.ts @@ -19,6 +19,7 @@ import { waitForAnimations } from "../helpers/animations"; // live even in mock-bridge mode (the mock only intercepts Tauri commands), so // this spec uses the simpler mock-bridge setup like messaging.spec.ts. const SHORTCODE = "buzz"; +const MOCK_MEDIA_PROXY_PORT = 54321; async function waitForMockLiveSubscription( page: import("@playwright/test").Page, @@ -60,6 +61,16 @@ test.beforeEach(async ({ page }) => { body: '', }), ); + // Keep the reaction fixture deliberately non-square so the real renderer can + // prove object-contain letterboxes it inside the fixed square glyph box. + await page.route( + `http://127.0.0.1:${MOCK_MEDIA_PROXY_PORT}/media/**`, + (route) => + route.fulfill({ + contentType: "image/svg+xml", + body: '', + }), + ); }); test("typing a known :shortcode: renders an inline emoji node in the composer", async ({ @@ -236,7 +247,6 @@ test("native emoji-only messages leave space below the author metadata", async ( // real 127.0.0.1 URL rather than the buzz-media:// fallback. const REACTION_SHORTCODE = "react"; -const MOCK_MEDIA_PROXY_PORT = 54321; const SELECTED_ACTION_CLASS = /(^|\s)bg-secondary(\s|$)/; // A seeded message in `general` with a real 64-hex id — the only reactable // target in mock mode (getReactionTargetId() requires a 64-hex `e` tag, which @@ -336,6 +346,25 @@ test("reacting with a custom emoji renders via the loopback media proxy", async `^http://127\\.0\\.0\\.1:${MOCK_MEDIA_PROXY_PORT}/media/[\\da-f]{64}\\.png$`, ), ); + await expect(reactionPill).toHaveCSS("height", "28px"); + await expect(reactionImg).toHaveCSS("height", "14px"); + await expect(reactionImg).toHaveCSS("width", "14px"); + await expect(reactionImg).toHaveCSS("object-fit", "contain"); + await expect(reactionImg).toHaveCSS("transform", "none"); + await expect + .poll(() => + reactionImg.evaluate((image) => { + const imageRect = image.getBoundingClientRect(); + const pillRect = image.closest("button")?.getBoundingClientRect(); + if (!(image instanceof HTMLImageElement) || !pillRect) return null; + return { + naturalHeight: image.naturalHeight, + naturalWidth: image.naturalWidth, + topOffset: imageRect.top - pillRect.top, + }; + }), + ) + .toEqual({ naturalHeight: 20, naturalWidth: 40, topOffset: 7 }); await expect .poll(() => quickReactionStorageContains(page, `:${REACTION_SHORTCODE}:`)) @@ -348,6 +377,15 @@ test("reacting with a custom emoji renders via the loopback media proxy", async ); const inlineAddReactionButton = row.getByLabel("Add reaction"); + // The picker closes with the pointer/focus position depending on animation + // timing. Put the row into a deterministic idle state before checking the + // pill's pre-existing hidden behavior. + await page.mouse.move(0, 0); + await page.evaluate(() => { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }); await expect .poll(() => inlineAddReactionButton.evaluate((button) => {