From a73d93ec369bd48520aeeaee84106da4e58133ff Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:26:26 +0200 Subject: [PATCH] test(android): reproduce About version clipping with system bold --- .../about-font-size/AboutFontWeightTest.java | 91 +++++++++++++++++++ .../app/e2e/mobile/about-font-size/README.md | 66 ++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 packages/app/e2e/mobile/about-font-size/AboutFontWeightTest.java create mode 100644 packages/app/e2e/mobile/about-font-size/README.md diff --git a/packages/app/e2e/mobile/about-font-size/AboutFontWeightTest.java b/packages/app/e2e/mobile/about-font-size/AboutFontWeightTest.java new file mode 100644 index 0000000000..9ae88170b2 --- /dev/null +++ b/packages/app/e2e/mobile/about-font-size/AboutFontWeightTest.java @@ -0,0 +1,91 @@ +package sh.paseo.diagnosis; + +import android.content.Context; +import android.content.res.Configuration; +import android.text.Layout; +import android.text.Spannable; +import android.util.Log; +import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import com.facebook.react.bridge.BridgeReactContext; +import com.facebook.react.common.mapbuffer.WritableMapBuffer; +import com.facebook.react.soloader.OpenSourceMergedSoMapping; +import com.facebook.react.uimanager.DisplayMetricsHolder; +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.views.text.ReactTextUpdate; +import com.facebook.react.views.text.ReactTextView; +import com.facebook.react.views.text.TextLayoutManager; +import com.facebook.soloader.SoLoader; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureOutput; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** Native reproduction for #4368. Uses the measurement and rendering paths used by RN Text. */ +@RunWith(AndroidJUnit4.class) +public class AboutFontWeightTest { + @Test public void ordinaryVersionFitsItsMeasuredBox() { checkVersion(0); } + @Test public void boldVersionFitsItsMeasuredBox() { checkVersion(300); } + + private void checkVersion(int adjustment) { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Context app = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Configuration configuration = new Configuration(app.getResources().getConfiguration()); + configuration.fontWeightAdjustment = adjustment; + Context configured = app.createConfigurationContext(configuration); + try { + SoLoader.init(app, OpenSourceMergedSoMapping.INSTANCE); + } catch (java.io.IOException error) { + throw new AssertionError("Could not initialize React Native", error); + } + DisplayMetricsHolder.initDisplayMetrics(configured); + ThemedReactContext context = new ThemedReactContext(new BridgeReactContext(configured), configured); + + WritableMapBuffer attributes = new WritableMapBuffer(); + attributes.put(4, 15.0); // TextAttributeProps.TA_KEY_FONT_SIZE + attributes.put(9, true); // TextAttributeProps.TA_KEY_ALLOW_FONT_SCALING + WritableMapBuffer fragment = new WritableMapBuffer(); + fragment.put(0, "v0.12.0"); + fragment.put(1, 1); + fragment.put(5, attributes); + WritableMapBuffer fragments = new WritableMapBuffer(); + fragments.put(0, fragment); + WritableMapBuffer text = new WritableMapBuffer(); + text.put(1, "v0.12.0"); + text.put(2, fragments); + text.put(4, attributes); + WritableMapBuffer paragraph = new WritableMapBuffer(); + paragraph.put(2, "highQuality"); + paragraph.put(5, "none"); + + long measured = TextLayoutManager.measureText(context, text, paragraph, + 1000, YogaMeasureMode.AT_MOST, 1000, YogaMeasureMode.AT_MOST, null, null); + int width = (int) Math.ceil(PixelUtil.toPixelFromDIP(YogaMeasureOutput.getWidth(measured))); + int height = (int) Math.ceil(PixelUtil.toPixelFromDIP(YogaMeasureOutput.getHeight(measured))); + Spannable content = TextLayoutManager.INSTANCE.getOrCreateSpannableForText(context, text, null); + ReactTextView view = new ReactTextView(context); + view.setId(2); // Fabric tag, matching the app's new-architecture Text path. + view.setText(new ReactTextUpdate(content, -1, false, 0, + Layout.BREAK_STRATEGY_HIGH_QUALITY, Layout.JUSTIFICATION_MODE_NONE)); + view.measure(exact(width), exact(height)); + view.layout(0, 0, width, height); + Layout rendered = view.getLayout(); + Log.i("AboutFontWeightTest", "adjustment=" + adjustment + " box=" + width + "x" + height + + " lines=" + rendered.getLineCount() + " lineWidth=" + rendered.getLineWidth(0) + + " weight=" + view.getTypeface().getWeight()); + assertEquals("The complete version should fit on its measured line", 1, rendered.getLineCount()); + assertTrue("The last digit must remain inside the allocated width", + rendered.getLineWidth(0) <= width); + assertTrue("The complete line must remain inside the allocated height", + rendered.getLineBottom(0) <= height); + }); + } + + private static int exact(int size) { + return View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY); + } +} diff --git a/packages/app/e2e/mobile/about-font-size/README.md b/packages/app/e2e/mobile/about-font-size/README.md new file mode 100644 index 0000000000..c92e68a199 --- /dev/null +++ b/packages/app/e2e/mobile/about-font-size/README.md @@ -0,0 +1,66 @@ +# About version clipping reproduction ([#4368](https://github.com/getpaseo/paseo/issues/4368)) + +This is a diagnostic branch, with no production fix. The Android test compares +React Native's measured box for `v0.12.0` with the native TextView layout that +renders the same attributed text. It runs both ordinary and system-bold text. +It does not turn off accessibility scaling or bold text. + +## Run the native test + +From the repository root, prepare a development Android project if necessary: + +```sh +cd packages/app +APP_VARIANT=development npx expo prebuild --platform android --no-install +``` + +In generated `android/app/build.gradle`, add: + +```groovy +android.defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +dependencies { + androidTestImplementation 'androidx.test:runner:1.6.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' +} +``` + +Copy this test into the generated test source set, then build the app and tests: + +```sh +mkdir -p android/app/src/androidTest/java/sh/paseo/diagnosis +cp e2e/mobile/about-font-size/AboutFontWeightTest.java android/app/src/androidTest/java/sh/paseo/diagnosis/ +cd android +APP_VARIANT=development ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest \ + -PreactNativeArchitectures=x86_64 --no-daemon --max-workers=2 -Dorg.gradle.parallel=false +adb install -r app/build/outputs/apk/debug/app-debug.apk +adb install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk +adb shell am instrument -w -e class sh.paseo.diagnosis.AboutFontWeightTest \ + sh.paseo.debug.test/androidx.test.runner.AndroidJUnitRunner +``` + +The test requires Android 12 or later for `fontWeightAdjustment`. The native +contexts set that adjustment to 0 and 300 independently; they do not change the +device setting. The selected device's density and font scale still apply. + +On Android 16 at 540 dpi and 2× font scale, both contexts receive a 301×123-pixel +box. The ordinary-weight control passes, with one rendered line. The bold test +fails with two rendered lines, so the final digit falls outside the allocated +single-line height. This isolates the measurement/rendering disagreement from +the About row's flex layout. A native dependency/build decision is still needed +before implementing a fix; changing an About-row style would leave the producer +of the incorrect measurement in place. + +## UI comparison + +Observed on Android 16, with current main's JavaScript and a freshly built native +development app: open Settings → About with 2× system font scale and 540 dpi on +a 1080-pixel-wide emulator. With system bold text, the displayed app version +loses its final digit (`v0.12.`). At the same display and text sizes without +system bold, the complete `v0.12.0` is visible. Cold-launch after configuration +changes before comparing; changing font scale in a running app also produced +temporary vertical clipping, which disappeared after a cold launch. + +This substitutes standard Android accessibility settings for Samsung Easy Mode. +It verifies the truncated-version symptom, not Samsung's complete UI or the +already-fixed home-screen overlap. The reporter used Paseo 0.7.2; this branch +uses main `dd244c076d5f17d56d5592c7fb173dc7fdc2bbae`.