Loading app/src/main/java/foundation/e/apps/data/Stores.kt +20 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ import foundation.e.apps.data.enums.Source.PLAY_STORE import foundation.e.apps.data.enums.Source.PWA import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import javax.inject.Inject import javax.inject.Singleton Loading @@ -45,6 +49,9 @@ class Stores @Inject constructor( appLoungePreference ) private val _enabledStoresFlow = MutableStateFlow(provideEnabledStores()) val enabledStoresFlow: StateFlow<Set<Source>> = _enabledStoresFlow.asStateFlow() /** * Retrieves a map of enabled store repositories based on user preferences. * Loading @@ -59,16 +66,27 @@ class Stores @Inject constructor( fun getStore(source: Source): StoreRepository? = getStores()[source] fun enableStore(source: Source) = fun enableStore(source: Source) { storeConfigs[source]?.enable?.invoke() ?: error("No matching Store found for $source.") fun disableStore(source: Source) = _enabledStoresFlow.update { provideEnabledStores() } } fun disableStore(source: Source) { storeConfigs[source]?.disable?.invoke() ?: error("No matching Store found for $source.") _enabledStoresFlow.update { provideEnabledStores() } } fun isStoreEnabled(source: Source): Boolean = storeConfigs[source]?.isEnabled?.invoke() == true private fun provideEnabledStores(): Set<Source> = storeConfigs .filterValues { it.isEnabled() } .keys } internal data class StoreConfig( Loading app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt +5 −14 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package foundation.e.apps.data.preference import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import androidx.preference.PreferenceManager import dagger.hilt.android.qualifiers.ApplicationContext Loading Loading @@ -66,18 +65,6 @@ class AppLoungePreference @Inject constructor( fun enableOpenSource() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_FOSS, true) } fun enablePwa() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_PWA, true) } /** * Expose preference change registration so UI layers can react to store toggles immediately. * Callers must unregister to avoid leaking the listener beyond the consumer lifecycle. */ fun registerStorePreferenceListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { preferenceManager.registerOnSharedPreferenceChangeListener(listener) } fun unregisterStorePreferenceListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { preferenceManager.unregisterOnSharedPreferenceChangeListener(listener) } fun getUpdateInterval(): Long { val currentUser = appLoungeDataStore.getUser() return when (currentUser) { Loading @@ -85,6 +72,7 @@ class AppLoungePreference @Inject constructor( context.getString(R.string.update_check_intervals_anonymous), context.getString(R.string.preference_update_interval_default_anonymous) )!!.toLong() else -> preferenceManager.getString( context.getString(R.string.update_check_intervals), context.getString(R.string.preference_update_interval_default) Loading Loading @@ -135,6 +123,9 @@ class AppLoungePreference @Inject constructor( } fun isOnlyUnmeteredNetworkEnabled(): Boolean { return preferenceManager.getBoolean(context.getString(R.string.only_unmetered_network), true) return preferenceManager.getBoolean( context.getString(R.string.only_unmetered_network), true ) } } app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt +4 −26 Original line number Diff line number Diff line Loading @@ -18,13 +18,9 @@ package foundation.e.apps.ui.search.v2 import android.content.SharedPreferences import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA import foundation.e.apps.data.Stores import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.data.Ratings Loading Loading @@ -82,21 +78,11 @@ class SearchViewModelV2 @Inject constructor( private var suggestionJob: Job? = null private val preferenceListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key in STORE_PREFERENCE_KEYS) { handleStoreSelectionChanged() } } init { appLoungePreference.registerStorePreferenceListener(preferenceListener) handleStoreSelectionChanged() viewModelScope.launch { stores.enabledStoresFlow .collect { handleStoreSelectionChanged() } } override fun onCleared() { appLoungePreference.unregisterStorePreferenceListener(preferenceListener) super.onCleared() } fun onQueryChanged(newQuery: String) { Loading Loading @@ -307,14 +293,6 @@ class SearchViewModelV2 @Inject constructor( } } companion object { private val STORE_PREFERENCE_KEYS = setOf( PREFERENCE_SHOW_GPLAY, PREFERENCE_SHOW_FOSS, PREFERENCE_SHOW_PWA, ) } private fun SearchTabType.toReadable(): String = when (this) { SearchTabType.COMMON_APPS -> "Standard app" SearchTabType.OPEN_SOURCE -> "Open source app" Loading app/src/test/java/foundation/e/apps/data/StoresTest.kt +61 −13 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.Before import org.junit.Test Loading @@ -16,19 +17,33 @@ class StoresTest { private val playStoreRepository: PlayStoreRepository = mockk(relaxed = true) private val cleanApkAppsRepository: CleanApkAppsRepository = mockk(relaxed = true) private val cleanApkPwaRepository: CleanApkPwaRepository = mockk(relaxed = true) private val preference: AppLoungePreference = mockk() private lateinit var preference: AppLoungePreference private lateinit var stores: Stores private var playStoreSelected = true private var openSourceSelected = true private var pwaSelected = false @Before fun setUp() { stores = Stores(playStoreRepository, cleanApkAppsRepository, cleanApkPwaRepository, preference) preference = mockk(relaxed = true) every { preference.isPlayStoreSelected() } answers { playStoreSelected } every { preference.isOpenSourceSelected() } answers { openSourceSelected } every { preference.isPWASelected() } answers { pwaSelected } every { preference.enablePlayStore() } answers { playStoreSelected = true } every { preference.disablePlayStore() } answers { playStoreSelected = false } every { preference.enableOpenSource() } answers { openSourceSelected = true } every { preference.disableOpenSource() } answers { openSourceSelected = false } every { preference.enablePwa() } answers { pwaSelected = true } every { preference.disablePwa() } answers { pwaSelected = false } buildStores() } @Test fun getStoresReturnsOnlyEnabledSources() { every { preference.isPlayStoreSelected() } returns true every { preference.isOpenSourceSelected() } returns false every { preference.isPWASelected() } returns true playStoreSelected = true openSourceSelected = false pwaSelected = true val result = stores.getStores() Loading @@ -39,21 +54,18 @@ class StoresTest { @Test fun enableAndDisableStoreProxiesPreference() { every { preference.enableOpenSource() } returns Unit every { preference.disableOpenSource() } returns Unit stores.enableStore(Source.OPEN_SOURCE) stores.disableStore(Source.OPEN_SOURCE) io.mockk.verify { preference.enableOpenSource() } io.mockk.verify { preference.disableOpenSource() } verify { preference.enableOpenSource() } verify { preference.disableOpenSource() } } @Test fun isStoreEnabledReflectsPreferenceFlags() { every { preference.isPlayStoreSelected() } returns false every { preference.isOpenSourceSelected() } returns false every { preference.isPWASelected() } returns true playStoreSelected = false openSourceSelected = false pwaSelected = true val enabled = stores.isStoreEnabled(Source.PWA) val disabled = stores.isStoreEnabled(Source.PLAY_STORE) Loading @@ -66,4 +78,40 @@ class StoresTest { fun enableStoreThrowsForUnknownSource() { stores.enableStore(Source.SYSTEM_APP) } @Test fun enabledStoresFlowReflectsInitialSelection() { playStoreSelected = true openSourceSelected = false pwaSelected = true buildStores() assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.PLAY_STORE, Source.PWA) } @Test fun enabledStoresFlowUpdatesAfterToggleChanges() { playStoreSelected = true openSourceSelected = false pwaSelected = false buildStores() stores.enableStore(Source.OPEN_SOURCE) assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.PLAY_STORE, Source.OPEN_SOURCE) stores.disableStore(Source.PLAY_STORE) assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.OPEN_SOURCE) } private fun buildStores() { stores = Stores( playStoreRepository, cleanApkAppsRepository, cleanApkPwaRepository, preference, ) } } app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt +28 −41 Original line number Diff line number Diff line Loading @@ -18,12 +18,10 @@ package foundation.e.apps.ui.search.v2 import android.content.SharedPreferences import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY import foundation.e.apps.data.Stores import foundation.e.apps.data.cleanapk.repositories.CleanApkAppsRepository import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.search.FakeSuggestionSource Loading @@ -34,7 +32,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before Loading @@ -55,7 +52,6 @@ class SearchViewModelV2Test { private var playStoreSelected = true private var openSourceSelected = true private var pwaSelected = false private var preferenceListener: SharedPreferences.OnSharedPreferenceChangeListener? = null private lateinit var viewModel: SearchViewModelV2 @Before Loading @@ -63,21 +59,15 @@ class SearchViewModelV2Test { suggestionSource = FakeSuggestionSource() preference = mockk(relaxed = true) stores = buildStores() every { preference.isPlayStoreSelected() } answers { playStoreSelected } every { preference.isOpenSourceSelected() } answers { openSourceSelected } every { preference.isPWASelected() } answers { pwaSelected } every { preference.registerStorePreferenceListener(any()) } answers { preferenceListener = arg(0) Unit } every { preference.unregisterStorePreferenceListener(any()) } answers { if (preferenceListener == arg<SharedPreferences.OnSharedPreferenceChangeListener>(0)) { preferenceListener = null } Unit } every { preference.enablePlayStore() } answers { playStoreSelected = true } every { preference.disablePlayStore() } answers { playStoreSelected = false } every { preference.enableOpenSource() } answers { openSourceSelected = true } every { preference.disableOpenSource() } answers { openSourceSelected = false } every { preference.enablePwa() } answers { pwaSelected = true } every { preference.disablePwa() } answers { pwaSelected = false } buildViewModel() } Loading Loading @@ -224,9 +214,9 @@ class SearchViewModelV2Test { buildViewModel() viewModel.onSearchSubmitted("apps") playStoreSelected = false openSourceSelected = true notifyPreferenceChange(PREFERENCE_SHOW_FOSS) stores.disableStore(Source.PLAY_STORE) stores.enableStore(Source.OPEN_SOURCE) runStoreUpdates() val state = viewModel.uiState.value assertEquals(listOf(SearchTabType.OPEN_SOURCE), state.availableTabs) Loading @@ -244,9 +234,8 @@ class SearchViewModelV2Test { advanceDebounce() assertTrue(viewModel.uiState.value.isSuggestionVisible) playStoreSelected = false notifyPreferenceChange(PREFERENCE_SHOW_GPLAY) advanceDebounce() stores.disableStore(Source.PLAY_STORE) runStoreUpdates() val state = viewModel.uiState.value assertFalse(state.isSuggestionVisible) Loading @@ -260,10 +249,8 @@ class SearchViewModelV2Test { buildViewModel() viewModel.onSearchSubmitted("apps") playStoreSelected = false openSourceSelected = false pwaSelected = false notifyPreferenceChange(PREFERENCE_SHOW_GPLAY) stores.disableStore(Source.PLAY_STORE) runStoreUpdates() val state = viewModel.uiState.value assertTrue(state.availableTabs.isEmpty()) Loading Loading @@ -299,14 +286,19 @@ class SearchViewModelV2Test { } @Test fun `on cleared unregisters preference listener`() { fun `store change before submit updates available tabs`() = runTest { playStoreSelected = true openSourceSelected = false pwaSelected = false buildViewModel() assertNotNull(preferenceListener) invokeOnCleared() stores.enableStore(Source.OPEN_SOURCE) runStoreUpdates() assertNull(preferenceListener) val state = viewModel.uiState.value assertEquals(listOf(SearchTabType.COMMON_APPS, SearchTabType.OPEN_SOURCE), state.availableTabs) assertEquals(SearchTabType.COMMON_APPS, state.selectedTab) assertFalse(state.hasSubmittedSearch) } private fun advanceDebounce() { Loading @@ -314,23 +306,18 @@ class SearchViewModelV2Test { mainCoroutineRule.testDispatcher.scheduler.runCurrent() } private fun runStoreUpdates() { mainCoroutineRule.testDispatcher.scheduler.runCurrent() } private fun visibleTabs(): List<SearchTabType> = buildList { if (playStoreSelected) add(SearchTabType.COMMON_APPS) if (openSourceSelected) add(SearchTabType.OPEN_SOURCE) if (pwaSelected) add(SearchTabType.PWA) } private fun notifyPreferenceChange(key: String) { preferenceListener?.onSharedPreferenceChanged(null, key) } private fun buildViewModel() { stores = buildStores() viewModel = SearchViewModelV2(suggestionSource, preference, stores) } private fun invokeOnCleared() { val method = SearchViewModelV2::class.java.getDeclaredMethod("onCleared") method.isAccessible = true method.invoke(viewModel) } } Loading
app/src/main/java/foundation/e/apps/data/Stores.kt +20 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ import foundation.e.apps.data.enums.Source.PLAY_STORE import foundation.e.apps.data.enums.Source.PWA import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import javax.inject.Inject import javax.inject.Singleton Loading @@ -45,6 +49,9 @@ class Stores @Inject constructor( appLoungePreference ) private val _enabledStoresFlow = MutableStateFlow(provideEnabledStores()) val enabledStoresFlow: StateFlow<Set<Source>> = _enabledStoresFlow.asStateFlow() /** * Retrieves a map of enabled store repositories based on user preferences. * Loading @@ -59,16 +66,27 @@ class Stores @Inject constructor( fun getStore(source: Source): StoreRepository? = getStores()[source] fun enableStore(source: Source) = fun enableStore(source: Source) { storeConfigs[source]?.enable?.invoke() ?: error("No matching Store found for $source.") fun disableStore(source: Source) = _enabledStoresFlow.update { provideEnabledStores() } } fun disableStore(source: Source) { storeConfigs[source]?.disable?.invoke() ?: error("No matching Store found for $source.") _enabledStoresFlow.update { provideEnabledStores() } } fun isStoreEnabled(source: Source): Boolean = storeConfigs[source]?.isEnabled?.invoke() == true private fun provideEnabledStores(): Set<Source> = storeConfigs .filterValues { it.isEnabled() } .keys } internal data class StoreConfig( Loading
app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt +5 −14 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package foundation.e.apps.data.preference import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import androidx.preference.PreferenceManager import dagger.hilt.android.qualifiers.ApplicationContext Loading Loading @@ -66,18 +65,6 @@ class AppLoungePreference @Inject constructor( fun enableOpenSource() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_FOSS, true) } fun enablePwa() = preferenceManager.edit { putBoolean(PREFERENCE_SHOW_PWA, true) } /** * Expose preference change registration so UI layers can react to store toggles immediately. * Callers must unregister to avoid leaking the listener beyond the consumer lifecycle. */ fun registerStorePreferenceListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { preferenceManager.registerOnSharedPreferenceChangeListener(listener) } fun unregisterStorePreferenceListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { preferenceManager.unregisterOnSharedPreferenceChangeListener(listener) } fun getUpdateInterval(): Long { val currentUser = appLoungeDataStore.getUser() return when (currentUser) { Loading @@ -85,6 +72,7 @@ class AppLoungePreference @Inject constructor( context.getString(R.string.update_check_intervals_anonymous), context.getString(R.string.preference_update_interval_default_anonymous) )!!.toLong() else -> preferenceManager.getString( context.getString(R.string.update_check_intervals), context.getString(R.string.preference_update_interval_default) Loading Loading @@ -135,6 +123,9 @@ class AppLoungePreference @Inject constructor( } fun isOnlyUnmeteredNetworkEnabled(): Boolean { return preferenceManager.getBoolean(context.getString(R.string.only_unmetered_network), true) return preferenceManager.getBoolean( context.getString(R.string.only_unmetered_network), true ) } }
app/src/main/java/foundation/e/apps/ui/search/v2/SearchViewModelV2.kt +4 −26 Original line number Diff line number Diff line Loading @@ -18,13 +18,9 @@ package foundation.e.apps.ui.search.v2 import android.content.SharedPreferences import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA import foundation.e.apps.data.Stores import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.application.data.Ratings Loading Loading @@ -82,21 +78,11 @@ class SearchViewModelV2 @Inject constructor( private var suggestionJob: Job? = null private val preferenceListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key in STORE_PREFERENCE_KEYS) { handleStoreSelectionChanged() } } init { appLoungePreference.registerStorePreferenceListener(preferenceListener) handleStoreSelectionChanged() viewModelScope.launch { stores.enabledStoresFlow .collect { handleStoreSelectionChanged() } } override fun onCleared() { appLoungePreference.unregisterStorePreferenceListener(preferenceListener) super.onCleared() } fun onQueryChanged(newQuery: String) { Loading Loading @@ -307,14 +293,6 @@ class SearchViewModelV2 @Inject constructor( } } companion object { private val STORE_PREFERENCE_KEYS = setOf( PREFERENCE_SHOW_GPLAY, PREFERENCE_SHOW_FOSS, PREFERENCE_SHOW_PWA, ) } private fun SearchTabType.toReadable(): String = when (this) { SearchTabType.COMMON_APPS -> "Standard app" SearchTabType.OPEN_SOURCE -> "Open source app" Loading
app/src/test/java/foundation/e/apps/data/StoresTest.kt +61 −13 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.Before import org.junit.Test Loading @@ -16,19 +17,33 @@ class StoresTest { private val playStoreRepository: PlayStoreRepository = mockk(relaxed = true) private val cleanApkAppsRepository: CleanApkAppsRepository = mockk(relaxed = true) private val cleanApkPwaRepository: CleanApkPwaRepository = mockk(relaxed = true) private val preference: AppLoungePreference = mockk() private lateinit var preference: AppLoungePreference private lateinit var stores: Stores private var playStoreSelected = true private var openSourceSelected = true private var pwaSelected = false @Before fun setUp() { stores = Stores(playStoreRepository, cleanApkAppsRepository, cleanApkPwaRepository, preference) preference = mockk(relaxed = true) every { preference.isPlayStoreSelected() } answers { playStoreSelected } every { preference.isOpenSourceSelected() } answers { openSourceSelected } every { preference.isPWASelected() } answers { pwaSelected } every { preference.enablePlayStore() } answers { playStoreSelected = true } every { preference.disablePlayStore() } answers { playStoreSelected = false } every { preference.enableOpenSource() } answers { openSourceSelected = true } every { preference.disableOpenSource() } answers { openSourceSelected = false } every { preference.enablePwa() } answers { pwaSelected = true } every { preference.disablePwa() } answers { pwaSelected = false } buildStores() } @Test fun getStoresReturnsOnlyEnabledSources() { every { preference.isPlayStoreSelected() } returns true every { preference.isOpenSourceSelected() } returns false every { preference.isPWASelected() } returns true playStoreSelected = true openSourceSelected = false pwaSelected = true val result = stores.getStores() Loading @@ -39,21 +54,18 @@ class StoresTest { @Test fun enableAndDisableStoreProxiesPreference() { every { preference.enableOpenSource() } returns Unit every { preference.disableOpenSource() } returns Unit stores.enableStore(Source.OPEN_SOURCE) stores.disableStore(Source.OPEN_SOURCE) io.mockk.verify { preference.enableOpenSource() } io.mockk.verify { preference.disableOpenSource() } verify { preference.enableOpenSource() } verify { preference.disableOpenSource() } } @Test fun isStoreEnabledReflectsPreferenceFlags() { every { preference.isPlayStoreSelected() } returns false every { preference.isOpenSourceSelected() } returns false every { preference.isPWASelected() } returns true playStoreSelected = false openSourceSelected = false pwaSelected = true val enabled = stores.isStoreEnabled(Source.PWA) val disabled = stores.isStoreEnabled(Source.PLAY_STORE) Loading @@ -66,4 +78,40 @@ class StoresTest { fun enableStoreThrowsForUnknownSource() { stores.enableStore(Source.SYSTEM_APP) } @Test fun enabledStoresFlowReflectsInitialSelection() { playStoreSelected = true openSourceSelected = false pwaSelected = true buildStores() assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.PLAY_STORE, Source.PWA) } @Test fun enabledStoresFlowUpdatesAfterToggleChanges() { playStoreSelected = true openSourceSelected = false pwaSelected = false buildStores() stores.enableStore(Source.OPEN_SOURCE) assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.PLAY_STORE, Source.OPEN_SOURCE) stores.disableStore(Source.PLAY_STORE) assertThat(stores.enabledStoresFlow.value) .containsExactly(Source.OPEN_SOURCE) } private fun buildStores() { stores = Stores( playStoreRepository, cleanApkAppsRepository, cleanApkPwaRepository, preference, ) } }
app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt +28 −41 Original line number Diff line number Diff line Loading @@ -18,12 +18,10 @@ package foundation.e.apps.ui.search.v2 import android.content.SharedPreferences import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY import foundation.e.apps.data.Stores import foundation.e.apps.data.cleanapk.repositories.CleanApkAppsRepository import foundation.e.apps.data.cleanapk.repositories.CleanApkPwaRepository import foundation.e.apps.data.enums.Source import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.data.search.FakeSuggestionSource Loading @@ -34,7 +32,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before Loading @@ -55,7 +52,6 @@ class SearchViewModelV2Test { private var playStoreSelected = true private var openSourceSelected = true private var pwaSelected = false private var preferenceListener: SharedPreferences.OnSharedPreferenceChangeListener? = null private lateinit var viewModel: SearchViewModelV2 @Before Loading @@ -63,21 +59,15 @@ class SearchViewModelV2Test { suggestionSource = FakeSuggestionSource() preference = mockk(relaxed = true) stores = buildStores() every { preference.isPlayStoreSelected() } answers { playStoreSelected } every { preference.isOpenSourceSelected() } answers { openSourceSelected } every { preference.isPWASelected() } answers { pwaSelected } every { preference.registerStorePreferenceListener(any()) } answers { preferenceListener = arg(0) Unit } every { preference.unregisterStorePreferenceListener(any()) } answers { if (preferenceListener == arg<SharedPreferences.OnSharedPreferenceChangeListener>(0)) { preferenceListener = null } Unit } every { preference.enablePlayStore() } answers { playStoreSelected = true } every { preference.disablePlayStore() } answers { playStoreSelected = false } every { preference.enableOpenSource() } answers { openSourceSelected = true } every { preference.disableOpenSource() } answers { openSourceSelected = false } every { preference.enablePwa() } answers { pwaSelected = true } every { preference.disablePwa() } answers { pwaSelected = false } buildViewModel() } Loading Loading @@ -224,9 +214,9 @@ class SearchViewModelV2Test { buildViewModel() viewModel.onSearchSubmitted("apps") playStoreSelected = false openSourceSelected = true notifyPreferenceChange(PREFERENCE_SHOW_FOSS) stores.disableStore(Source.PLAY_STORE) stores.enableStore(Source.OPEN_SOURCE) runStoreUpdates() val state = viewModel.uiState.value assertEquals(listOf(SearchTabType.OPEN_SOURCE), state.availableTabs) Loading @@ -244,9 +234,8 @@ class SearchViewModelV2Test { advanceDebounce() assertTrue(viewModel.uiState.value.isSuggestionVisible) playStoreSelected = false notifyPreferenceChange(PREFERENCE_SHOW_GPLAY) advanceDebounce() stores.disableStore(Source.PLAY_STORE) runStoreUpdates() val state = viewModel.uiState.value assertFalse(state.isSuggestionVisible) Loading @@ -260,10 +249,8 @@ class SearchViewModelV2Test { buildViewModel() viewModel.onSearchSubmitted("apps") playStoreSelected = false openSourceSelected = false pwaSelected = false notifyPreferenceChange(PREFERENCE_SHOW_GPLAY) stores.disableStore(Source.PLAY_STORE) runStoreUpdates() val state = viewModel.uiState.value assertTrue(state.availableTabs.isEmpty()) Loading Loading @@ -299,14 +286,19 @@ class SearchViewModelV2Test { } @Test fun `on cleared unregisters preference listener`() { fun `store change before submit updates available tabs`() = runTest { playStoreSelected = true openSourceSelected = false pwaSelected = false buildViewModel() assertNotNull(preferenceListener) invokeOnCleared() stores.enableStore(Source.OPEN_SOURCE) runStoreUpdates() assertNull(preferenceListener) val state = viewModel.uiState.value assertEquals(listOf(SearchTabType.COMMON_APPS, SearchTabType.OPEN_SOURCE), state.availableTabs) assertEquals(SearchTabType.COMMON_APPS, state.selectedTab) assertFalse(state.hasSubmittedSearch) } private fun advanceDebounce() { Loading @@ -314,23 +306,18 @@ class SearchViewModelV2Test { mainCoroutineRule.testDispatcher.scheduler.runCurrent() } private fun runStoreUpdates() { mainCoroutineRule.testDispatcher.scheduler.runCurrent() } private fun visibleTabs(): List<SearchTabType> = buildList { if (playStoreSelected) add(SearchTabType.COMMON_APPS) if (openSourceSelected) add(SearchTabType.OPEN_SOURCE) if (pwaSelected) add(SearchTabType.PWA) } private fun notifyPreferenceChange(key: String) { preferenceListener?.onSharedPreferenceChanged(null, key) } private fun buildViewModel() { stores = buildStores() viewModel = SearchViewModelV2(suggestionSource, preference, stores) } private fun invokeOnCleared() { val method = SearchViewModelV2::class.java.getDeclaredMethod("onCleared") method.isAccessible = true method.invoke(viewModel) } }