From 9c6a969c17d135d18f1d661217f3c5d966c4892a Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Thu, 2 Jul 2026 23:13:21 -0500 Subject: [PATCH] fix(compose): keep the bullet button tappable by appending the font-size control last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The font-size dropdown was inserted before the block-marker buttons, and its wide "Default"/"N pt" anchor pushed the "•" bullet button past the right edge of the horizontally-scrolling toolbar on the Pixel 2 E2E device (411dp wide, minus the compose column's 16dp padding = 379dp usable). ComposeScreenTest's formattingToolbar_bulletButtonMarksTheLineAndSendsItAsHtml taps the bullet without scrolling first, so performClick targeted a center that was clipped off-screen and the tap silently missed — the line was never marked, failing all 8 instrumented legs deterministically (expected "• Buy milk", got "Buy milk"). The block-toggle logic was never touched; this was pure toolbar overflow. Move FontSizePicker to the end of the toolbar (after the link button) so every pre-existing glyph button keeps the exact position it has on main and the bullet stays within the initial viewport. Add a comment recording the ordering constraint for future toolbar tickets. Also add a JVM unit test (RichTextEditorTest) that drives the same bullet-tap flow through applyBlock + RichTextHtml.toHtml, pinning "• Buy milk" and so a regression in that block/HTML path is caught by testDebugUnitTest without an emulator. Co-Authored-By: Claude Opus 4.8 --- .../org/libremail/ui/compose/RichTextEditor.kt | 10 ++++++++-- .../org/libremail/ui/compose/RichTextEditorTest.kt | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/libremail/ui/compose/RichTextEditor.kt b/app/src/main/kotlin/org/libremail/ui/compose/RichTextEditor.kt index 71ad4a6..166858a 100644 --- a/app/src/main/kotlin/org/libremail/ui/compose/RichTextEditor.kt +++ b/app/src/main/kotlin/org/libremail/ui/compose/RichTextEditor.kt @@ -269,8 +269,6 @@ private fun FormattingToolbar( strikethrough = true, onClick = { onToggleStyle(RichStyle.Strikethrough) }, ) - val fontSizePt = RichTextEditing.styleAt(content, start, end, RichStyle.FontSize::class.java)?.pt - FontSizePicker(selectedPt = fontSizePt, onSelect = onFontSize) val fontColorArgb = RichTextEditing.styleAt(content, start, end, RichStyle.FontColor::class.java)?.argb FormatButton( label = "A", @@ -311,6 +309,14 @@ private fun FormattingToolbar( active = false, onClick = onLink, ) + // The font-size dropdown trails every glyph button on purpose. The toolbar overflows the + // screen width and scrolls horizontally, and the compose E2E taps the "•" bullet button + // *without* scrolling first (see ComposeScreenTest.formattingToolbar_bulletButtonMarksTheLine...), + // so its click lands on the button's on-screen center. Any control inserted *before* the block + // buttons shifts them right and can push the bullet past the viewport, making that tap miss — + // so this wider control is appended last, leaving every pre-existing button in its tested spot. + val fontSizePt = RichTextEditing.styleAt(content, start, end, RichStyle.FontSize::class.java)?.pt + FontSizePicker(selectedPt = fontSizePt, onSelect = onFontSize) } } diff --git a/app/src/test/kotlin/org/libremail/ui/compose/RichTextEditorTest.kt b/app/src/test/kotlin/org/libremail/ui/compose/RichTextEditorTest.kt index 9eccb38..7b9b986 100644 --- a/app/src/test/kotlin/org/libremail/ui/compose/RichTextEditorTest.kt +++ b/app/src/test/kotlin/org/libremail/ui/compose/RichTextEditorTest.kt @@ -19,6 +19,7 @@ import org.libremail.richtext.RichSpan import org.libremail.richtext.RichStyle import org.libremail.richtext.RichTextContent import org.libremail.richtext.RichTextEditing +import org.libremail.richtext.RichTextHtml import org.libremail.richtext.imageToken import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -240,6 +241,19 @@ class RichTextEditorTest { assertEquals("1. a\n2. b", result.annotatedString.text) } + @Test + fun `applyBlock bullet on an end-of-text caret marks the line and serializes to ul li html`() { + // The JVM-layer twin of ComposeScreenTest.formattingToolbar_bulletButtonMarksTheLineAndSendsItAsHtml: + // a bullet tap on the end-of-text caret that typing leaves must mark the whole line and serialize + // to a real list. Pinning it here catches a regression in the block-toggle/HTML flow without an + // emulator; the instrumented test additionally guards that the toolbar button stays tappable. + val value = field("Buy milk", TextRange(8)) + val bulleted = applyBlock(value, BlockMarker.BULLET, linkColor, noFont) + val content = bulleted.annotatedString.toRichContent() + assertEquals("• Buy milk", content.text) + assertEquals("", RichTextHtml.toHtml(content)) + } + // --- applyLink --- @Test