mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): integer-align custom reaction emoji (#4779)
## Summary - remove the fractional half-pixel translation from custom reaction emoji - preserve the existing 28px reaction pill, 14×14 glyph box, and `object-fit: contain` - add real-app Playwright coverage for integer centering and non-square intrinsic dimensions ### Related issue None found. Follow-up to the Buzz emoji-warp investigation. ### Testing - `cd desktop && pnpm exec playwright test tests/e2e/custom-emoji.spec.ts --project=smoke` (15 passed) - `cd desktop && pnpm test` (4,171 passed) - `cd desktop && pnpm lint` (passed; two pre-existing informational `useTemplate` diagnostics) - `cd desktop && pnpm typecheck` (passed) - `cd desktop && pnpm exec biome check src/features/messages/ui/MessageReactions.tsx tests/e2e/custom-emoji.spec.ts` (passed) Independent review also mutation-tested the regression coverage by restoring the half-pixel transform and confirming the new test fails. No after screenshot is included because the patch preserves dimensions and fixes subpixel raster alignment; the real-app test asserts the mechanism directly. Validated at `bc95969b21b58d83b7f94de4ad25e499e52b35fb`. Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc
parent
ce3cf3cd25
commit
8b8d86c5d2
@@ -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 =
|
||||
|
||||
@@ -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: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><rect width="32" height="32" fill="#22c55e"/></svg>',
|
||||
}),
|
||||
);
|
||||
// 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: '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"><rect width="40" height="20" fill="#22c55e"/></svg>',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user