Test-only additions that close real (non-coroutine) branch gaps in pure-JVM logic already ≥95% line-covered. No production changes. Validated with :app:testDebugUnitTest :app:jacocoTestReport :app:ktlintCheck :app:detekt (all green). No emulator needed.
Branch coverage: before → after (per source file, JaCoCo)
Source file
Before
After
Missed closed
richtext/RichTextHtmlParser.kt
75.3% (43 miss)
94.3% (10 miss)
−33
richtext/RichTextEditing.kt
76.5% (35 miss)
94.0% (9 miss)
−26
ui/compose/ComposeViewModel.kt
79.7% (37 miss)
91.8% (15 miss)
−22
mail/HtmlToText.kt
64.7% (12 miss)
94.1% (2 miss)
−10
reporting/DiagnosticsCollector.kt
59.4% (13 miss)
90.6% (3 miss)
−10
data/SignatureBlock.kt
64.3% (5 miss)
100% (0 miss)
−5
reporting/AppLog.kt
50.0% (4 miss)
100% (0 miss)
−4
ui/mailbox/MailboxViewModel.kt
69.2% (40 miss)
76.2% (31 miss)
−9
data/repository/MailRepositoryImpl.kt
66.0% (81 miss)
68.1% (76 miss)
−5
data/repository/AccountRepositoryImpl.kt
BR 100% / LN 98.0%
BR 100% / LN 100%
reorderAccounts line
~124 real branches closed across the batch.
What was added
RichTextHtmlParser (new RichTextHtmlParserTest): drives the CSS/attr helpers directly — parseCssColor (short/long/invalid hex, non-#), parseFontSizePt (pt/px/zero/malformed), parseInlineStyles/parseBaseStyle/parseTextAlign/richAlignFor, extractHref/extractStyleAttr/unescape — plus parser edges: unterminated tag, stray/empty/unclosed tags, <img> without/with non-cid src, <li> outside a list, unknown block tag, clamped-to-trimmed channels.
ComposeViewModel: blank draft/from nav args, resumed draft with null account, first-non-empty account wait, non-inline attachment retention, contact-search cancellation, single-field autosave detection, empty-new-composition no-op, post-send exit guard, signature-swap HTML rebuild (the "no longer ends with old block" path), remembered-font seed onto an existing HTML body.
SignatureBlock: isEmpty field independence; blank-text-but-non-blank-html block.
AppLog (new AppLogUninstalledTest): the "no buffer installed yet" path.
Deliberately skipped (with reason)
The remaining misses are not real logic branches:
MailRepositoryImpl (76) — dominated by Kotlin coroutine state-machine dispatch (withContext(Dispatchers.IO) / suspend-point resume branches; e.g. openMessage's 11 on one line) that only cover if a mock actually suspends at that exact point, plus defensive empty-group loop branches (a routing group is never empty). Role-folder logic itself is now fully covered.
DiagnosticsCollector (3) — Build.VERSION.RELEASE / MANUFACTURER / MODEL?: ""; the non-null side only occurs on-device (the JVM android.jar stub returns null), and the fields are static final so can't be set on JDK 21.
HtmlToText (2) — a toIntOrNull() null branch on a value the regex guarantees is all digits (unreachable).
A handful of RichText defensive branches (e.g. lineStartsTouching is never empty, so hasBlock's isNotEmpty() false side is dead).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #289
Test-only additions that close real (non-coroutine) branch gaps in pure-JVM logic already ≥95% line-covered. No production changes. Validated with
:app:testDebugUnitTest :app:jacocoTestReport :app:ktlintCheck :app:detekt(all green). No emulator needed.Branch coverage: before → after (per source file, JaCoCo)
richtext/RichTextHtmlParser.ktrichtext/RichTextEditing.ktui/compose/ComposeViewModel.ktmail/HtmlToText.ktreporting/DiagnosticsCollector.ktdata/SignatureBlock.ktreporting/AppLog.ktui/mailbox/MailboxViewModel.ktdata/repository/MailRepositoryImpl.ktdata/repository/AccountRepositoryImpl.kt~124 real branches closed across the batch.
What was added
RichTextHtmlParserTest): drives the CSS/attr helpers directly —parseCssColor(short/long/invalid hex, non-#),parseFontSizePt(pt/px/zero/malformed),parseInlineStyles/parseBaseStyle/parseTextAlign/richAlignFor,extractHref/extractStyleAttr/unescape— plus parser edges: unterminated tag, stray/empty/unclosed tags,<img>without/with non-cid src,<li>outside a list, unknown block tag, clamped-to-trimmed channels.removeLink(was 0% covered),applyLink/styleAt/isStyledcaret & partial-range edges, quote/ordered marker detect+remove,remap*collapse-to-null branches,insertImageunshifted-prefix branch.folderUnreadCounts/nullcurrentFolderRole, no-selection no-op, null-message error fallbacks (selection + refresh),onDrawerOpenedwithout a drawer account, non-trash/spam delete, unified-view non-inbox refresh,confirmPendingnull arm.cancelOutboxMessage(present + already-gone rows). The role-folder fallbacks (archive/reportSpam/trashmissing-folder,resolveRoleFoldercold-cache retry) were already covered by the existing suite.reorderAccounts(#240 path).isEmptyfield independence; blank-text-but-non-blank-html block.AppLogUninstalledTest): the "no buffer installed yet" path.Deliberately skipped (with reason)
The remaining misses are not real logic branches:
withContext(Dispatchers.IO)/ suspend-point resume branches; e.g.openMessage's 11 on one line) that only cover if a mock actually suspends at that exact point, plus defensive empty-grouploop branches (a routing group is never empty). Role-folder logic itself is now fully covered.combine/foldresume branches whose behaviour is asserted elsewhere.Build.VERSION.RELEASE/MANUFACTURER/MODEL?: ""; the non-null side only occurs on-device (the JVMandroid.jarstub returns null), and the fields arestatic finalso can't be set on JDK 21.toIntOrNull()null branch on a value the regex guarantees is all digits (unreachable).lineStartsTouchingis never empty, sohasBlock'sisNotEmpty()false side is dead).🤖 Generated with Claude Code