2023-10-03 22:34:51 +03:00
|
|
|
package com.x8bit.bitwarden
|
|
|
|
|
|
|
|
import android.content.Intent
|
2024-01-26 18:35:31 +03:00
|
|
|
import androidx.lifecycle.SavedStateHandle
|
2024-01-27 01:52:54 +03:00
|
|
|
import app.cash.turbine.test
|
2024-01-28 21:25:15 +03:00
|
|
|
import com.bitwarden.core.CipherView
|
2024-02-01 09:33:10 +03:00
|
|
|
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
2024-01-28 21:25:15 +03:00
|
|
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
|
|
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
2024-01-31 22:27:54 +03:00
|
|
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
2024-01-27 22:48:39 +03:00
|
|
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
2024-01-31 22:27:54 +03:00
|
|
|
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
2024-01-27 22:48:39 +03:00
|
|
|
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
2024-01-23 01:14:06 +03:00
|
|
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
2024-02-01 09:33:10 +03:00
|
|
|
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
|
2024-01-23 01:14:06 +03:00
|
|
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
2024-01-17 02:41:34 +03:00
|
|
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
|
|
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
|
|
|
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
2024-01-20 00:01:43 +03:00
|
|
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
2023-10-03 22:34:51 +03:00
|
|
|
import io.mockk.every
|
|
|
|
import io.mockk.mockk
|
2024-02-16 00:33:19 +03:00
|
|
|
import io.mockk.mockkStatic
|
|
|
|
import io.mockk.unmockkStatic
|
2023-10-03 22:34:51 +03:00
|
|
|
import io.mockk.verify
|
2024-01-17 02:41:34 +03:00
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
2024-01-26 19:26:17 +03:00
|
|
|
import kotlinx.coroutines.test.runTest
|
2024-02-16 00:33:19 +03:00
|
|
|
import org.junit.jupiter.api.AfterEach
|
2024-01-17 02:41:34 +03:00
|
|
|
import org.junit.jupiter.api.Assertions.assertEquals
|
2024-01-26 18:35:31 +03:00
|
|
|
import org.junit.jupiter.api.Assertions.assertNull
|
2024-02-16 00:33:19 +03:00
|
|
|
import org.junit.jupiter.api.BeforeEach
|
2024-01-17 02:41:34 +03:00
|
|
|
import org.junit.jupiter.api.Test
|
2023-10-03 22:34:51 +03:00
|
|
|
|
2024-01-17 02:41:34 +03:00
|
|
|
class MainViewModelTest : BaseViewModelTest() {
|
2023-10-03 22:34:51 +03:00
|
|
|
|
2024-01-28 21:25:15 +03:00
|
|
|
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
2024-01-17 02:41:34 +03:00
|
|
|
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
2024-01-26 19:26:17 +03:00
|
|
|
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
2024-01-17 02:41:34 +03:00
|
|
|
private val settingsRepository = mockk<SettingsRepository> {
|
|
|
|
every { appTheme } returns AppTheme.DEFAULT
|
|
|
|
every { appThemeStateFlow } returns mutableAppThemeFlow
|
2024-01-26 19:26:17 +03:00
|
|
|
every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow
|
2023-10-03 22:34:51 +03:00
|
|
|
}
|
2024-01-23 01:14:06 +03:00
|
|
|
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
2024-01-20 00:01:43 +03:00
|
|
|
private val intentManager: IntentManager = mockk {
|
|
|
|
every { getShareDataFromIntent(any()) } returns null
|
|
|
|
}
|
2024-01-26 18:35:31 +03:00
|
|
|
private val savedStateHandle = SavedStateHandle()
|
|
|
|
|
2024-02-16 00:33:19 +03:00
|
|
|
@BeforeEach
|
|
|
|
fun setup() {
|
|
|
|
mockkStatic(
|
|
|
|
Intent::getPasswordlessRequestDataIntentOrNull,
|
|
|
|
Intent::getAutofillSaveItemOrNull,
|
|
|
|
Intent::getAutofillSelectionDataOrNull,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
fun tearDown() {
|
|
|
|
unmockkStatic(
|
|
|
|
Intent::getPasswordlessRequestDataIntentOrNull,
|
|
|
|
Intent::getAutofillSaveItemOrNull,
|
|
|
|
Intent::getAutofillSelectionDataOrNull,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-26 18:35:31 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `initialization should set a saved SpecialCircumstance to the SpecialCircumstanceManager if present`() {
|
|
|
|
assertNull(specialCircumstanceManager.specialCircumstance)
|
|
|
|
|
|
|
|
val specialCircumstance = mockk<SpecialCircumstance>()
|
|
|
|
createViewModel(
|
|
|
|
initialSpecialCircumstance = specialCircumstance,
|
|
|
|
)
|
|
|
|
|
|
|
|
assertEquals(
|
|
|
|
specialCircumstance,
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-28 21:25:15 +03:00
|
|
|
@Test
|
|
|
|
fun `autofill selection updates should emit CompleteAutofill events`() = runTest {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val cipherView = mockk<CipherView>()
|
|
|
|
viewModel.eventFlow.test {
|
|
|
|
// Ignore initial screen capture event
|
|
|
|
awaitItem()
|
|
|
|
|
|
|
|
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
|
|
|
assertEquals(
|
|
|
|
MainEvent.CompleteAutofill(cipherView = cipherView),
|
|
|
|
awaitItem(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 18:35:31 +03:00
|
|
|
@Test
|
|
|
|
fun `SpecialCircumstance updates should update the SavedStateHandle`() {
|
|
|
|
createViewModel()
|
|
|
|
|
|
|
|
assertNull(savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY])
|
|
|
|
|
|
|
|
val specialCircumstance = mockk<SpecialCircumstance>()
|
|
|
|
specialCircumstanceManager.specialCircumstance = specialCircumstance
|
|
|
|
|
|
|
|
assertEquals(
|
|
|
|
specialCircumstance,
|
|
|
|
savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY],
|
|
|
|
)
|
|
|
|
}
|
2024-01-20 00:01:43 +03:00
|
|
|
|
2023-10-03 22:34:51 +03:00
|
|
|
@Test
|
2024-01-17 02:41:34 +03:00
|
|
|
fun `on AppThemeChanged should update state`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
|
|
|
|
assertEquals(
|
|
|
|
MainState(
|
|
|
|
theme = AppTheme.DEFAULT,
|
|
|
|
),
|
|
|
|
viewModel.stateFlow.value,
|
|
|
|
)
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.Internal.ThemeUpdate(
|
|
|
|
theme = AppTheme.DARK,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
MainState(
|
|
|
|
theme = AppTheme.DARK,
|
|
|
|
),
|
|
|
|
viewModel.stateFlow.value,
|
|
|
|
)
|
|
|
|
|
|
|
|
verify {
|
|
|
|
settingsRepository.appTheme
|
|
|
|
settingsRepository.appThemeStateFlow
|
2023-10-03 22:34:51 +03:00
|
|
|
}
|
2024-01-17 02:41:34 +03:00
|
|
|
}
|
|
|
|
|
2024-01-20 00:01:43 +03:00
|
|
|
@Suppress("MaxLineLength")
|
2024-01-17 02:41:34 +03:00
|
|
|
@Test
|
2024-01-20 00:01:43 +03:00
|
|
|
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
2024-01-17 02:41:34 +03:00
|
|
|
val viewModel = createViewModel()
|
2024-01-20 00:01:43 +03:00
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val shareData = mockk<IntentManager.ShareData>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
2024-01-27 22:48:39 +03:00
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
2024-01-20 00:01:43 +03:00
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveFirstIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
2024-01-23 01:14:06 +03:00
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.ShareNewSend(
|
2024-01-20 00:01:43 +03:00
|
|
|
data = shareData,
|
|
|
|
shouldFinishWhenComplete = true,
|
2024-01-23 01:14:06 +03:00
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
2024-01-20 00:01:43 +03:00
|
|
|
}
|
|
|
|
|
2024-01-27 22:48:39 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveFirstIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val autofillSelectionData = mockk<AutofillSelectionData>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
2024-01-27 22:48:39 +03:00
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveFirstIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.AutofillSelection(
|
|
|
|
autofillSelectionData = autofillSelectionData,
|
|
|
|
shouldFinishWhenComplete = true,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-31 22:27:54 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveFirstIntent with an autofill save item should set the special circumstance to AutofillSave`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val autofillSaveItem = mockk<AutofillSaveItem>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveFirstIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.AutofillSave(
|
|
|
|
autofillSaveItem = autofillSaveItem,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-02-01 09:33:10 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveFirstIntent with a passwordless request data should set the special circumstance to PasswordlessRequest`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val passwordlessRequestData = mockk<PasswordlessRequestData>()
|
|
|
|
every {
|
|
|
|
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
|
|
|
} returns passwordlessRequestData
|
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveFirstIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.PasswordlessRequest(
|
|
|
|
passwordlessRequestData = passwordlessRequestData,
|
|
|
|
shouldFinishWhenComplete = true,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-20 00:01:43 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val shareData = mockk<IntentManager.ShareData>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
2024-01-27 22:48:39 +03:00
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
2024-01-20 00:01:43 +03:00
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveNewIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
2024-01-23 01:14:06 +03:00
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.ShareNewSend(
|
2024-01-20 00:01:43 +03:00
|
|
|
data = shareData,
|
|
|
|
shouldFinishWhenComplete = false,
|
2024-01-23 01:14:06 +03:00
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
|
|
|
)
|
2024-01-20 00:01:43 +03:00
|
|
|
}
|
|
|
|
|
2024-01-27 22:48:39 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val autofillSelectionData = mockk<AutofillSelectionData>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
2024-01-27 22:48:39 +03:00
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveNewIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.AutofillSelection(
|
|
|
|
autofillSelectionData = autofillSelectionData,
|
|
|
|
shouldFinishWhenComplete = false,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
2024-01-31 22:27:54 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveNewIntent with an autofill save item should set the special circumstance to AutofillSave`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val autofillSaveItem = mockk<AutofillSaveItem>()
|
2024-02-01 09:33:10 +03:00
|
|
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
2024-01-31 22:27:54 +03:00
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveNewIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.AutofillSave(
|
|
|
|
autofillSaveItem = autofillSaveItem,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
2024-02-01 09:33:10 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `on ReceiveNewIntent with a passwordless auth request data should set the special circumstance to PasswordlessRequest`() {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
val mockIntent = mockk<Intent>()
|
|
|
|
val passwordlessRequestData = mockk<PasswordlessRequestData>()
|
|
|
|
every {
|
|
|
|
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
|
|
|
} returns passwordlessRequestData
|
|
|
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
|
|
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
|
|
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
|
|
|
|
|
|
|
viewModel.trySendAction(
|
|
|
|
MainAction.ReceiveNewIntent(
|
|
|
|
intent = mockIntent,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assertEquals(
|
|
|
|
SpecialCircumstance.PasswordlessRequest(
|
|
|
|
passwordlessRequestData = passwordlessRequestData,
|
|
|
|
shouldFinishWhenComplete = false,
|
|
|
|
),
|
|
|
|
specialCircumstanceManager.specialCircumstance,
|
2024-01-27 22:48:39 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-26 19:26:17 +03:00
|
|
|
@Suppress("MaxLineLength")
|
|
|
|
@Test
|
|
|
|
fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() =
|
|
|
|
runTest {
|
|
|
|
val viewModel = createViewModel()
|
|
|
|
|
|
|
|
viewModel.eventFlow.test {
|
|
|
|
assertEquals(
|
|
|
|
MainEvent.ScreenCaptureSettingChange(isAllowed = true),
|
|
|
|
awaitItem(),
|
|
|
|
)
|
|
|
|
|
|
|
|
mutableScreenCaptureAllowedFlow.value = false
|
|
|
|
assertEquals(
|
|
|
|
MainEvent.ScreenCaptureSettingChange(isAllowed = false),
|
|
|
|
awaitItem(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 18:35:31 +03:00
|
|
|
private fun createViewModel(
|
|
|
|
initialSpecialCircumstance: SpecialCircumstance? = null,
|
|
|
|
) = MainViewModel(
|
2024-01-28 21:25:15 +03:00
|
|
|
autofillSelectionManager = autofillSelectionManager,
|
2024-01-23 01:14:06 +03:00
|
|
|
specialCircumstanceManager = specialCircumstanceManager,
|
2024-01-17 02:41:34 +03:00
|
|
|
settingsRepository = settingsRepository,
|
2024-01-20 00:01:43 +03:00
|
|
|
intentManager = intentManager,
|
2024-01-26 18:35:31 +03:00
|
|
|
savedStateHandle = savedStateHandle.apply {
|
|
|
|
set(SPECIAL_CIRCUMSTANCE_KEY, initialSpecialCircumstance)
|
|
|
|
},
|
2024-01-17 02:41:34 +03:00
|
|
|
)
|
2023-10-03 22:34:51 +03:00
|
|
|
}
|
2024-02-16 00:33:19 +03:00
|
|
|
|
|
|
|
private const val SPECIAL_CIRCUMSTANCE_KEY: String = "special-circumstance"
|