Closes issue #16944 Refactor TrackingProtectionPanelDialogFragment to observe session via store.

upstream-sync
Arturo Mejia 4 years ago
parent 9bee4797de
commit 743ba6918f

@ -14,6 +14,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.appcompat.view.ContextThemeWrapper
import androidx.lifecycle.lifecycleScope
@ -22,13 +23,20 @@ import androidx.navigation.fragment.navArgs
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.fragment_tracking_protection.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.content.blocking.Tracker
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.lib.state.ext.consumeFlow
import mozilla.components.lib.state.ext.observe
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
@ -38,6 +46,8 @@ import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions")
class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private val args by navArgs<TrackingProtectionPanelDialogFragmentArgs>()
@ -54,7 +64,8 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
)
}
private lateinit var trackingProtectionStore: TrackingProtectionStore
@VisibleForTesting
internal lateinit var trackingProtectionStore: TrackingProtectionStore
private lateinit var trackingProtectionView: TrackingProtectionPanelView
private lateinit var trackingProtectionInteractor: TrackingProtectionPanelInteractor
private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases
@ -69,17 +80,14 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val store = requireComponents.core.store
val view = inflateRootView(container)
val session = requireComponents.core.sessionManager.findSessionById(args.sessionId)
@Suppress("DEPRECATION")
// TODO Use browser store instead of session observer: https://github.com/mozilla-mobile/fenix/issues/16944
session?.register(sessionObserver, view = view)
val tab = store.state.findTabOrCustomTab(provideTabId())
trackingProtectionStore = StoreProvider.get(this) {
TrackingProtectionStore(
TrackingProtectionState(
session,
tab,
args.url,
args.trackingProtectionEnabled,
listTrackers = listOf(),
@ -95,27 +103,14 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
)
trackingProtectionView =
TrackingProtectionPanelView(view.fragment_tp, trackingProtectionInteractor)
session?.let { updateTrackers(it) }
tab?.let { updateTrackers(it) }
return view
}
private val sessionObserver = object : Session.Observer {
override fun onUrlChanged(session: Session, url: String) {
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange(url))
}
override fun onTrackerBlocked(session: Session, tracker: Tracker, all: List<Tracker>) {
updateTrackers(session)
}
override fun onTrackerLoaded(session: Session, tracker: Tracker, all: List<Tracker>) {
updateTrackers(session)
}
}
private fun updateTrackers(session: Session) {
@VisibleForTesting
internal fun updateTrackers(tab: SessionState) {
trackingProtectionUseCases.fetchTrackingLogs(
session.id,
tab.id,
onSuccess = {
trackingProtectionStore.dispatch(TrackingProtectionAction.TrackerLogChange(it))
},
@ -127,7 +122,10 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val store = requireComponents.core.store
observeUrlChange(store)
observeTrackersChange(store)
trackingProtectionStore.observe(view) {
viewLifecycleOwner.lifecycleScope.launch {
whenStarted {
@ -211,4 +209,35 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
}
return true
}
@VisibleForTesting
internal fun observeUrlChange(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId())
}.ifChanged { tab -> tab.content.url }
.collect {
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange(it.content.url))
}
}
}
@VisibleForTesting
internal fun provideTabId(): String = args.sessionId
@VisibleForTesting
internal fun observeTrackersChange(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId())
}.ifAnyChanged { tab ->
arrayOf(
tab.trackingProtection.blockedTrackers,
tab.trackingProtection.loadedTrackers
)
}.collect {
updateTrackers(it)
}
}
}
}

@ -18,8 +18,8 @@ import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tracking_protection_panel.*
import kotlinx.android.synthetic.main.component_tracking_protection_panel.details_blocking_header
import kotlinx.android.synthetic.main.fragment_tracking_protection_blocking.*
import kotlinx.android.synthetic.main.switch_with_description.view.*
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
@ -107,7 +107,7 @@ class TrackingProtectionPanelView(
private fun setUIForNormalMode(state: TrackingProtectionState) {
details_mode.visibility = View.GONE
normal_mode.visibility = View.VISIBLE
protection_settings.isGone = state.session?.customTabConfig != null
protection_settings.isGone = state.tab is CustomTabSessionState
not_blocking_header.isGone = bucketedTrackers.loadedIsEmpty()
bindUrl(state.url)

@ -5,7 +5,7 @@
package org.mozilla.fenix.trackingprotection
import androidx.annotation.StringRes
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.content.blocking.TrackerLog
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
@ -55,7 +55,7 @@ sealed class TrackingProtectionAction : Action {
* accessibly focus after returning from details_moode
*/
data class TrackingProtectionState(
val session: Session?,
val tab: SessionState?,
val url: String,
val isTrackingProtectionEnabled: Boolean,
val listTrackers: List<TrackerLog>,

@ -0,0 +1,149 @@
/* 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.trackingprotection
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import junit.framework.TestCase.assertNotSame
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.action.TrackingProtectionAction.TrackerBlockedAction
import mozilla.components.browser.state.action.TrackingProtectionAction.TrackerLoadedAction
import mozilla.components.browser.state.selector.findTab
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.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionPanelDialogFragmentTest {
private val testDispatcher = TestCoroutineDispatcher()
@get:Rule
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
private lateinit var lifecycleOwner: MockedLifecycleOwner
private lateinit var fragment: TrackingProtectionPanelDialogFragment
private lateinit var store: BrowserStore
@Before
fun setup() {
fragment = spyk(TrackingProtectionPanelDialogFragment())
lifecycleOwner = MockedLifecycleOwner(Lifecycle.State.STARTED)
store = BrowserStore()
every { fragment.view } returns mockk(relaxed = true)
every { fragment.lifecycle } returns lifecycleOwner.lifecycle
every { fragment.activity } returns mockk(relaxed = true)
}
@After
fun cleanUp() {
testDispatcher.cleanupTestCoroutines()
}
@Test
fun `WHEN the url is updated THEN the url view is updated`() {
val trackingProtectionStore: TrackingProtectionStore = mockk(relaxed = true)
val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
fragment.observeUrlChange(store)
addAndSelectTab(tab)
verify(exactly = 1) {
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange("mozilla.org"))
}
store.dispatch(ContentAction.UpdateUrlAction(tab.id, "wikipedia.org")).joinBlocking()
verify(exactly = 1) {
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange("wikipedia.org"))
}
}
@Test
fun `WHEN a tracker is loaded THEN trackers view is updated`() {
val trackingProtectionStore: TrackingProtectionStore = mockk(relaxed = true)
val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)
addAndSelectTab(tab)
verify(exactly = 1) {
fragment.updateTrackers(tab)
}
store.dispatch(TrackerLoadedAction(tab.id, mockk())).joinBlocking()
val updatedTab = store.state.findTab(tab.id)!!
assertNotSame(updatedTab, tab)
verify(exactly = 1) {
fragment.updateTrackers(updatedTab)
}
}
@Test
fun `WHEN a tracker is blocked THEN trackers view is updated`() {
val trackingProtectionStore: TrackingProtectionStore = mockk(relaxed = true)
val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)
addAndSelectTab(tab)
verify(exactly = 1) {
fragment.updateTrackers(tab)
}
store.dispatch(TrackerBlockedAction(tab.id, mockk())).joinBlocking()
val updatedTab = store.state.findTab(tab.id)!!
assertNotSame(updatedTab, tab)
verify(exactly = 1) {
fragment.updateTrackers(tab)
}
}
private fun addAndSelectTab(tab: TabSessionState) {
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking()
}
internal class MockedLifecycleOwner(initialState: Lifecycle.State) : LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this).apply {
currentState = initialState
}
override fun getLifecycle(): Lifecycle = lifecycleRegistry
}
}

@ -29,7 +29,7 @@ class TrackingProtectionPanelViewTest {
private lateinit var interactor: TrackingProtectionPanelInteractor
private lateinit var view: TrackingProtectionPanelView
private val baseState = TrackingProtectionState(
session = null,
tab = null,
url = "",
isTrackingProtectionEnabled = false,
listTrackers = emptyList(),

@ -6,7 +6,7 @@ package org.mozilla.fenix.trackingprotection
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.content.blocking.TrackerLog
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotSame
@ -14,7 +14,7 @@ import org.junit.Test
class TrackingProtectionStoreTest {
val session: Session = mockk(relaxed = true)
val tab: SessionState = mockk(relaxed = true)
@Test
fun enterDetailsMode() = runBlocking {
@ -131,7 +131,7 @@ class TrackingProtectionStoreTest {
}
private fun defaultState(): TrackingProtectionState = TrackingProtectionState(
session = session,
tab = tab,
url = "www.mozilla.org",
isTrackingProtectionEnabled = true,
listTrackers = listOf(),
@ -140,7 +140,7 @@ class TrackingProtectionStoreTest {
)
private fun detailsState(): TrackingProtectionState = TrackingProtectionState(
session = session,
tab = tab,
url = "www.mozilla.org",
isTrackingProtectionEnabled = true,
listTrackers = listOf(),

Loading…
Cancel
Save