Merge branch 'main' into fix-reader-inline-images
This commit is contained in:
@@ -67,8 +67,9 @@ class ComposeScreenTest {
|
||||
|
||||
@Before
|
||||
fun grantContactsPermission() {
|
||||
// ComposeScreen requests READ_CONTACTS on first composition; pre-grant it (before the test
|
||||
// calls setContent) so no system permission dialog appears to block the headless run.
|
||||
// ComposeScreen no longer requests READ_CONTACTS (the request moved to onboarding/#127); it
|
||||
// only reads the current grant on resume. Pre-grant it (before setContent) so contactsAllowed
|
||||
// resolves true and the autocomplete path stays exercised — no system dialog is ever shown.
|
||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
instrumentation.uiAutomation.grantRuntimePermission(
|
||||
instrumentation.targetContext.packageName,
|
||||
|
||||
+6
-1
@@ -30,6 +30,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.libremail.R
|
||||
import org.libremail.contacts.ContactsPermissionManager
|
||||
import org.libremail.data.settings.SettingsRepository
|
||||
import org.libremail.push.BatteryOptimizationManager
|
||||
import org.libremail.ui.navigation.Routes
|
||||
@@ -70,7 +71,11 @@ class BatteryOptimizationStepTest {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
|
||||
settingsRepository = SettingsRepository(context)
|
||||
runBlocking { settingsRepository.setBatteryPromptHandled(handled) }
|
||||
onboarding = OnboardingViewModel(BatteryOptimizationManager(context), settingsRepository)
|
||||
onboarding = OnboardingViewModel(
|
||||
BatteryOptimizationManager(context),
|
||||
ContactsPermissionManager(context),
|
||||
settingsRepository,
|
||||
)
|
||||
onboarding.onAccountAdded(FIRST_ACCOUNT_ID)
|
||||
|
||||
composeTestRule.setContent {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package org.libremail.ui.onboarding
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.libremail.R
|
||||
import org.libremail.ui.theme.LibreMailTheme
|
||||
|
||||
/**
|
||||
* UI tests for the onboarding contacts-access step (#127, #128). They drive the presentational
|
||||
* [ContactsAccessContent] with explicit signals so the three paths — skip, grant (the "done" state),
|
||||
* and request (with the re-ask rationale) — run deterministically without a live system permission
|
||||
* dialog (whose grant state would otherwise leak across the shared instrumentation process).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ContactsAccessStepTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private fun string(resId: Int) = composeTestRule.activity.getString(resId)
|
||||
|
||||
private fun setContent(
|
||||
granted: Boolean,
|
||||
showRationale: Boolean = false,
|
||||
onAllow: () -> Unit = {},
|
||||
onSkip: () -> Unit = {},
|
||||
onContinue: () -> Unit = {},
|
||||
) {
|
||||
composeTestRule.setContent {
|
||||
LibreMailTheme(darkTheme = false, dynamicColor = false) {
|
||||
ContactsAccessContent(
|
||||
granted = granted,
|
||||
showRationale = showRationale,
|
||||
onAllow = onAllow,
|
||||
onSkip = onSkip,
|
||||
onContinue = onContinue,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notGranted_notNow_skipsTheStep() {
|
||||
var skipped = false
|
||||
var allowed = false
|
||||
setContent(granted = false, onAllow = { allowed = true }, onSkip = { skipped = true })
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_title)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_not_now)).performClick()
|
||||
|
||||
assertTrue("Not now must invoke the skip callback", skipped)
|
||||
assertFalse("Skipping must not request the permission", allowed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notGranted_allow_triggersTheRequest() {
|
||||
var allowed = false
|
||||
setContent(granted = false, onAllow = { allowed = true })
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_allow)).performClick()
|
||||
|
||||
assertTrue("Allow must trigger the permission request", allowed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun granted_showsDoneState_andContinues() {
|
||||
var continued = false
|
||||
setContent(granted = true, onContinue = { continued = true })
|
||||
|
||||
// The "done" copy is shown and the request/skip buttons are gone.
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_done_title)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_allow)).assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_not_now)).assertDoesNotExist()
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_continue)).performClick()
|
||||
assertTrue("Continue must invoke the continue callback", continued)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reRequest_showsRationale() {
|
||||
setContent(granted = false, showRationale = true)
|
||||
|
||||
// A re-request explains itself (shouldShowRequestPermissionRationale handling, #128).
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_rationale)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.onboarding_contacts_allow)).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.libremail.R
|
||||
import org.libremail.auth.OutlookAuthManager
|
||||
import org.libremail.contacts.ContactsPermissionManager
|
||||
import org.libremail.data.settings.SettingsRepository
|
||||
import org.libremail.domain.model.Message
|
||||
import org.libremail.push.BatteryOptimizationManager
|
||||
@@ -85,6 +86,7 @@ class OnboardingFlowTest {
|
||||
val appContext = composeTestRule.activity.applicationContext
|
||||
val onboarding = OnboardingViewModel(
|
||||
BatteryOptimizationManager(appContext),
|
||||
ContactsPermissionManager(appContext),
|
||||
SettingsRepository(appContext),
|
||||
)
|
||||
composeTestRule.setContent {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package org.libremail.ui.settings
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.libremail.R
|
||||
import org.libremail.contacts.ContactPermissionState
|
||||
import org.libremail.ui.theme.LibreMailTheme
|
||||
|
||||
/**
|
||||
* UI tests for the Settings contacts-autocomplete row (#129). The row is presentational, so each of
|
||||
* its three states — on / off / blocked-in-settings — is driven directly and asserted deterministically,
|
||||
* independent of the process's real `READ_CONTACTS` grant.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ContactAutocompleteRowTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private fun string(resId: Int) = composeTestRule.activity.getString(resId)
|
||||
|
||||
private fun setContent(state: ContactPermissionState, onClick: () -> Unit = {}) {
|
||||
composeTestRule.setContent {
|
||||
LibreMailTheme(darkTheme = false, dynamicColor = false) {
|
||||
ContactAutocompleteRow(state = state, onClick = onClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun granted_showsOnSubtitle_andIsClickable() {
|
||||
var clicked = false
|
||||
setContent(ContactPermissionState.GRANTED) { clicked = true }
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete_on)).assertIsDisplayed()
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete_on)).performClick()
|
||||
assertTrue("Tapping the row must invoke onClick", clicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun denied_showsOffSubtitle() {
|
||||
setContent(ContactPermissionState.DENIED)
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete_off)).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blocked_showsBlockedSubtitle() {
|
||||
setContent(ContactPermissionState.BLOCKED)
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete_blocked)).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
package org.libremail.ui.settings
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
@@ -15,6 +16,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.libremail.R
|
||||
import org.libremail.contacts.ContactsPermissionManager
|
||||
import org.libremail.data.security.AppLockManager
|
||||
import org.libremail.data.security.DatabaseKeyCipher
|
||||
import org.libremail.data.security.DatabaseKeyStore
|
||||
@@ -57,6 +59,7 @@ class SettingsScreenTest {
|
||||
insecureDevice,
|
||||
keyStore,
|
||||
BatteryOptimizationManager(context),
|
||||
ContactsPermissionManager(context),
|
||||
SyncScheduler(Provider { WorkManager.getInstance(context) }),
|
||||
)
|
||||
}
|
||||
@@ -88,6 +91,16 @@ class SettingsScreenTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contactsAutocompleteRow_isShown() {
|
||||
// The contacts entry (#129) is wired into the real screen; it reflects the live permission
|
||||
// state, so we assert only that the row is present (state-specific rendering is covered by
|
||||
// ContactAutocompleteRowTest).
|
||||
setContent(settingsViewModel(SettingsRepository(context)))
|
||||
composeTestRule.onNodeWithText(string(R.string.settings_contacts_autocomplete))
|
||||
.performScrollTo().assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enablingAppLockWithoutSecureDevice_showsRejectionSnackbar() {
|
||||
val settingsRepository = SettingsRepository(context)
|
||||
|
||||
Reference in New Issue
Block a user