From df5786f1e3a28bc3526045c24decc83154d2e923 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Wed, 25 May 2022 12:50:38 +0300 Subject: [PATCH] For #25381 - Add a setting allowing to toggle Pocket sponsored stories --- .../components/appstate/AppStoreReducer.kt | 12 +- .../fenix/settings/HomeSettingsFragment.kt | 24 +++ .../java/org/mozilla/fenix/utils/Settings.kt | 3 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/home_preferences.xml | 6 + .../mozilla/fenix/components/AppStoreTest.kt | 22 ++- .../settings/HomeSettingsFragmentTest.kt | 154 ++++++++++++++++++ 7 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/settings/HomeSettingsFragmentTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt index 57dc4ee06..d0fa7faff 100644 --- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt +++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt @@ -150,9 +150,15 @@ internal object AppStoreReducer { pocketStories = emptyList(), pocketSponsoredStories = emptyList() ) - is AppAction.PocketSponsoredStoriesChange -> state.copy( - pocketSponsoredStories = action.sponsoredStories - ) + is AppAction.PocketSponsoredStoriesChange -> { + val updatedStoriesState = state.copy( + pocketSponsoredStories = action.sponsoredStories, + ) + + updatedStoriesState.copy( + pocketStories = updatedStoriesState.getFilteredStories() + ) + } is AppAction.PocketStoriesShown -> { var updatedCategories = state.pocketStoriesCategories action.storiesShown.filterIsInstance().forEach { shownStory -> diff --git a/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt index eac5e8713..5660541e6 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/HomeSettingsFragment.kt @@ -13,6 +13,8 @@ import androidx.preference.SwitchPreference import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.GleanMetrics.CustomizeHome import org.mozilla.fenix.R +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.utils.view.addToRadioGroup @@ -116,6 +118,28 @@ class HomeSettingsFragment : PreferenceFragmentCompat() { } } + requirePreference(R.string.pref_key_pocket_sponsored_stories).apply { + isVisible = FeatureFlags.isPocketSponsoredStoriesFeatureEnabled(context) + isChecked = context.settings().showPocketSponsoredStories + onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + when (newValue) { + true -> { + context.components.core.pocketStoriesService.startPeriodicSponsoredStoriesRefresh() + } + false -> { + context.components.core.pocketStoriesService.deleteProfile() + context.components.appStore.dispatch( + AppAction.PocketSponsoredStoriesChange(emptyList()) + ) + } + } + + return super.onPreferenceChange(preference, newValue) + } + } + } + requirePreference(R.string.pref_key_history_metadata_feature).apply { isVisible = FeatureFlags.historyMetadataUIFeature isChecked = context.settings().historyMetadataUIFeature diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 2b535219e..6db31473c 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -1269,6 +1269,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + /** + * Indicates if the Pocket recommended stories homescreen section should be shown. + */ var showPocketRecommendationsFeature by lazyFeatureFlagPreference( appContext.getPreferenceKey(R.string.pref_key_pocket_homescreen_recommendations), featureFlag = FeatureFlags.isPocketRecommendationsFeatureEnabled(appContext), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2845e3507..f089d9565 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -409,6 +409,8 @@ Recently visited Pocket + + Sponsored stories Wallpapers diff --git a/app/src/main/res/xml/home_preferences.xml b/app/src/main/res/xml/home_preferences.xml index 975919a69..a13a10e16 100644 --- a/app/src/main/res/xml/home_preferences.xml +++ b/app/src/main/res/xml/home_preferences.xml @@ -35,6 +35,12 @@ android:title="@string/customize_toggle_pocket" app:isPreferenceVisible="false" /> + + diff --git a/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt b/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt index b9fb6e538..a61ddf74a 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt @@ -15,6 +15,7 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.pocket.PocketStory +import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory import mozilla.components.service.pocket.PocketStory.PocketSponsoredStoryCaps import org.junit.Assert.assertEquals @@ -364,7 +365,7 @@ class AppStoreTest { } @Test - fun `Test updating the list of Pocket sponsored stories`() = runTest { + fun `Test updating the list of Pocket sponsored stories also updates the list of stories to show`() = runTest { val story1 = PocketSponsoredStory( id = 3, title = "title", @@ -379,13 +380,20 @@ class AppStoreTest { appStore = AppStore(AppState()) - appStore.dispatch(AppAction.PocketSponsoredStoriesChange(listOf(story1, story2))) - .join() - assertTrue(appStore.state.pocketSponsoredStories.containsAll(listOf(story1, story2))) + mockkStatic("org.mozilla.fenix.ext.AppStateKt") { + val firstFilteredStories = listOf(mockk()) + every { any().getFilteredStories() } returns firstFilteredStories + appStore.dispatch(AppAction.PocketSponsoredStoriesChange(listOf(story1, story2))).join() + assertTrue(appStore.state.pocketSponsoredStories.containsAll(listOf(story1, story2))) + assertEquals(firstFilteredStories, appStore.state.pocketStories) - val updatedStories = listOf(story2.copy(title = "title3")) - appStore.dispatch(AppAction.PocketSponsoredStoriesChange(updatedStories)).join() - assertTrue(updatedStories.containsAll(appStore.state.pocketSponsoredStories)) + val secondFilteredStories = firstFilteredStories + mockk() + every { any().getFilteredStories() } returns secondFilteredStories + val updatedStories = listOf(story2.copy(title = "title3")) + appStore.dispatch(AppAction.PocketSponsoredStoriesChange(updatedStories)).join() + assertTrue(updatedStories.containsAll(appStore.state.pocketSponsoredStories)) + assertEquals(secondFilteredStories, appStore.state.pocketStories) + } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/settings/HomeSettingsFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/settings/HomeSettingsFragmentTest.kt new file mode 100644 index 000000000..6cf1e5c55 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/HomeSettingsFragmentTest.kt @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.content.Context +import android.content.SharedPreferences +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.preference.CheckBoxPreference +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import mozilla.components.service.pocket.PocketStoriesService +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.FeatureFlags +import org.mozilla.fenix.R +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings +import org.robolectric.Robolectric + +@RunWith(FenixRobolectricTestRunner::class) +internal class HomeSettingsFragmentTest { + private lateinit var homeSettingsFragment: HomeSettingsFragment + private lateinit var sponsoredStoriesSetting: CheckBoxPreference + private lateinit var appSettings: Settings + private lateinit var appPrefs: SharedPreferences + private lateinit var appPrefsEditor: SharedPreferences.Editor + private lateinit var pocketService: PocketStoriesService + private lateinit var store: AppStore + + @Before + fun setup() { + mockkStatic("org.mozilla.fenix.ext.FragmentKt") + every { any().showToolbar(any()) } just Runs + mockkStatic("org.mozilla.fenix.ext.ContextKt") + appPrefsEditor = mockk(relaxed = true) + appPrefs = mockk(relaxed = true) { + every { edit() } returns appPrefsEditor + } + appSettings = mockk(relaxed = true) { + every { preferences } returns appPrefs + } + every { any().settings() } returns appSettings + store = mockk(relaxed = true) + pocketService = mockk(relaxed = true) + every { any().components } returns mockk { + every { appStore } returns store + every { core.pocketStoriesService } returns pocketService + } + + homeSettingsFragment = HomeSettingsFragment() + + val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get() + activity.supportFragmentManager.beginTransaction() + .add(homeSettingsFragment, "HomeSettingFragmentTest") + .commitNow() + + sponsoredStoriesSetting = homeSettingsFragment.findPreference( + homeSettingsFragment.getPreferenceKey(R.string.pref_key_pocket_sponsored_stories) + )!! + } + + @After + fun teardown() { + unmockkStatic("org.mozilla.fenix.ext.ContextKt") + unmockkStatic("org.mozilla.fenix.ext.FragmentKt") + } + + @Test + fun `GIVEN the Pocket sponsored stories feature is disabled for the app WHEN accessing settings THEN the settings for it are not visible`() { + every { any().settings() } returns mockk(relaxed = true) + + mockkObject(FeatureFlags) { + every { FeatureFlags.isPocketSponsoredStoriesFeatureEnabled(any()) } returns false + + homeSettingsFragment.onResume() + + assertFalse(sponsoredStoriesSetting.isVisible) + } + } + + @Test + fun `GIVEN the Pocket sponsored stories feature is enabled for the app WHEN accessing settings THEN the settings for it are visible`() { + every { any().settings() } returns mockk(relaxed = true) + + mockkObject(FeatureFlags) { + every { FeatureFlags.isPocketSponsoredStoriesFeatureEnabled(any()) } returns true + + homeSettingsFragment.onResume() + + assertTrue(sponsoredStoriesSetting.isVisible) + } + } + + @Test + fun `GIVEN the Pocket sponsored stories preference is false WHEN accessing settings THEN the setting for it is unchecked`() { + every { appSettings.showPocketSponsoredStories } returns false + + homeSettingsFragment.onResume() + + assertFalse(sponsoredStoriesSetting.isChecked) + } + + @Test + fun `GIVEN the Pocket sponsored stories preference is true WHEN accessing settings THEN the setting for it is checked`() { + every { appSettings.showPocketSponsoredStories } returns true + + homeSettingsFragment.onResume() + + assertTrue(sponsoredStoriesSetting.isChecked) + } + + @Test + fun `GIVEN the setting for Pocket sponsored stories is unchecked WHEN tapping it THEN toggle it and start downloading stories`() { + homeSettingsFragment.onResume() + + val result = sponsoredStoriesSetting.callChangeListener(true) + + assertTrue(result) + verify { appPrefsEditor.putBoolean(testContext.getString(R.string.pref_key_pocket_sponsored_stories), true) } + verify { pocketService.startPeriodicSponsoredStoriesRefresh() } + } + + @Test + fun `GIVEN the setting for Pocket sponsored stories is checked WHEN tapping it THEN toggle it, delete Pocket profile and remove sponsored stories from showing`() { + homeSettingsFragment.onResume() + + val result = sponsoredStoriesSetting.callChangeListener(false) + + assertTrue(result) + verify { appPrefsEditor.putBoolean(testContext.getString(R.string.pref_key_pocket_sponsored_stories), false) } + verify { pocketService.deleteProfile() } + verify { store.dispatch(AppAction.PocketSponsoredStoriesChange(emptyList())) } + } +}