Merge branch 'main' into feat-150-battery-deeplink

This commit is contained in:
Jason Ross
2026-07-02 18:37:23 -05:00
committed by GitHub
12 changed files with 458 additions and 52 deletions
@@ -3,6 +3,7 @@ package org.libremail.ui.compose
import android.Manifest
import androidx.activity.ComponentActivity
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
@@ -169,6 +170,59 @@ class ComposeScreenTest {
assertEquals("bcc@example.com", sent.bcc)
}
@Test
fun formattingToolbar_bulletButtonMarksTheLineAndSendsItAsHtml() {
val mailRepository = FakeMailRepository()
setContent(mailRepository)
composeTestRule.onNodeWithText(string(R.string.compose_to)).performTextInput("you@example.com")
composeTestRule.onNodeWithText(string(R.string.compose_body)).performTextInput("Buy milk")
// The bullet-list button is deliberately chosen over the inline styles (bold/italic): block
// markers apply to the caret's whole line, so the end-of-text caret that performTextInput
// leaves is enough - no on-device range selection (which is unreliable in instrumented tests)
// is needed to prove that a toolbar tap flows real formatting into the sent message's HTML.
// "•" is the bullet button's own (untranslated) glyph label - see FormattingToolbar.
composeTestRule.onNodeWithText("•").performClick()
composeTestRule.onNodeWithContentDescription(string(R.string.action_send)).performClick()
composeTestRule.waitUntil(timeoutMillis = 5_000) { mailRepository.sentMessages.isNotEmpty() }
val sent = mailRepository.sentMessages.single()
// Plaintext keeps the readable "• " marker; the HTML part carries the real <ul>/<li> structure.
assertEquals("• Buy milk", sent.body)
assertTrue(
"expected bullet-list html, got ${sent.bodyHtml}",
sent.bodyHtml?.contains("<ul><li>Buy milk</li></ul>") == true,
)
}
@Test
fun formattingToolbar_buttonsCarryOnClickLabelsForAccessibility() {
setContent()
// Every toolbar button (see FormattingToolbar in RichTextEditor.kt) is a plain clickable Box with
// a bare glyph Text as its only visible content, so TalkBack relies entirely on the click action's
// label (there is no separate contentDescription) to announce what the button does.
val buttons = listOf(
"B" to R.string.format_bold,
"I" to R.string.format_italic,
"U" to R.string.format_underline,
"•" to R.string.format_bullet_list,
"1." to R.string.format_numbered_list,
"❝" to R.string.format_quote,
"🔗" to R.string.format_link,
)
buttons.forEach { (glyph, descriptionRes) ->
val config = composeTestRule.onNodeWithText(glyph).fetchSemanticsNode().config
val clickLabel = if (config.contains(SemanticsActions.OnClick)) {
config[SemanticsActions.OnClick].label
} else {
null
}
val message = "toolbar button \"$glyph\" is missing its accessibility label"
assertEquals(message, string(descriptionRes), clickLabel)
}
}
/** Matches the editable field labelled [labelRes] but not the collapsed Cc/Bcc link buttons. */
private fun editableField(labelRes: Int) = composeTestRule.onNode(hasText(string(labelRes)) and hasSetTextAction())
}
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package org.libremail.ui.onboarding
import android.Manifest
import android.os.Build
import androidx.activity.ComponentActivity
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -18,6 +20,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,6 +57,19 @@ class OnboardingFlowTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
// OnboardingWelcomeScreen requests POST_NOTIFICATIONS when it first composes (#151). On API 33+
// that runtime dialog would pop over the test, backgrounding the activity and leaving the compose
// rule with "No compose hierarchies found". Pre-grant it so the flow runs uninterrupted; the
// permission only exists on API 33+, so below TIRAMISU grant nothing (granting a nonexistent
// permission errors on older devices).
@get:Rule
val notificationPermission: GrantPermissionRule =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS)
} else {
GrantPermissionRule.grant()
}
private fun string(resId: Int, vararg args: Any) = composeTestRule.activity.getString(resId, *args)
// Generous cap for the slow, animation-disabled CI matrix emulators; waitUntil returns as soon