// UI Polish Test - Regression guards for the wasm-ui-polish fixes // (wxwidgets wasm-port commit 4a02a39c07). // // The C++ app (tests/apps/standalone/uipolish/uipolish_test.cpp) runs // self-asserting checks at boot and logs one "[UIPOLISH_TEST] : PASS/ // FAIL" line each plus a "done checks=N pass=N" summary: // clip-clear / clip-empty / clip-box DC clip box reaches the canvas // (pre-fix every clip was empty and the JS layer disabled clipping — // the collapsed wire-properties-panel bug) // blit-origin Blit honors the source DC device origin (wxBufferedDC) // mask-alpha ConvertToImage carries wxMask as alpha (infobar close btn) // scaled-dims ConvertToImage returns physical size for scaled bitmaps // checkbox-floor wxCheckBox best-height floor (selection-filter density) // statbmp-best wxStaticBitmap best size is the bundle's LOGICAL size // rounded-neg-radius negative/oversize DrawRoundedRectangle radius paints, // never throws (findings O-1 — the badge that killed the wx tick) // // statbmp-best only discriminates at devicePixelRatio >= 1.5 (pre-fix the // FromPhys path inflated 16 -> 32 there), so a second pass runs the app at // deviceScaleFactor: 2 and additionally asserts the ships the 32px // asset at 16 CSS px — the crisp-layer-eyes contract. // // Determinism: no waitForTimeout; readiness via waitForWxApp plus polling for // the app's own "done" summary line. import { test, expect, waitForWxApp } from './utils/fixtures'; import { stableShot } from './utils/element-tracker'; import { Page } from '@playwright/test'; const CHECKS = [ 'clip-clear', 'clip-empty', 'clip-box', 'blit-origin', 'mask-alpha', 'scaled-dims', 'checkbox-floor', 'statbmp-best', 'rounded-neg-radius', 'enable-propagation', ]; async function waitForDone(page: Page, logs: string[]) { await expect.poll( () => logs.some((l) => l.includes('[UIPOLISH_TEST] done checks=')), { message: 'app should log its check summary' }, ).toBe(true); } function collectUipolishLogs(page: Page): string[] { const logs: string[] = []; page.on('console', (msg) => { const text = msg.text(); if (text.includes('[UIPOLISH_TEST]')) logs.push(text); }); return logs; } function assertChecks(logs: string[]) { for (const check of CHECKS) { const pass = logs.some((l) => l.includes(`[UIPOLISH_TEST] ${check}: PASS`)); const fail = logs.find((l) => l.includes(`[UIPOLISH_TEST] ${check}: FAIL`)); expect(fail, `${check} must not FAIL (${fail ?? ''})`).toBeUndefined(); expect(pass, `${check} must PASS`).toBe(true); } } test.describe('UI Polish regression guards', () => { test('all DC/bestsize checks pass at default DPR', async ({ page }) => { const logs = collectUipolishLogs(page); await page.goto('/standalone/uipolish/uipolish_test.html'); await waitForWxApp(page); await waitForDone(page, logs); await stableShot(page, 'uipolish-01-default-dpr.png', { fullPage: true }); assertChecks(logs); // dom-nav-keys (findings O-2): keydown Enter / ArrowDown on the DOM // must surface as wxEVT_CHAR_HOOK (13 = WXK_RETURN, 317 = // WXK_DOWN); the Skip()ing hook lets wxEVT_TEXT_ENTER fire exactly once. const domIdLine = logs.find((l) => l.includes('navkeys domId=')); expect(domIdLine, 'navkeys text ctrl announced its DOM id').toBeDefined(); const domId = Number(domIdLine!.split('domId=')[1]); const fired = await page.evaluate((id) => { const el = document.querySelector(`[data-wx-dom-id="${id}"]`) as HTMLElement | null; const input = (el?.tagName === 'INPUT' ? el : el?.querySelector('input')) as HTMLInputElement | null; if (!input) return false; input.focus(); for (const key of ['Enter', 'ArrowDown']) { input.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true })); } return true; }, domId); expect(fired, 'navkeys found').toBe(true); await expect.poll(() => logs.some((l) => l.includes('charhook key=13')), { message: 'Enter reached wxEVT_CHAR_HOOK' }).toBe(true); await expect.poll(() => logs.some((l) => l.includes('charhook key=317')), { message: 'ArrowDown reached wxEVT_CHAR_HOOK' }).toBe(true); await expect.poll(() => logs.filter((l) => l.includes('[UIPOLISH_TEST] textenter')).length, { message: 'TEXT_ENTER fired once' }).toBe(1); }); }); test.describe('UI Polish regression guards @2x', () => { // statbmp-best is only a regression guard on a hi-DPI display: pre-fix the // base DoGetBestSize inflated the layout box to physical pixels (32) once // GetDPIScaleFactor() >= 1.5. test.use({ deviceScaleFactor: 2 }); test('statbmp lays out at logical size and ships the 2x asset', async ({ page }) => { const logs = collectUipolishLogs(page); await page.goto('/standalone/uipolish/uipolish_test.html'); await waitForWxApp(page); await waitForDone(page, logs); await stableShot(page, 'uipolish-02-hidpi.png', { fullPage: true }); assertChecks(logs); // The frame's wxStaticBitmap is the only control in the app: it must // ship the hi-res 32px asset but occupy the LOGICAL 16 CSS px. const img = await page.evaluate(() => { const el = Array.from(document.querySelectorAll('img.wx-dom-control')) .map((e) => e as HTMLImageElement) .find((e) => e.getBoundingClientRect().width > 0); if (!el) return null; const r = el.getBoundingClientRect(); return { naturalWidth: el.naturalWidth, cssWidth: Math.round(r.width) }; }); expect(img, 'the statbmp should exist').not.toBeNull(); expect(img!.naturalWidth, 'should ship the 2x (32px) asset').toBe(32); expect(img!.cssWidth, 'should occupy the logical 16 CSS px').toBe(16); }); });