Merge branch 'main' into perf-mailbox-message-loading

This commit is contained in:
Jason Ross
2026-07-02 07:35:36 -05:00
committed by GitHub
5 changed files with 96 additions and 99 deletions
@@ -3,6 +3,7 @@ package org.libremail.ui.settings
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
@@ -14,7 +15,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.libremail.R
import org.libremail.data.security.AppLockAvailability
import org.libremail.data.security.AppLockManager
import org.libremail.data.security.DatabaseKeyCipher
import org.libremail.data.security.DatabaseKeyStore
@@ -29,8 +29,10 @@ import org.libremail.ui.theme.LibreMailTheme
import javax.inject.Provider
/**
* End-to-end test for the top-level message-downloading setting: tapping a policy must round-trip
* through the real [SettingsRepository] (DataStore), proving the UI → ViewModel → persistence wiring.
* End-to-end tests for the global settings screen. Tapping a policy must round-trip through the real
* [SettingsRepository] (DataStore), proving the UI → ViewModel → persistence wiring; and enabling
* app-lock on a device with no secure lock must surface the rejection message via a snackbar — now
* visible to Compose semantics, unlike the former Toast it replaced.
*/
@RunWith(AndroidJUnit4::class)
class SettingsScreenTest {
@@ -38,27 +40,28 @@ class SettingsScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
private val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
// Device reports no secure lock, so enabling app-lock is rejected with a message.
private val insecureDevice = object : AppLockManager {
override fun isDeviceSecure() = false
}
private fun string(resId: Int) = composeTestRule.activity.getString(resId)
@Test
fun selectingFetchPolicy_persistsThroughTheRepository() {
val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
val settingsRepository = SettingsRepository(context)
runBlocking { settingsRepository.setFetchPolicy(FetchPolicy.ALWAYS) } // known starting state
val appLockManager = object : AppLockManager {
override fun isDeviceSecure() = false
override fun availability() = AppLockAvailability.NONE_ENROLLED
}
private fun settingsViewModel(settingsRepository: SettingsRepository): SettingsViewModel {
val keyStore = DatabaseKeyStore(context, KeystoreCrypto(), DatabaseKeyCipher(), PassphraseSession())
val viewModel = SettingsViewModel(
return SettingsViewModel(
FakeAccountRepository(),
settingsRepository,
appLockManager,
insecureDevice,
keyStore,
BatteryOptimizationManager(context),
SyncScheduler(Provider { WorkManager.getInstance(context) }),
)
}
private fun setContent(viewModel: SettingsViewModel) {
composeTestRule.setContent {
LibreMailTheme(darkTheme = false, dynamicColor = false) {
SettingsScreen(
@@ -70,6 +73,13 @@ class SettingsScreenTest {
)
}
}
}
@Test
fun selectingFetchPolicy_persistsThroughTheRepository() {
val settingsRepository = SettingsRepository(context)
runBlocking { settingsRepository.setFetchPolicy(FetchPolicy.ALWAYS) } // known starting state
setContent(settingsViewModel(settingsRepository))
composeTestRule.onNodeWithText(string(R.string.fetch_on_demand)).performScrollTo().performClick()
@@ -77,4 +87,20 @@ class SettingsScreenTest {
runBlocking { settingsRepository.fetchPolicy() } == FetchPolicy.ON_DEMAND
}
}
@Test
fun enablingAppLockWithoutSecureDevice_showsRejectionSnackbar() {
val settingsRepository = SettingsRepository(context)
runBlocking { settingsRepository.setAppLock(false) } // known starting state: off
setContent(settingsViewModel(settingsRepository))
// App-lock lives under the collapsed "Advanced" section: expand it, then toggle the switch on.
composeTestRule.onNodeWithText(string(R.string.settings_advanced)).performScrollTo().performClick()
composeTestRule.onNodeWithText(string(R.string.settings_adv_app_lock)).performScrollTo().performClick()
val message = string(R.string.app_lock_needs_device_lock)
composeTestRule.waitUntil(5_000) {
composeTestRule.onAllNodesWithText(message).fetchSemanticsNodes().isNotEmpty()
}
}
}