diff --git a/desktop/src/features/messages/lib/rowHeightEstimate.test.mjs b/desktop/src/features/messages/lib/rowHeightEstimate.test.mjs index dc33da4f8..50e7c93ac 100644 --- a/desktop/src/features/messages/lib/rowHeightEstimate.test.mjs +++ b/desktop/src/features/messages/lib/rowHeightEstimate.test.mjs @@ -90,13 +90,13 @@ test("estimateRowHeight: bare URL line adds a preview card", () => { assert.ok(withUrl > withoutUrl + 50, `url ${withUrl} vs ${withoutUrl}`); }); -test("timelineRowReserveStyle: message item yields containIntrinsicSize", () => { +test("timelineRowReserveStyle: message item yields rem containIntrinsicSize", () => { const style = timelineRowReserveStyle({ kind: "message", key: "k", entry: { message: msg({ body: "hi" }), summary: null }, }); - assert.match(String(style.containIntrinsicSize), /^auto \d+px$/); + assert.match(String(style.containIntrinsicSize), /^auto \d+(?:\.\d+)?rem$/); }); test("timelineRowReserveStyle: divider is short fixed height", () => { @@ -105,5 +105,5 @@ test("timelineRowReserveStyle: divider is short fixed height", () => { key: "k", headingTimestamp: 0, }); - assert.equal(style.containIntrinsicSize, "auto 32px"); + assert.equal(style.containIntrinsicSize, "auto 2rem"); }); diff --git a/desktop/src/features/messages/lib/rowHeightEstimate.ts b/desktop/src/features/messages/lib/rowHeightEstimate.ts index c979a8ad1..199be4060 100644 --- a/desktop/src/features/messages/lib/rowHeightEstimate.ts +++ b/desktop/src/features/messages/lib/rowHeightEstimate.ts @@ -175,5 +175,5 @@ export function timelineRowReserveStyle( : item.kind === "system" ? estimateRowHeight(item.entry.message) : DIVIDER_HEIGHT; - return { containIntrinsicSize: `auto ${height}px` }; + return { containIntrinsicSize: `auto ${height / 16}rem` }; } diff --git a/desktop/tests/e2e/channels.spec.ts b/desktop/tests/e2e/channels.spec.ts index 032d2ede9..ae97643ea 100644 --- a/desktop/tests/e2e/channels.spec.ts +++ b/desktop/tests/e2e/channels.spec.ts @@ -6,6 +6,7 @@ import { installMockBridge, openChannelBrowser, } from "../helpers/bridge"; +import { expectedScaledPx } from "../helpers/css"; const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"; const MOCK_IDENTITY_PUBKEY = "deadbeef".repeat(8); @@ -276,7 +277,9 @@ async function expectSameLeftInset( throw new Error(`Could not measure ${firstTestId} against ${secondTestId}`); } - expect(Math.abs(firstBox.x - secondBox.x)).toBeLessThanOrEqual(4); + expect(Math.abs(firstBox.x - secondBox.x)).toBeLessThanOrEqual( + await expectedScaledPx(page.getByTestId(firstTestId), 4), + ); } async function expectIntroSpacedAboveDayDivider( @@ -325,9 +328,14 @@ async function expectIntroActionCardLayout( } expect(actionBox.height).toBeGreaterThan(actionBox.width); - expect(Math.round(actionBox.width)).toBe(220); - expect(Math.round(iconBox.width)).toBe(48); - expect(Math.round(iconBox.height)).toBe(48); + expect(Math.round(actionBox.width)).toBe( + Math.round(await expectedScaledPx(page.getByTestId(actionTestId), 220)), + ); + const expectedIconSize = Math.round( + await expectedScaledPx(page.getByTestId(`${actionTestId}-icon`), 48), + ); + expect(Math.round(iconBox.width)).toBe(expectedIconSize); + expect(Math.round(iconBox.height)).toBe(expectedIconSize); const introIconRadius = await page .getByTestId("message-channel-intro-icon") .evaluate((element) => window.getComputedStyle(element).borderRadius); diff --git a/desktop/tests/e2e/custom-emoji.spec.ts b/desktop/tests/e2e/custom-emoji.spec.ts index 42c7e707a..488e76bba 100644 --- a/desktop/tests/e2e/custom-emoji.spec.ts +++ b/desktop/tests/e2e/custom-emoji.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "@playwright/test"; import { installMockBridge } from "../helpers/bridge"; +import { expectedScaledPx } from "../helpers/css"; // Custom-emoji end-to-end guard. // @@ -271,14 +272,34 @@ test("reacting with a custom emoji renders via the loopback media proxy", async }), ) .toBe("0"); + const expectedInlineReactionButtonWidth = Math.round( + await expectedScaledPx(inlineAddReactionButton, 40), + ); + const minExpectedInlineReactionButtonHeight = Math.round( + await expectedScaledPx(inlineAddReactionButton, 28), + ); await expect .poll(() => inlineAddReactionButton.evaluate((button) => { const rect = button.getBoundingClientRect(); - return `${Math.round(rect.width)}x${Math.round(rect.height)}`; + return { + height: Math.round(rect.height), + width: Math.round(rect.width), + }; }), ) - .toBe("40x28"); + .toEqual({ + height: expect.any(Number), + width: expectedInlineReactionButtonWidth, + }); + await expect + .poll(() => + inlineAddReactionButton.evaluate((button) => { + const rect = button.getBoundingClientRect(); + return Math.round(rect.height); + }), + ) + .toBeGreaterThanOrEqual(minExpectedInlineReactionButtonHeight); await expect .poll(() => inlineAddReactionButton.evaluate((button) => { @@ -292,10 +313,24 @@ test("reacting with a custom emoji renders via the loopback media proxy", async .poll(() => inlineAddReactionButton.evaluate((button) => { const rect = button.getBoundingClientRect(); - return `${Math.round(rect.width)}x${Math.round(rect.height)}`; + return { + height: Math.round(rect.height), + width: Math.round(rect.width), + }; }), ) - .toBe("40x28"); + .toEqual({ + height: expect.any(Number), + width: expectedInlineReactionButtonWidth, + }); + await expect + .poll(() => + inlineAddReactionButton.evaluate((button) => { + const rect = button.getBoundingClientRect(); + return Math.round(rect.height); + }), + ) + .toBeGreaterThanOrEqual(minExpectedInlineReactionButtonHeight); // Toggle the reaction back off: click the pill, which fires remove_reaction // -> emits a kind:5 deletion targeting the reaction event. The pill must diff --git a/desktop/tests/e2e/file-attachment.spec.ts b/desktop/tests/e2e/file-attachment.spec.ts index 41eb55e52..ac3148630 100644 --- a/desktop/tests/e2e/file-attachment.spec.ts +++ b/desktop/tests/e2e/file-attachment.spec.ts @@ -47,7 +47,7 @@ test("upload a file and see a FileCard in the timeline", async ({ page }) => { // escapes the webview to the OS browser and hits a corporate CDN page. const card = page.getByTestId("file-card").last(); await expect(card).toBeVisible(); - await expectCornerRadiusPx(card, 16); + await expectCornerRadiusPx(card, 16, { scaleWithRootFont: true }); await expectSmoothCorners(card); await expect(card).toContainText("quarterly-report.pdf"); diff --git a/desktop/tests/e2e/image-attachment-gallery.spec.ts b/desktop/tests/e2e/image-attachment-gallery.spec.ts index df4cfc980..6df4eaacf 100644 --- a/desktop/tests/e2e/image-attachment-gallery.spec.ts +++ b/desktop/tests/e2e/image-attachment-gallery.spec.ts @@ -126,8 +126,10 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => { const triggers = row.getByTestId("message-image-lightbox-trigger"); await expect(triggers).toHaveCount(3); - await expectCornerRadiusPx(triggers.first(), 16); - await expectCornerRadiusPx(triggers.first().locator("img"), 16); + await expectCornerRadiusPx(triggers.first(), 16, { scaleWithRootFont: true }); + await expectCornerRadiusPx(triggers.first().locator("img"), 16, { + scaleWithRootFont: true, + }); await expectSmoothCorners(triggers.first().locator("img")); await triggers.first().click(); @@ -137,7 +139,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => { const lightboxSurface = page .locator("[data-image-lightbox-frame] > div > div") .first(); - await expectCornerRadiusPx(lightboxSurface, 16); + await expectCornerRadiusPx(lightboxSurface, 16, { scaleWithRootFont: true }); await expectSmoothCorners(lightboxSurface); await expect( page.getByRole("button", { name: "Previous image" }), @@ -174,6 +176,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => { await expectCornerRadiusPx( page.locator("[data-image-lightbox-frame] > div > div").first(), 16, + { scaleWithRootFont: true }, ); expect(Math.abs(closingFrameBox.x - currentThumbnailBox.x)).toBeLessThan(2); diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts index 1c7202125..85f843ba0 100644 --- a/desktop/tests/e2e/messaging.spec.ts +++ b/desktop/tests/e2e/messaging.spec.ts @@ -1,7 +1,11 @@ import { expect, test, type Locator } from "@playwright/test"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; -import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css"; +import { + expectCornerRadiusPx, + expectSmoothCorners, + expectedScaledPx, +} from "../helpers/css"; import { openSettings } from "../helpers/settings"; async function expectThreadReplyUnobscured(row: Locator) { @@ -179,7 +183,7 @@ test("supported link previews keep the message link visible", async ({ ).toBeVisible(); const previewCard = row.locator('[data-link-preview="github-pull-request"]'); await expect(previewCard).toBeVisible(); - await expectCornerRadiusPx(previewCard, 16); + await expectCornerRadiusPx(previewCard, 16, { scaleWithRootFont: true }); await expectSmoothCorners(previewCard); }); @@ -233,7 +237,9 @@ test("copy a rendered code block and paste it back as code", async ({ const codeBlock = page.locator("[data-code-block]"); await expect(codeBlock).toHaveCount(1); - await expectCornerRadiusPx(codeBlock.locator("pre"), 16); + await expectCornerRadiusPx(codeBlock.locator("pre"), 16, { + scaleWithRootFont: true, + }); await expectSmoothCorners(codeBlock.locator("pre")); const copyButton = page.getByLabel("Copy code block"); @@ -647,7 +653,19 @@ test("opens a single-level thread panel with inline expansion", async ({ return `${Math.round(rect.width)}x${Math.round(rect.height)}`; }), ) - .toBe("24x24"); + .toBe( + `${Math.round( + await expectedScaledPx( + rootSummaryRow.getByTestId("message-thread-summary-participant"), + 24, + ), + )}x${Math.round( + await expectedScaledPx( + rootSummaryRow.getByTestId("message-thread-summary-participant"), + 24, + ), + )}`, + ); const summaryGeometry = await measureThreadSummaryGeometry(rootSummaryRow); expect( Math.abs(summaryGeometry.authorLeft - summaryGeometry.bodyLeft), diff --git a/desktop/tests/e2e/video-attachment.spec.ts b/desktop/tests/e2e/video-attachment.spec.ts index c4bc4c70b..ff9a894b5 100644 --- a/desktop/tests/e2e/video-attachment.spec.ts +++ b/desktop/tests/e2e/video-attachment.spec.ts @@ -253,7 +253,7 @@ test("video upload previews use poster frames and inline videos open review mode const inlinePlayer = page.getByTestId("video-player").last(); const inlineSurface = inlinePlayer.locator("[data-smooth-corners]").first(); - await expectCornerRadiusPx(inlineSurface, 16); + await expectCornerRadiusPx(inlineSurface, 16, { scaleWithRootFont: true }); await expectSmoothCorners(inlineSurface); const inlineVideo = inlinePlayer.locator("video"); await inlinePlayer.getByRole("button", { name: "Play video" }).click(); diff --git a/desktop/tests/helpers/css.ts b/desktop/tests/helpers/css.ts index 0985fb788..0f0b586b5 100644 --- a/desktop/tests/helpers/css.ts +++ b/desktop/tests/helpers/css.ts @@ -1,8 +1,25 @@ import { expect, type Locator } from "@playwright/test"; +export async function currentRootFontScale(locator: Locator) { + const rootFontSize = await locator.evaluate(() => + Number.parseFloat( + window.getComputedStyle(document.documentElement).fontSize, + ), + ); + return Number.isFinite(rootFontSize) ? rootFontSize / 16 : 1; +} + +export async function expectedScaledPx( + locator: Locator, + pxAtDefaultScale: number, +) { + return pxAtDefaultScale * (await currentRootFontScale(locator)); +} + export async function expectCornerRadiusPx( locator: Locator, expectedRadiusPx: number, + options: { scaleWithRootFont?: boolean } = {}, ) { const measurement = await locator.evaluate((element) => { const style = window.getComputedStyle(element); @@ -65,13 +82,18 @@ export async function expectCornerRadiusPx( className: element.getAttribute("class") ?? "", radius, rawRadius, + rootFontSize, }; }); + const expected = options.scaleWithRootFont + ? expectedRadiusPx * (measurement.rootFontSize / 16) + : expectedRadiusPx; + expect( measurement.radius, - `Expected ${expectedRadiusPx}px corner radius, got ${measurement.rawRadius} on class "${measurement.className}".`, - ).toBeCloseTo(expectedRadiusPx, 0); + `Expected ${expected}px corner radius, got ${measurement.rawRadius} on class "${measurement.className}".`, + ).toBeCloseTo(expected, 0); } export async function expectSmoothCorners(