Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.github.warnastrophy.core.ui.map.MockPermissionManager
import com.github.warnastrophy.core.util.AppConfig
import com.github.warnastrophy.core.util.BaseAndroidComposeTest
import com.github.warnastrophy.userPrefsDataStore
import kotlin.collections.isNotEmpty
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
Expand All @@ -35,10 +36,9 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
fun setup() {
val context = composeTestRule.activity.applicationContext
UserPreferencesRepositoryProvider.initLocal(context.userPrefsDataStore)
// Initialize the ActivityRepositoryProvider with mock for testing
mockActivityRepository = MockActivityRepository()
ActivityRepositoryProvider.useMock()
// Make sure user preferences are reset to defaults so tests are deterministic

runBlocking {
val repo = UserPreferencesRepositoryProvider.repository
repo.setAutoActionsEnabled(false)
Expand All @@ -49,12 +49,15 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
repo.setInactivityDetection(false)
repo.setAlertMode(false)
}
// Initialize services after prefs have been reset
InstrumentationRegistry.getInstrumentation().runOnMainSync { StateManagerService.init(context) }
StateManagerService.permissionManager =
MockPermissionManager(currentResult = PermissionResult.Granted)
StateManagerService.dangerModeService =
DangerModeService(permissionManager = StateManagerService.permissionManager)

val instrumentation = InstrumentationRegistry.getInstrumentation()
instrumentation.runOnMainSync { StateManagerService.init(context) }
instrumentation.runOnMainSync {
StateManagerService.permissionManager =
MockPermissionManager(currentResult = PermissionResult.Granted)
StateManagerService.dangerModeService =
DangerModeService(permissionManager = StateManagerService.permissionManager)
}
}

private fun createTestViewModel(repository: MockActivityRepository = mockActivityRepository) =
Expand Down Expand Up @@ -242,28 +245,26 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
val viewModel = createTestViewModel()
composeTestRule.setContent { MaterialTheme { DangerModeCard(viewModel = viewModel) } }

// Ensure the ViewModel starts with no capabilities enabled to make the test deterministic
viewModel.onCapabilitiesChanged(emptySet())
assert(viewModel.capabilitiesInternal.value.isEmpty())

// Advanced section should not be visible initially
composeTestRule
.onNodeWithTag(DangerModeTestTags.ADVANCED_SECTION, useUnmergedTree = true)
.assertDoesNotExist()

// Enable CALL capability deterministically via ViewModel
viewModel.onCapabilityToggled(DangerModeCapability.CALL)

// Wait for capabilities to be applied and advanced section to appear
val callCapabilityNode =
composeTestRule.onNodeWithTag(
DangerModeTestTags.capabilityTag(DangerModeCapability.CALL), useUnmergedTree = true)
callCapabilityNode.performClick()
composeTestRule.waitUntilWithTimeout {
viewModel.capabilitiesInternal.value.contains(DangerModeCapability.CALL)
}

composeTestRule
.onNodeWithTag(DangerModeTestTags.EXPAND_ARROW, useUnmergedTree = true)
.performClick()
composeTestRule
.onNodeWithTag(DangerModeTestTags.ADVANCED_SECTION, useUnmergedTree = true)
.assertExists()

// Wait for autoActions to be enabled by the ViewModel's logic and then assert confirmations are
// false
composeTestRule.waitUntilWithTimeout { viewModel.autoActionsEnabled.value }
assert(viewModel.autoActionsEnabled.value)
assert(!viewModel.confirmTouchRequired.value)
Expand All @@ -276,16 +277,17 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
val viewModel = createTestViewModel()
composeTestRule.setContent { MaterialTheme { DangerModeCard(viewModel = viewModel) } }

// Enable CALL capability to show advanced section deterministically via ViewModel
viewModel.onCapabilityToggled(DangerModeCapability.CALL)

// Wait until autoActions is enabled by the ViewModel
composeTestRule.waitUntilWithTimeout { viewModel.autoActionsEnabled.value }

composeTestRule
.onNodeWithTag(DangerModeTestTags.EXPAND_ARROW, useUnmergedTree = true)
.performClick()

assert(viewModel.autoActionsEnabled.value)
assert(!viewModel.confirmTouchRequired.value)
assert(!viewModel.confirmVoiceRequired.value)

// Toggle auto actions off and verify state updates correctly
viewModel.onAutoActionsEnabled(false)
composeTestRule.waitUntilWithTimeout { !viewModel.autoActionsEnabled.value }
assert(!viewModel.autoActionsEnabled.value)
Expand Down Expand Up @@ -318,11 +320,17 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
MaterialTheme { DangerModeCard(viewModel = viewModel) }
}

// Enable CALL capability to show advanced section
val callNode =
composeTestRule.onNodeWithTag(
DangerModeTestTags.capabilityTag(DangerModeCapability.CALL), useUnmergedTree = true)
callNode.performClick()
composeTestRule.waitUntilWithTimeout {
viewModel.capabilitiesInternal.value.contains(DangerModeCapability.CALL)
}

composeTestRule
.onNodeWithTag(DangerModeTestTags.EXPAND_ARROW, useUnmergedTree = true)
.performClick()

val confirmTouchSwitch =
composeTestRule.onNodeWithTag(
Expand All @@ -331,12 +339,10 @@ class DangerModeCardTest : BaseAndroidComposeTest() {
composeTestRule.onNodeWithTag(
DangerModeTestTags.CONFIRM_VOICE_SWITCH, useUnmergedTree = true)

// Toggle voice -> voice on, touch off
confirmVoiceSwitch.performClick()
confirmVoiceSwitch.assertIsOn()
confirmTouchSwitch.assertIsOff()

// Toggle touch -> touch on, voice off
confirmTouchSwitch.performClick()
confirmTouchSwitch.assertIsOn()
confirmVoiceSwitch.assertIsOff()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,13 @@ class LatestNewsCardTest : BaseAndroidComposeTest() {

Assert.assertEquals(expectedUrl, capturedUrl.get())
}

@Test
fun article_with_no_url_link_not_displayed() {
hazardService.setHazards(listOf(no_url_hazard))
composeTestRule.setContent { MaterialTheme { LatestNewsCard(hazardService) } }
composeTestRule
.onNodeWithTag(LatestNewsTestTags.LINK, useUnmergedTree = true)
.assertIsNotDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -431,4 +431,29 @@ class MapScreenTest : BaseAndroidComposeTest() {
// We don’t have a direct tag, but rendering without crash covers all lines
composeTestRule.waitForIdle()
}

@Test
fun search_bar_cleared_when_clear_button_clicked() {
setContent()
applyPerm(PermissionResult.Granted)
waitForMapReadyAndAssertVisibility()

val testInput = "Test Location"
val textField = composeTestRule.onNodeWithTag(MapScreenTestTags.SEARCH_BAR_TEXT_FIELD)

textField.assertIsDisplayed()
textField.performClick()
textField.performTextInput(testInput)
textField.assert(hasText(testInput))

val clearBtn = composeTestRule.onNodeWithTag(MapScreenTestTags.SEARCH_BAR_CLEAR_BUTTON)
clearBtn.assertIsDisplayed()
clearBtn.performClick()
composeTestRule.waitForIdle()

// Vérifications après clear
textField.assert(hasText(""))
composeTestRule.onNodeWithTag(MapScreenTestTags.SEARCH_BAR_CLEAR_BUTTON).assertIsNotDisplayed()
composeTestRule.onNodeWithTag(MapScreenTestTags.SEARCH_BAR_DROPDOWN).assertIsNotDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import androidx.compose.ui.test.performScrollToNode
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.github.warnastrophy.core.data.interfaces.UserPreferencesRepository
import com.github.warnastrophy.core.data.provider.ContactRepositoryProvider
import com.github.warnastrophy.core.data.provider.UserPreferencesRepositoryProvider
import com.github.warnastrophy.core.data.service.StateManagerService
import com.github.warnastrophy.core.permissions.PermissionResult
import com.github.warnastrophy.core.ui.components.FALLBACK_ACTIVITY_ERROR
import com.github.warnastrophy.core.ui.features.profile.preferences.DangerModePreferencesScreen
Expand All @@ -24,6 +27,7 @@ import com.github.warnastrophy.core.ui.features.profile.preferences.PendingActio
import com.github.warnastrophy.core.ui.map.MockPermissionManager
import com.github.warnastrophy.core.ui.util.MockUserPreferencesRepository
import com.github.warnastrophy.core.util.BaseAndroidComposeTest
import com.github.warnastrophy.userPrefsDataStore
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
Expand All @@ -49,6 +53,12 @@ class DangerModePreferencesScreenTest : BaseAndroidComposeTest() {
/** Helper function to set the content of the test rule with a configured ViewModel. */
private fun setContent() {
viewModel = DangerModePreferencesViewModel(mockPermissionManager, userPreferencesRepository)

val context = composeTestRule.activity.applicationContext
ContactRepositoryProvider.initLocal(context)
UserPreferencesRepositoryProvider.initLocal(context.userPrefsDataStore)
composeTestRule.runOnUiThread { StateManagerService.init(context) }

composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides composeTestRule.activity) {
DangerModePreferencesScreen(viewModel = viewModel)
Expand All @@ -67,6 +77,13 @@ class DangerModePreferencesScreenTest : BaseAndroidComposeTest() {
.performScrollToNode(hasTestTag(DangerModePreferencesScreenTestTags.AUTOMATIC_CALLS_ITEM))
}

private fun scrollToVoiceConfirmation() {
composeTestRule
.onNodeWithTag(DangerModePreferencesScreenTestTags.SCROLL_CONTAINER)
.performScrollToNode(
hasTestTag(DangerModePreferencesScreenTestTags.VOICE_CONFIRMATION_ITEM))
}

/** Applies a given [PermissionResult] to the view model. */
private fun applyPerm(permissionResult: PermissionResult) {
mockPermissionManager.setPermissionResult(permissionResult)
Expand Down Expand Up @@ -286,5 +303,34 @@ class DangerModePreferencesScreenTest : BaseAndroidComposeTest() {
}
}

@Test
fun voiceConfirmationToggle_requestsMicrophonePermission_whenNotGranted() {
mockPermissionManager.setPermissionResult(PermissionResult.Granted)
setContent()

toggleSwitch(
DangerModePreferencesScreenTestTags.ALERT_MODE_SWITCH,
{ viewModel.uiState.value.alertModeAutomaticEnabled })
toggleSwitch(
DangerModePreferencesScreenTestTags.INACTIVITY_DETECTION_SWITCH,
{ viewModel.uiState.value.inactivityDetectionEnabled })

mockPermissionManager.setPermissionResult(PermissionResult.Denied(listOf("FAKE_PERMISSION")))

nodeWithTag(DangerModePreferencesScreenTestTags.VOICE_CONFIRMATION_SWITCH)
.assertIsEnabled()
.assertIsOff()

// Simulate user granting permission
applyPerm(PermissionResult.Granted)

// Scroll, click the switch and assert on the UI node directly (voice confirmation is local
// state)
scrollToVoiceConfirmation()
nodeWithTag(DangerModePreferencesScreenTestTags.VOICE_CONFIRMATION_SWITCH).performClick()
composeTestRule.waitForIdle()
nodeWithTag(DangerModePreferencesScreenTestTags.VOICE_CONFIRMATION_SWITCH).assertIsOn()
}

private fun nodeWithTag(tag: String) = composeTestRule.onNodeWithTag(tag, useUnmergedTree = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ sealed class AppPermissions(val key: String, vararg permissionsWithNulls: String
object ForegroundServiceLocation :
AppPermissions(
key = "foreground_service_location",
if (isAtLeastSdkVersion(34)) {
Manifest.permission.FOREGROUND_SERVICE_LOCATION
Manifest.permission.POST_NOTIFICATIONS
} else {
null
})
// Add FOREGROUND_SERVICE_LOCATION on API 34+, and POST_NOTIFICATIONS on TIRAMISU+ as
// needed.
if (isAtLeastSdkVersion(34)) Manifest.permission.FOREGROUND_SERVICE_LOCATION else null,
if (isAtLeastSdkVersion(Build.VERSION_CODES.TIRAMISU))
Manifest.permission.POST_NOTIFICATIONS
else null)

/** Permissions required to send emergency messages */
object SendEmergencySms :
Expand All @@ -83,6 +83,9 @@ sealed class AppPermissions(val key: String, vararg permissionsWithNulls: String
Manifest.permission.ACCESS_FINE_LOCATION,
)

object MicrophonePermission :
AppPermissions(key = "microphone_permission", Manifest.permission.RECORD_AUDIO)

companion object {
/** A reusable check for SDK versions, annotated to help the compiler with smart casting. */
@ChecksSdkIntAtLeast(parameter = 0)
Expand Down
Loading
Loading