Skip to content

Commit 5c87ca9

Browse files
Abbondanzometa-codesync[bot]
authored andcommitted
Guard RTL overhang handling for fixed-line text (#58685)
Summary: Pull Request resolved: #58685 Add Android regression coverage for exactly constrained RTL text that reserves start-side ink overhang while enforcing a maximum line count and ellipsizing overflow. Also cover the mixed-direction exclusion, where a shared right-side reservation must not be applied. Changelog: [Internal] ___ Differential Revision: D121670747 fbshipit-source-id: 307461e4ef24d0dad90e609aa8dd7845efc70fa7
1 parent 447ccd1 commit 5c87ca9

1 file changed

Lines changed: 72 additions & 6 deletions

File tree

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextLayoutManagerStartOverhangTest.kt‎

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react.views.text
99

10+
import android.annotation.SuppressLint
1011
import android.graphics.RectF
1112
import android.text.BoringLayout
1213
import android.text.Layout
@@ -24,6 +25,7 @@ import org.robolectric.RobolectricTestRunner
2425
import org.robolectric.annotation.Config
2526

2627
@RunWith(RobolectricTestRunner::class)
28+
@SuppressLint("NewApi")
2729
class TextLayoutManagerStartOverhangTest {
2830

2931
@Test
@@ -56,6 +58,64 @@ class TextLayoutManagerStartOverhangTest {
5658
assertThat(TextLayoutManager.getRtlRightOverhang(layout)).isEqualTo(8)
5759
}
5860

61+
@Test
62+
@Config(sdk = [35])
63+
fun `RTL overhang reservation preserves max lines and ellipsis`() {
64+
val initialLayout = mock<Layout>()
65+
whenever(initialLayout.lineCount).thenReturn(2)
66+
whenever(initialLayout.width).thenReturn(LAYOUT_WIDTH.toInt())
67+
whenever(initialLayout.getParagraphDirection(any())).thenReturn(Layout.DIR_RIGHT_TO_LEFT)
68+
whenever(initialLayout.computeDrawingBoundingBox())
69+
.thenReturn(RectF(10f, 0f, LAYOUT_WIDTH + 7.1f, 40f))
70+
var rebuiltWidth = 0
71+
72+
val layout =
73+
TextLayoutManager.adjustLayoutForRtlRightOverhang(
74+
initialLayout,
75+
LAYOUT_WIDTH.toInt(),
76+
) { adjustedWidth ->
77+
rebuiltWidth = adjustedWidth
78+
createLayout(
79+
YogaMeasureMode.EXACTLY,
80+
text =
81+
SpannableString(
82+
listOf(
83+
"\u200Ffirst paragraph",
84+
"\u200Fsecond paragraph",
85+
"\u200Fthird paragraph",
86+
)
87+
.joinToString("\n"),
88+
),
89+
layoutWidth = adjustedWidth.toFloat(),
90+
ellipsizeMode = TextUtils.TruncateAt.END,
91+
maxNumberOfLines = 2,
92+
)
93+
}
94+
95+
assertThat(rebuiltWidth).isEqualTo(192)
96+
assertThat(layout.width).isEqualTo(rebuiltWidth)
97+
assertThat(layout.lineCount).isEqualTo(2)
98+
assertThat(layout.getEllipsisCount(layout.lineCount - 1)).isGreaterThan(0)
99+
}
100+
101+
@Test
102+
@Config(sdk = [35])
103+
fun `mixed direction text does not reserve RTL right overhang`() {
104+
val initialLayout = mock<Layout>()
105+
whenever(initialLayout.lineCount).thenReturn(2)
106+
whenever(initialLayout.width).thenReturn(200)
107+
whenever(initialLayout.getParagraphDirection(0)).thenReturn(Layout.DIR_RIGHT_TO_LEFT)
108+
whenever(initialLayout.getParagraphDirection(1)).thenReturn(Layout.DIR_LEFT_TO_RIGHT)
109+
whenever(initialLayout.computeDrawingBoundingBox()).thenReturn(RectF(10f, 0f, 208f, 40f))
110+
111+
val layout =
112+
TextLayoutManager.adjustLayoutForRtlRightOverhang(initialLayout, 200) {
113+
throw AssertionError("Mixed-direction text must not be rebuilt")
114+
}
115+
116+
assertThat(layout).isSameAs(initialLayout)
117+
}
118+
59119
@Test
60120
@Config(sdk = [34])
61121
fun `EXACTLY mode remains supported before Android 15`() {
@@ -64,9 +124,15 @@ class TextLayoutManagerStartOverhangTest {
64124
assertThat(layout.width).isEqualTo(LAYOUT_WIDTH.toInt())
65125
}
66126

67-
private fun createLayout(widthMode: YogaMeasureMode): Layout {
68-
val text = SpannableString("\u0622\u064a\u0629 \u0627\u0644\u0643\u0631\u0633\u064a")
69-
val paint = TextPaint(TextPaint.ANTI_ALIAS_FLAG).apply { textSize = 26f }
127+
private fun createLayout(
128+
widthMode: YogaMeasureMode,
129+
text: SpannableString =
130+
SpannableString("\u0622\u064a\u0629 \u0627\u0644\u0643\u0631\u0633\u064a"),
131+
layoutWidth: Float = LAYOUT_WIDTH,
132+
ellipsizeMode: TextUtils.TruncateAt? = null,
133+
maxNumberOfLines: Int = 2,
134+
paint: TextPaint = TextPaint(TextPaint.ANTI_ALIAS_FLAG).apply { textSize = 26f },
135+
): Layout {
70136
val method =
71137
TextLayoutManager::class
72138
.java
@@ -91,15 +157,15 @@ class TextLayoutManagerStartOverhangTest {
91157
TextLayoutManager,
92158
text,
93159
null,
94-
LAYOUT_WIDTH,
160+
layoutWidth,
95161
widthMode,
96162
/* includeFontPadding = */ false,
97163
/* textBreakStrategy = */ Layout.BREAK_STRATEGY_HIGH_QUALITY,
98164
/* hyphenationFrequency = */ Layout.HYPHENATION_FREQUENCY_NONE,
99165
Layout.Alignment.ALIGN_NORMAL,
100166
/* justificationMode = */ 0,
101-
/* ellipsizeMode = */ null,
102-
/* maxNumberOfLines = */ 2,
167+
/* ellipsizeMode = */ ellipsizeMode,
168+
/* maxNumberOfLines = */ maxNumberOfLines,
103169
paint,
104170
) as Layout
105171
}

0 commit comments

Comments
 (0)