Bug 1964789: Enabled and disable the private mode lock feature through shared pref r=android-reviewers,gmalekpour
Differential Revision: https://phabricator.services.mozilla.com/D248567
This commit is contained in:
committed by
mavduevskiy@mozilla.com
parent
27f2438098
commit
0422c9dc82
@@ -17,7 +17,6 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingLocked
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.appstate.AppAction.PrivateBrowsingLockAction
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.tabstray.Page
|
||||
@@ -94,11 +93,7 @@ class UnlockPrivateTabsFragment : Fragment() {
|
||||
private fun onAuthSuccess() {
|
||||
PrivateBrowsingLocked.authSuccess.record()
|
||||
|
||||
requireComponents.appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
),
|
||||
)
|
||||
requireComponents.privateBrowsingLockFeature.onSuccessfulAuthentication()
|
||||
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import org.mozilla.fenix.home.middleware.HomeTelemetryMiddleware
|
||||
import org.mozilla.fenix.home.setup.store.DefaultSetupChecklistRepository
|
||||
import org.mozilla.fenix.home.setup.store.SetupChecklistPreferencesMiddleware
|
||||
import org.mozilla.fenix.home.setup.store.SetupChecklistTelemetryMiddleware
|
||||
import org.mozilla.fenix.lifecycle.DefaultPrivateBrowsingLockStorage
|
||||
import org.mozilla.fenix.lifecycle.PrivateBrowsingLockFeature
|
||||
import org.mozilla.fenix.messaging.state.MessagingMiddleware
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
@@ -198,7 +199,10 @@ class Components(private val context: Context) {
|
||||
PrivateBrowsingLockFeature(
|
||||
appStore = appStore,
|
||||
browserStore = core.store,
|
||||
settings = settings,
|
||||
storage = DefaultPrivateBrowsingLockStorage(
|
||||
preferences = settings.preferences,
|
||||
privateBrowsingLockPrefKey = context.getString(R.string.pref_key_private_browsing_locked_enabled),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
package org.mozilla.fenix.lifecycle
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Lifecycle.State.RESUMED
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -24,7 +26,59 @@ import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.components.appstate.AppAction.PrivateBrowsingLockAction
|
||||
import org.mozilla.fenix.components.appstate.AppState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayFragment
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* An interface to access and observe the enabled/disabled state of the Private Browsing Lock feature.
|
||||
*/
|
||||
interface PrivateBrowsingLockStorage {
|
||||
/**
|
||||
* Returns the current enabled state of the private browsing lock feature.
|
||||
*/
|
||||
val isFeatureEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Registers a listener that is invoked whenever the enabled state changes.
|
||||
*
|
||||
* @param listener A lambda that receives the new boolean value when it changes.
|
||||
*/
|
||||
fun addFeatureStateListener(listener: (Boolean) -> Unit)
|
||||
|
||||
/**
|
||||
* Removes the previously registered listener.
|
||||
*/
|
||||
fun removeFeatureStateListener()
|
||||
}
|
||||
|
||||
/**
|
||||
* A default implementation of `PrivateBrowsingLockStorage`.
|
||||
*
|
||||
* @param preferences The [SharedPreferences] instance from which to read the preference value.
|
||||
* @param privateBrowsingLockPrefKey The key in [SharedPreferences] representing the feature flag.
|
||||
*/
|
||||
class DefaultPrivateBrowsingLockStorage(
|
||||
private val preferences: SharedPreferences,
|
||||
private val privateBrowsingLockPrefKey: String,
|
||||
) : PrivateBrowsingLockStorage {
|
||||
private var listener: ((Boolean) -> Unit)? = null
|
||||
private val onFeatureStateChanged = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
|
||||
if (key == privateBrowsingLockPrefKey) {
|
||||
listener?.invoke(prefs.getBoolean(privateBrowsingLockPrefKey, false))
|
||||
}
|
||||
}
|
||||
|
||||
override val isFeatureEnabled: Boolean
|
||||
get() = preferences.getBoolean(privateBrowsingLockPrefKey, false)
|
||||
|
||||
override fun addFeatureStateListener(listener: (Boolean) -> Unit) {
|
||||
this.listener = listener
|
||||
preferences.registerOnSharedPreferenceChangeListener(onFeatureStateChanged)
|
||||
}
|
||||
|
||||
override fun removeFeatureStateListener() {
|
||||
preferences.unregisterOnSharedPreferenceChangeListener(onFeatureStateChanged)
|
||||
listener = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lifecycle-aware feature that locks private browsing mode behind authentication
|
||||
@@ -33,44 +87,104 @@ import org.mozilla.fenix.utils.Settings
|
||||
class PrivateBrowsingLockFeature(
|
||||
private val appStore: AppStore,
|
||||
private val browserStore: BrowserStore,
|
||||
private val settings: Settings,
|
||||
private val storage: PrivateBrowsingLockStorage,
|
||||
) : DefaultLifecycleObserver {
|
||||
private var browserStoreScope: CoroutineScope? = null
|
||||
private var appStoreScope: CoroutineScope? = null
|
||||
private var isFeatureEnabled = false
|
||||
|
||||
init {
|
||||
// When the app is initialized, if there are private tabs, we should lock the private mode.
|
||||
if (settings.privateBrowsingLockedEnabled) {
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = browserStore.state.privateTabs.isNotEmpty(),
|
||||
),
|
||||
isFeatureEnabled = storage.isFeatureEnabled
|
||||
|
||||
updateFeatureState(
|
||||
isFeatureEnabled = isFeatureEnabled,
|
||||
isLocked = browserStore.state.privateTabs.isNotEmpty(),
|
||||
)
|
||||
|
||||
observeFeatureStateUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a successful authentication event by unlocking the private browsing mode.
|
||||
*
|
||||
* This should be called by biometric or password authentication mechanisms (e.g., fingerprint,
|
||||
* face unlock, or PIN entry) once the user has successfully authenticated. It updates the app state
|
||||
* to reflect that private browsing tabs are now accessible.
|
||||
*/
|
||||
fun onSuccessfulAuthentication() {
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(isLocked = false),
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeFeatureStateUpdates() {
|
||||
storage.addFeatureStateListener { isEnabled ->
|
||||
isFeatureEnabled = isEnabled
|
||||
|
||||
updateFeatureState(
|
||||
isFeatureEnabled = isFeatureEnabled,
|
||||
isLocked = appStore.state.mode == BrowsingMode.Normal &&
|
||||
browserStore.state.privateTabs.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFeatureState(
|
||||
isFeatureEnabled: Boolean,
|
||||
isLocked: Boolean,
|
||||
) {
|
||||
if (isFeatureEnabled) {
|
||||
start(isLocked)
|
||||
} else {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun start(isLocked: Boolean) {
|
||||
observePrivateTabsClosure()
|
||||
observeSwitchingToNormalMode()
|
||||
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = isLocked,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun stop() {
|
||||
browserStoreScope?.cancel()
|
||||
appStoreScope?.cancel()
|
||||
storage.removeFeatureStateListener()
|
||||
|
||||
browserStoreScope = null
|
||||
appStoreScope = null
|
||||
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun observePrivateTabsClosure() {
|
||||
browserStore.flowScoped { flow ->
|
||||
browserStoreScope = browserStore.flowScoped { flow ->
|
||||
flow
|
||||
.map { it.privateTabs.size }
|
||||
.distinctUntilChanged()
|
||||
.filter { it == 0 }
|
||||
.collect {
|
||||
if (settings.privateBrowsingLockedEnabled) {
|
||||
// When all private tabs are closed, we don't need to lock the private mode.
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When all private tabs are closed, we don't need to lock the private mode.
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeSwitchingToNormalMode() {
|
||||
appStore.flowScoped { flow ->
|
||||
appStoreScope = appStore.flowScoped { flow ->
|
||||
flow
|
||||
.map { it.mode }
|
||||
.distinctUntilChanged()
|
||||
@@ -78,10 +192,8 @@ class PrivateBrowsingLockFeature(
|
||||
.collect {
|
||||
// When witching from private to normal mode with private tabs open,
|
||||
// we lock the private mode.
|
||||
val isPrivateModeLockEnabled = settings.privateBrowsingLockedEnabled
|
||||
val hasPrivateTabs = browserStore.state.privateTabs.isNotEmpty()
|
||||
|
||||
if (isPrivateModeLockEnabled && hasPrivateTabs) {
|
||||
if (hasPrivateTabs) {
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
@@ -95,6 +207,8 @@ class PrivateBrowsingLockFeature(
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
super.onStop(owner)
|
||||
|
||||
if (!isFeatureEnabled) return
|
||||
|
||||
when (owner) {
|
||||
// lock when activity hits onStop and it isn’t a config-change restart
|
||||
is Activity -> {
|
||||
@@ -112,11 +226,10 @@ class PrivateBrowsingLockFeature(
|
||||
|
||||
private fun maybeLockPrivateModeOnStop() {
|
||||
// When the app gets inactive in private mode with opened tabs, we lock the private mode.
|
||||
val isPrivateModeLockEnabled = settings.privateBrowsingLockedEnabled
|
||||
val hasPrivateTabs = browserStore.state.privateTabs.isNotEmpty()
|
||||
val isPrivateMode = appStore.state.mode == BrowsingMode.Private
|
||||
|
||||
if (isPrivateModeLockEnabled && isPrivateMode && hasPrivateTabs) {
|
||||
if (isPrivateMode && hasPrivateTabs) {
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
@@ -126,11 +239,10 @@ class PrivateBrowsingLockFeature(
|
||||
}
|
||||
|
||||
private fun maybeLockPrivateModeOnTabsTrayClosure() {
|
||||
val isPrivateModeLockEnabled = settings.privateBrowsingLockedEnabled
|
||||
val hasPrivateTabs = browserStore.state.privateTabs.isNotEmpty()
|
||||
val isNormalMode = appStore.state.mode == BrowsingMode.Normal
|
||||
|
||||
if (isPrivateModeLockEnabled && isNormalMode && hasPrivateTabs) {
|
||||
if (isNormalMode && hasPrivateTabs) {
|
||||
appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
|
||||
@@ -51,7 +51,7 @@ import org.mozilla.fenix.biometricauthentication.BiometricAuthenticationManager
|
||||
import org.mozilla.fenix.biometricauthentication.BiometricAuthenticationNeededInfo
|
||||
import org.mozilla.fenix.browser.tabstrip.isTabStripEnabled
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.components.appstate.AppAction.PrivateBrowsingLockAction
|
||||
import org.mozilla.fenix.components.components
|
||||
import org.mozilla.fenix.compose.core.Action
|
||||
import org.mozilla.fenix.compose.snackbar.Snackbar
|
||||
import org.mozilla.fenix.compose.snackbar.SnackbarState
|
||||
@@ -780,9 +780,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
|
||||
|
||||
tabsTrayInteractor.onTrayPositionSelected(page.ordinal, false)
|
||||
|
||||
requireComponents.appStore.dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(isLocked = false),
|
||||
)
|
||||
requireComponents.privateBrowsingLockFeature.onSuccessfulAuthentication()
|
||||
},
|
||||
onAuthFailure = {
|
||||
PrivateBrowsingLocked.authFailure.record()
|
||||
|
||||
@@ -16,27 +16,21 @@ import junit.framework.TestCase.assertTrue
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.selector.privateTabs
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import org.mozilla.fenix.components.appstate.AppAction
|
||||
import org.mozilla.fenix.components.appstate.AppAction.PrivateBrowsingLockAction
|
||||
import org.mozilla.fenix.components.appstate.AppState
|
||||
import org.mozilla.fenix.tabstray.TabsTrayFragment
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PrivateBrowsingLockFeatureTest {
|
||||
@@ -44,223 +38,674 @@ class PrivateBrowsingLockFeatureTest {
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
|
||||
private lateinit var appStore: AppStore
|
||||
private lateinit var browserStore: BrowserStore
|
||||
private lateinit var settings: Settings
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
appStore = spy(AppStore())
|
||||
browserStore = BrowserStore()
|
||||
settings = mockk(relaxed = true)
|
||||
}
|
||||
|
||||
// zero tabs cases
|
||||
@Test
|
||||
fun `GIVEN the feature is enabled WHEN number of private tabs reaches zero THEN we unlock private mode`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns true
|
||||
val store = createBrowserStore()
|
||||
PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
fun `GIVEN feature is enabled and mode is normal WHEN number of private tabs reaches zero THEN we unlock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
store.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
|
||||
verify(appStore).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
browserStore.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is disabled WHEN number of private tabs reaches zero THEN we don't lock private mode`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns false
|
||||
val store = createBrowserStore()
|
||||
PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
fun `GIVEN feature is enabled and mode is private WHEN authenticated and number of private tabs reaches zero THEN private mode is unchanged`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
store.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
|
||||
verify(appStore, never()).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = false,
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
feature.onSuccessfulAuthentication()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
browserStore.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is on and there are private tabs WHEN initializing the app THEN we lock private mode`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns true
|
||||
val store = createBrowserStore(
|
||||
fun `GIVEN the feature is disabled and mode is normal WHEN number of private tabs reaches zero THEN private mode is unchanged `() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
browserStore.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is disabled and mode is private WHEN number of private tabs reaches zero THEN private mode is unchanged `() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
browserStore.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
// initializing cases
|
||||
@Test
|
||||
fun `GIVEN feature is enabled and mode is private and there are private tabs WHEN initializing feature THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val appStore = spy(
|
||||
AppStore(
|
||||
initialState = AppState(mode = BrowsingMode.Private),
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
|
||||
verify(appStore).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
),
|
||||
)
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is off and there are private tabs WHEN initializing the app THEN we do not lock private mode`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns false
|
||||
val store = createBrowserStore(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
)
|
||||
val appStore = spy(
|
||||
AppStore(
|
||||
initialState = AppState(mode = BrowsingMode.Private),
|
||||
),
|
||||
)
|
||||
fun `GIVEN feature is enabled and mode is normal and there are private tabs WHEN initializing feature THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
|
||||
verify(appStore, times(0)).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN there are private tabs WHEN switching to normal mode THEN we lock private mode`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns true
|
||||
val store = createBrowserStore(tabs = listOf(createTab("https://www.mozilla.org", id = "mozilla")))
|
||||
val appStore = spy(AppStore(initialState = AppState(mode = BrowsingMode.Private)))
|
||||
PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
fun `GIVEN feature is disabled and mode is private and there are private tabs WHEN initializing the app THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is disabled and mode is normal and there are private tabs WHEN initializing the app THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is enabled and mode is private and there are no private tabs WHEN initializing feature THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is enabled and mode is normal and there are no private tabs WHEN initializing feature THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is enabled and there are private tabs WHEN switching to normal mode THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
feature.onSuccessfulAuthentication()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://www.firefox.com", id = "firefox", private = true))).joinBlocking()
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Normal)).joinBlocking()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
verify(appStore, times(1)).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(
|
||||
isLocked = true,
|
||||
),
|
||||
)
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal mode and enabled lock WHEN lifecycle is resumed THEN observing lock doesn't trigger`() {
|
||||
val localScope = TestScope()
|
||||
var isLocked = false
|
||||
val mode = BrowsingMode.Normal
|
||||
val isPrivateScreenLocked = true
|
||||
var result = false
|
||||
val appStore = AppStore(initialState = AppState(mode = mode, isPrivateScreenLocked = isPrivateScreenLocked))
|
||||
|
||||
observePrivateModeLock(
|
||||
viewLifecycleOwner = MockedLifecycleOwner(Lifecycle.State.RESUMED),
|
||||
scope = localScope,
|
||||
appStore = appStore,
|
||||
onPrivateModeLocked = { isLocked = true },
|
||||
onPrivateModeLocked = { result = true },
|
||||
)
|
||||
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Normal)).joinBlocking()
|
||||
appStore.dispatch(PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(true)).joinBlocking()
|
||||
localScope.advanceUntilIdle()
|
||||
|
||||
assertFalse(isLocked)
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal mode and disabled lock WHEN lifecycle is resumed THEN observing lock doesn't trigger`() {
|
||||
val localScope = TestScope()
|
||||
var isLocked = false
|
||||
val mode = BrowsingMode.Normal
|
||||
val isPrivateScreenLocked = false
|
||||
var result = false
|
||||
val appStore = AppStore(initialState = AppState(mode = mode, isPrivateScreenLocked = isPrivateScreenLocked))
|
||||
|
||||
observePrivateModeLock(
|
||||
viewLifecycleOwner = MockedLifecycleOwner(Lifecycle.State.RESUMED),
|
||||
scope = localScope,
|
||||
appStore = appStore,
|
||||
onPrivateModeLocked = { isLocked = true },
|
||||
onPrivateModeLocked = { result = true },
|
||||
)
|
||||
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Normal)).joinBlocking()
|
||||
appStore.dispatch(PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(true)).joinBlocking()
|
||||
localScope.advanceUntilIdle()
|
||||
|
||||
assertFalse(isLocked)
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode and enabled lock WHEN lifecycle is resumed THEN observing lock triggers`() {
|
||||
val localScope = TestScope()
|
||||
var isLocked = false
|
||||
val mode = BrowsingMode.Private
|
||||
val isPrivateScreenLocked = true
|
||||
var result = false
|
||||
val appStore = AppStore(initialState = AppState(mode = mode, isPrivateScreenLocked = isPrivateScreenLocked))
|
||||
|
||||
observePrivateModeLock(
|
||||
viewLifecycleOwner = MockedLifecycleOwner(Lifecycle.State.RESUMED),
|
||||
scope = localScope,
|
||||
appStore = appStore,
|
||||
onPrivateModeLocked = { isLocked = true },
|
||||
onPrivateModeLocked = { result = true },
|
||||
)
|
||||
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Private)).joinBlocking()
|
||||
appStore.dispatch(PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(true)).joinBlocking()
|
||||
localScope.advanceUntilIdle()
|
||||
|
||||
assertTrue(isLocked)
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode and disabled lock WHEN lifecycle is resumed THEN observing lock doesn't trigger`() {
|
||||
val localScope = TestScope()
|
||||
var isLocked = false
|
||||
val mode = BrowsingMode.Private
|
||||
val isPrivateScreenLocked = false
|
||||
var result = false
|
||||
val appStore = AppStore(initialState = AppState(mode = mode, isPrivateScreenLocked = isPrivateScreenLocked))
|
||||
|
||||
observePrivateModeLock(
|
||||
viewLifecycleOwner = MockedLifecycleOwner(Lifecycle.State.RESUMED),
|
||||
scope = localScope,
|
||||
appStore = appStore,
|
||||
onPrivateModeLocked = { isLocked = true },
|
||||
onPrivateModeLocked = { result = true },
|
||||
)
|
||||
|
||||
appStore.dispatch(PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(false)).joinBlocking()
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Private)).joinBlocking()
|
||||
localScope.advanceUntilIdle()
|
||||
|
||||
assertFalse(isLocked)
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN private mode with private tabs WHEN Activity stops without config change THEN lock is triggered`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns true
|
||||
val store = createBrowserStore(
|
||||
tabs = listOf(createTab("https://www.mozilla.org", id = "mozilla")),
|
||||
selectedTabId = "mozilla",
|
||||
fun `GIVEN feature is on and mode is private and there are private tabs WHEN Activity stops without config change THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val appStore = spy(AppStore(AppState(mode = BrowsingMode.Private)))
|
||||
val feature = PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://www.firefox.com", id = "firefox", private = true))).joinBlocking()
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
verify(appStore).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(isLocked = true),
|
||||
)
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN normal mode with private tabs WHEN TabsTrayFragment stops THEN lock is triggered`() {
|
||||
every { settings.privateBrowsingLockedEnabled } returns true
|
||||
val store = createBrowserStore(
|
||||
tabs = listOf(createTab("https://www.mozilla.org", id = "mozilla")),
|
||||
selectedTabId = "mozilla",
|
||||
fun `GIVEN feature is on and mode is normal and there are private tabs WHEN Activity stops without config change THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val appStore = spy(AppStore(AppState(mode = BrowsingMode.Normal)))
|
||||
val feature = PrivateBrowsingLockFeature(appStore, store, settings)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is on and mode is private and there are no private tabs WHEN Activity stops without config change THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is on and mode is normal and there are no private tabs WHEN Activity stops without config change THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is off and mode is private and there are private tabs WHEN Activity stops without config change THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is on and mode is normal and there are private tabs WHEN Activity stops without config change THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
// NB: this is an important case: we don't want to lock the screen on config change if the user is already in
|
||||
// private mode because switching modes causes the activity to recreate; e.g., the user switches to private mode
|
||||
// tab through the tabstray. the app goes into private mode and then activity shuts due to configuration change.
|
||||
// if we lock the screen at that point, going into private mode will always lock it due to activity restart.
|
||||
@Test
|
||||
fun `GIVEN feature is on and mode is private and there are private tabs WHEN Activity stops with config change THEN we do not lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
feature.onSuccessfulAuthentication()
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns true
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature is on and mode is normal and there are private tabs WHEN TabsTrayFragment stops THEN we lock private mode`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = createStorage(isFeatureEnabled = isFeatureEnabled))
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
val fragment = mockk<TabsTrayFragment>(relaxed = true)
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://www.firefox.com", id = "firefox", private = true))).joinBlocking()
|
||||
feature.onStop(fragment)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
verify(appStore).dispatch(
|
||||
PrivateBrowsingLockAction.UpdatePrivateBrowsingLock(isLocked = true),
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is on and there are private tabs WHEN we turn off the feature THEN we unlock private tabs and don't lock it`() {
|
||||
val isFeatureEnabled = true
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val storage = createStorage(isFeatureEnabled = isFeatureEnabled)
|
||||
|
||||
val feature = createFeature(browserStore = browserStore, appStore = appStore, storage = storage)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
// verify that disabled feature state unlocks private mode
|
||||
val sharedPrefUpdate = false
|
||||
storage.listener?.invoke(sharedPrefUpdate)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(browserStore.state.privateTabs.isNotEmpty())
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
// verify that activity.onStop doesn't lock private mode
|
||||
val activity = mockk<AppCompatActivity>(relaxed = true)
|
||||
every { activity.isChangingConfigurations } returns false
|
||||
|
||||
feature.onStop(activity)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(browserStore.state.privateTabs.isNotEmpty())
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
// verify that going to normal mode doesn't lock private mode
|
||||
appStore.dispatch(AppAction.ModeChange(mode = BrowsingMode.Normal)).joinBlocking()
|
||||
|
||||
assertTrue(browserStore.state.privateTabs.isNotEmpty())
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is off and mode is normal and there are private tabs WHEN we turn on the feature THEN we lock private tabs`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Normal
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val storage = createStorage(isFeatureEnabled = isFeatureEnabled)
|
||||
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = storage)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
// verify that disabled feature state unlocks private mode
|
||||
val sharedPrefUpdate = true
|
||||
storage.listener?.invoke(sharedPrefUpdate)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(browserStore.state.privateTabs.isNotEmpty())
|
||||
assertTrue(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the feature is off and mode is private and there are private tabs WHEN we turn on the feature THEN we do not lock private tabs`() {
|
||||
val isFeatureEnabled = false
|
||||
val mode = BrowsingMode.Private
|
||||
|
||||
val appStore = AppStore(initialState = AppState(mode = mode))
|
||||
val browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId = "mozilla",
|
||||
),
|
||||
)
|
||||
val storage = createStorage(isFeatureEnabled = isFeatureEnabled)
|
||||
|
||||
createFeature(browserStore = browserStore, appStore = appStore, storage = storage)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
|
||||
// verify that disabled feature state unlocks private mode
|
||||
val sharedPrefUpdate = true
|
||||
storage.listener?.invoke(sharedPrefUpdate)
|
||||
appStore.waitUntilIdle()
|
||||
|
||||
assertTrue(browserStore.state.privateTabs.isNotEmpty())
|
||||
assertFalse(appStore.state.isPrivateScreenLocked)
|
||||
}
|
||||
|
||||
internal class MockedLifecycleOwner(initialState: Lifecycle.State) : LifecycleOwner {
|
||||
@@ -269,16 +714,26 @@ class PrivateBrowsingLockFeatureTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBrowserStore(
|
||||
tabs: List<TabSessionState> = listOf(
|
||||
createTab("https://www.firefox.com", id = "firefox", private = true),
|
||||
createTab("https://www.mozilla.org", id = "mozilla"),
|
||||
),
|
||||
selectedTabId: String = "mozilla",
|
||||
) = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = tabs,
|
||||
selectedTabId = selectedTabId,
|
||||
),
|
||||
internal class MockedPrivateBrowsingLockStorage(
|
||||
override val isFeatureEnabled: Boolean = true,
|
||||
) : PrivateBrowsingLockStorage {
|
||||
var listener: ((Boolean) -> Unit)? = null
|
||||
|
||||
override fun addFeatureStateListener(listener: (Boolean) -> Unit) {
|
||||
this.listener = listener
|
||||
}
|
||||
override fun removeFeatureStateListener() {}
|
||||
}
|
||||
|
||||
private fun createFeature(
|
||||
appStore: AppStore,
|
||||
browserStore: BrowserStore,
|
||||
storage: PrivateBrowsingLockStorage,
|
||||
) = PrivateBrowsingLockFeature(
|
||||
appStore = appStore,
|
||||
browserStore = browserStore,
|
||||
storage = storage,
|
||||
)
|
||||
|
||||
private fun createStorage(isFeatureEnabled: Boolean = true) = MockedPrivateBrowsingLockStorage(isFeatureEnabled)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user