Issue #19175: Fix SyncTabs list not updating on changes

The main cause for this is that the `LifecycleProvider` needs to be set
to `State.RESUMED` to avoid the account manager's internal
`ObserverRegistry` from putting the UI observers into the paused state.

The rest of the changes is to rely the internal (safe) logic to
correctly sync and then update the tabs list.
upstream-sync
Jonathan Almeida 3 years ago committed by Jonathan Almeida
parent fda8fd398e
commit 392ace67d6

@ -16,7 +16,6 @@ import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
@ -29,8 +28,7 @@ class AccessibleNewTabButtonBinding(
private val store: TabsTrayStore, private val store: TabsTrayStore,
private val settings: Settings, private val settings: Settings,
private val newTabButton: ImageButton, private val newTabButton: ImageButton,
private val browserTrayInteractor: BrowserTrayInteractor, private val browserTrayInteractor: BrowserTrayInteractor
private val syncedTabsInteractor: SyncedTabsInteractor
) : LifecycleAwareFeature { ) : LifecycleAwareFeature {
private var scope: CoroutineScope? = null private var scope: CoroutineScope? = null
@ -94,7 +92,6 @@ class AccessibleNewTabButtonBinding(
// a sync was requested. // a sync was requested.
if (!syncing) { if (!syncing) {
store.dispatch(TabsTrayAction.SyncNow) store.dispatch(TabsTrayAction.SyncNow)
syncedTabsInteractor.onRefresh()
} }
} }
} }

@ -15,7 +15,6 @@ import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
@ -28,8 +27,7 @@ class FloatingActionButtonBinding(
private val store: TabsTrayStore, private val store: TabsTrayStore,
private val settings: Settings, private val settings: Settings,
private val actionButton: ExtendedFloatingActionButton, private val actionButton: ExtendedFloatingActionButton,
private val browserTrayInteractor: BrowserTrayInteractor, private val browserTrayInteractor: BrowserTrayInteractor
private val syncedTabsInteractor: SyncedTabsInteractor
) : LifecycleAwareFeature { ) : LifecycleAwareFeature {
private var scope: CoroutineScope? = null private var scope: CoroutineScope? = null
@ -98,7 +96,6 @@ class FloatingActionButtonBinding(
// a sync was requested. // a sync was requested.
if (!syncing) { if (!syncing) {
store.dispatch(TabsTrayAction.SyncNow) store.dispatch(TabsTrayAction.SyncNow)
syncedTabsInteractor.onRefresh()
} }
} }
} }

@ -12,9 +12,12 @@ import kotlinx.coroutines.launch
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.storage.sync.Tab as SyncTab
import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.tabstray.Tab import mozilla.components.concept.tabstray.Tab
import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.FxaAccountManager
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.collections.CollectionsDialog import org.mozilla.fenix.collections.CollectionsDialog
import org.mozilla.fenix.collections.show import org.mozilla.fenix.collections.show
import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.TabCollectionStorage
@ -75,6 +78,11 @@ interface NavigationInteractor {
* Used when adding [Tab]s as bookmarks. * Used when adding [Tab]s as bookmarks.
*/ */
fun onSaveToBookmarks(tabs: Collection<Tab>) fun onSaveToBookmarks(tabs: Collection<Tab>)
/**
* Called when clicking on a SyncedTab item.
*/
fun onSyncedTabClicked(tab: SyncTab)
} }
/** /**
@ -83,6 +91,7 @@ interface NavigationInteractor {
@Suppress("LongParameterList") @Suppress("LongParameterList")
class DefaultNavigationInteractor( class DefaultNavigationInteractor(
private val context: Context, private val context: Context,
private val activity: HomeActivity,
private val browserStore: BrowserStore, private val browserStore: BrowserStore,
private val navController: NavController, private val navController: NavController,
private val metrics: MetricController, private val metrics: MetricController,
@ -193,4 +202,13 @@ class DefaultNavigationInteractor(
// TODO show successful snackbar here (regardless of operation succes). // TODO show successful snackbar here (regardless of operation succes).
} }
override fun onSyncedTabClicked(tab: SyncTab) {
metrics.track(Event.SyncedTabOpened)
activity.openToBrowserAndLoad(
searchTermOrURL = tab.active().url,
newTab = true,
from = BrowserDirection.FromTabTray
)
}
} }

@ -6,10 +6,8 @@ package org.mozilla.fenix.tabstray
import androidx.navigation.NavController import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.base.profiler.Profiler
import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.sync.SyncReason
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
@ -23,11 +21,6 @@ interface TabsTrayController {
* Called when user clicks the new tab button. * Called when user clicks the new tab button.
*/ */
fun onNewTabTapped(isPrivate: Boolean) fun onNewTabTapped(isPrivate: Boolean)
/**
* Starts user account tab syncing.
* */
fun onSyncStarted()
} }
class DefaultTabsTrayController( class DefaultTabsTrayController(
@ -54,22 +47,6 @@ class DefaultTabsTrayController(
sendNewTabEvent(isPrivate) sendNewTabEvent(isPrivate)
} }
override fun onSyncStarted() {
ioScope.launch {
metrics.track(Event.SyncAccountSyncNow)
// Trigger a sync.
accountManager.syncNow(SyncReason.User)
// Poll for device events & update devices.
accountManager.authenticatedAccount()
?.deviceConstellation()?.run {
refreshDevices()
pollForCommands()
}
}.invokeOnCompletion {
store.dispatch(TabsTrayAction.SyncCompleted)
}
}
private fun sendNewTabEvent(isPrivateModeSelected: Boolean) { private fun sendNewTabEvent(isPrivateModeSelected: Boolean) {
val eventToSend = if (isPrivateModeSelected) { val eventToSend = if (isPrivateModeSelected) {
Event.NewPrivateTabTapped Event.NewPrivateTabTapped

@ -53,7 +53,6 @@ import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier
import org.mozilla.fenix.tabstray.ext.getTrayPosition import org.mozilla.fenix.tabstray.ext.getTrayPosition
import org.mozilla.fenix.tabstray.ext.showWithTheme import org.mozilla.fenix.tabstray.ext.showWithTheme
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
import kotlin.math.max import kotlin.math.max
@ -111,6 +110,7 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
val navigationInteractor = val navigationInteractor =
DefaultNavigationInteractor( DefaultNavigationInteractor(
context = requireContext(), context = requireContext(),
activity = activity,
tabsTrayStore = tabsTrayStore, tabsTrayStore = tabsTrayStore,
browserStore = requireComponents.core.store, browserStore = requireComponents.core.store,
navController = findNavController(), navController = findNavController(),
@ -143,20 +143,13 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
requireComponents.analytics.metrics requireComponents.analytics.metrics
) )
val syncedTabsTrayInteractor = SyncedTabsInteractor(
requireComponents.analytics.metrics,
requireActivity() as HomeActivity,
this,
controller = tabsTrayController
)
setupMenu(view, navigationInteractor) setupMenu(view, navigationInteractor)
setupPager( setupPager(
view.context, view.context,
tabsTrayStore, tabsTrayStore,
this, this,
browserTrayInteractor, browserTrayInteractor,
syncedTabsTrayInteractor navigationInteractor
) )
setupBackgroundDismissalListener { setupBackgroundDismissalListener {
@ -216,8 +209,7 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
store = tabsTrayStore, store = tabsTrayStore,
settings = requireComponents.settings, settings = requireComponents.settings,
actionButton = new_tab_button, actionButton = new_tab_button,
browserTrayInteractor = browserTrayInteractor, browserTrayInteractor = browserTrayInteractor
syncedTabsInteractor = syncedTabsTrayInteractor
), ),
owner = this, owner = this,
view = view view = view
@ -228,8 +220,7 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
store = tabsTrayStore, store = tabsTrayStore,
settings = requireComponents.settings, settings = requireComponents.settings,
newTabButton = tab_tray_new_tab, newTabButton = tab_tray_new_tab,
browserTrayInteractor = browserTrayInteractor, browserTrayInteractor = browserTrayInteractor
syncedTabsInteractor = syncedTabsTrayInteractor
), ),
owner = this, owner = this,
view = view view = view
@ -337,14 +328,14 @@ class TabsTrayFragment : AppCompatDialogFragment(), TabsTrayInteractor {
store: TabsTrayStore, store: TabsTrayStore,
trayInteractor: TabsTrayInteractor, trayInteractor: TabsTrayInteractor,
browserInteractor: BrowserTrayInteractor, browserInteractor: BrowserTrayInteractor,
syncedTabsTrayInteractor: SyncedTabsInteractor navigationInteractor: NavigationInteractor
) { ) {
tabsTray.apply { tabsTray.apply {
adapter = TrayPagerAdapter( adapter = TrayPagerAdapter(
context, context,
store, store,
browserInteractor, browserInteractor,
syncedTabsTrayInteractor, navigationInteractor,
trayInteractor, trayInteractor,
requireComponents.core.store requireComponents.core.store
) )

@ -12,10 +12,10 @@ import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.sync.SyncedTabsAdapter import org.mozilla.fenix.sync.SyncedTabsAdapter
import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter import org.mozilla.fenix.tabstray.browser.BrowserTabsAdapter
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.syncedtabs.TabClickDelegate
import org.mozilla.fenix.tabstray.viewholders.AbstractTrayViewHolder import org.mozilla.fenix.tabstray.viewholders.AbstractTrayViewHolder
import org.mozilla.fenix.tabstray.viewholders.NormalBrowserTabViewHolder import org.mozilla.fenix.tabstray.viewholders.NormalBrowserTabViewHolder
import org.mozilla.fenix.tabstray.viewholders.PrivateBrowserTabViewHolder import org.mozilla.fenix.tabstray.viewholders.PrivateBrowserTabViewHolder
@ -25,14 +25,14 @@ class TrayPagerAdapter(
private val context: Context, private val context: Context,
private val store: TabsTrayStore, private val store: TabsTrayStore,
private val browserInteractor: BrowserTrayInteractor, private val browserInteractor: BrowserTrayInteractor,
private val syncedTabsInteractor: SyncedTabsView.Listener, private val navInteractor: NavigationInteractor,
private val interactor: TabsTrayInteractor, private val interactor: TabsTrayInteractor,
private val browserStore: BrowserStore private val browserStore: BrowserStore
) : RecyclerView.Adapter<AbstractTrayViewHolder>() { ) : RecyclerView.Adapter<AbstractTrayViewHolder>() {
private val normalAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) } private val normalAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) }
private val privateAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) } private val privateAdapter by lazy { BrowserTabsAdapter(context, browserInteractor, store) }
private val syncedTabsAdapter by lazy { SyncedTabsAdapter(syncedTabsInteractor) } private val syncedTabsAdapter by lazy { SyncedTabsAdapter(TabClickDelegate(navInteractor)) }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractTrayViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractTrayViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
@ -59,7 +59,7 @@ class TrayPagerAdapter(
SyncedTabViewHolder.LAYOUT_ID -> { SyncedTabViewHolder.LAYOUT_ID -> {
SyncedTabViewHolder( SyncedTabViewHolder(
itemView, itemView,
syncedTabsInteractor store
) )
} }
else -> throw IllegalStateException("Unknown viewType.") else -> throw IllegalStateException("Unknown viewType.")

@ -0,0 +1,35 @@
/* 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.tabstray.syncedtabs
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.components.AbstractBinding
import org.mozilla.fenix.tabstray.TabsTrayState
import org.mozilla.fenix.tabstray.TabsTrayStore
/**
* An [AbstractBinding] that invokes the [onSyncNow] callback when the [TabsTrayState.syncing] is
* set.
*
* This binding is useful for connecting with [SyncedTabsView.Listener].
*/
class SyncButtonBinding(
tabsTrayStore: TabsTrayStore,
private val onSyncNow: () -> Unit
) : AbstractBinding<TabsTrayState>(tabsTrayStore) {
override suspend fun onState(flow: Flow<TabsTrayState>) {
flow.map { it.syncing }
.ifChanged()
.collect { syncingNow ->
if (syncingNow) {
onSyncNow()
}
}
}
}

@ -1,34 +0,0 @@
/* 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.tabstray.syncedtabs
import mozilla.components.browser.storage.sync.Tab
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.tabstray.TabsTrayController
import org.mozilla.fenix.tabstray.TabsTrayInteractor
class SyncedTabsInteractor(
private val metrics: MetricController,
private val activity: HomeActivity,
private val trayInteractor: TabsTrayInteractor,
private val controller: TabsTrayController
) : SyncedTabsView.Listener {
override fun onRefresh() {
controller.onSyncStarted()
}
override fun onTabClicked(tab: Tab) {
metrics.track(Event.SyncedTabOpened)
activity.openToBrowserAndLoad(
searchTermOrURL = tab.active().url,
newTab = true,
from = BrowserDirection.FromTabTray
)
trayInteractor.navigateToBrowser()
}
}

@ -24,7 +24,9 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.sync.SyncedTabsAdapter import org.mozilla.fenix.sync.SyncedTabsAdapter
import org.mozilla.fenix.sync.ext.toAdapterItem import org.mozilla.fenix.sync.ext.toAdapterItem
import org.mozilla.fenix.sync.ext.toStringRes import org.mozilla.fenix.sync.ext.toStringRes
import org.mozilla.fenix.tabstray.TabsTrayAction
import org.mozilla.fenix.tabstray.TabsTrayFragment import org.mozilla.fenix.tabstray.TabsTrayFragment
import org.mozilla.fenix.tabstray.TabsTrayStore
import org.mozilla.fenix.tabstray.TrayItem import org.mozilla.fenix.tabstray.TrayItem
import org.mozilla.fenix.utils.view.LifecycleViewProvider import org.mozilla.fenix.utils.view.LifecycleViewProvider
@ -37,6 +39,7 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
private val lifecycleProvider = LifecycleViewProvider(this) private val lifecycleProvider = LifecycleViewProvider(this)
private val coroutineScope = CoroutineScope(Dispatchers.Main) private val coroutineScope = CoroutineScope(Dispatchers.Main)
private val syncedTabsFeature by lazy { private val syncedTabsFeature by lazy {
SyncedTabsFeature( SyncedTabsFeature(
context = context, context = context,
@ -52,6 +55,14 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
) )
} }
private val syncButtonBinding by lazy {
SyncButtonBinding(tabsTrayStore) {
listener?.onRefresh()
}
}
lateinit var tabsTrayStore: TabsTrayStore
override var listener: SyncedTabsView.Listener? = null override var listener: SyncedTabsView.Listener? = null
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) { override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
@ -79,33 +90,28 @@ class SyncedTabsTrayLayout @JvmOverloads constructor(
} }
} }
override fun startLoading() {
// TODO implement with https://github.com/mozilla-mobile/fenix/issues/18515
// start animating FAB
// interactor.syncingTabs(loading = true)
}
override fun stopLoading() {
// TODO implement with https://github.com/mozilla-mobile/fenix/issues/18515
// stop animating FAB
// interactor.syncingTabs(loading = false)
}
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
syncedTabsFeature.start() syncedTabsFeature.start()
syncButtonBinding.start()
} }
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
syncedTabsFeature.stop() syncedTabsFeature.stop()
syncButtonBinding.stop()
coroutineScope.cancel() coroutineScope.cancel()
} }
fun onRefresh() { override fun stopLoading() {
// TODO implement with https://github.com/mozilla-mobile/fenix/issues/18515 tabsTrayStore.dispatch(TabsTrayAction.SyncCompleted)
// syncedTabsFeature.interactor.onRefresh()
} }
/**
* Do nothing; the UI is handled with FloatingActionButtonBinding.
*/
override fun startLoading() = Unit
} }

@ -0,0 +1,22 @@
/* 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.tabstray.syncedtabs
import mozilla.components.browser.storage.sync.Tab
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.tabstray.NavigationInteractor
/**
* A wrapper class that handles tab clicks from a Synced Tabs list.
*/
class TabClickDelegate(
private val interactor: NavigationInteractor
) : SyncedTabsView.Listener {
override fun onTabClicked(tab: Tab) {
interactor.onSyncedTabClicked(tab)
}
override fun onRefresh() = Unit
}

@ -7,12 +7,12 @@ package org.mozilla.fenix.tabstray.viewholders
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.* import kotlinx.android.synthetic.main.component_sync_tabs_tray_layout.*
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.TabsTrayStore
class SyncedTabViewHolder( class SyncedTabViewHolder(
containerView: View, containerView: View,
private val listener: SyncedTabsView.Listener private val tabsTrayStore: TabsTrayStore
) : AbstractTrayViewHolder(containerView) { ) : AbstractTrayViewHolder(containerView) {
override fun bind( override fun bind(
@ -21,7 +21,8 @@ class SyncedTabViewHolder(
) { ) {
synced_tabs_list.layoutManager = layoutManager synced_tabs_list.layoutManager = layoutManager
synced_tabs_list.adapter = adapter synced_tabs_list.adapter = adapter
synced_tabs_tray_layout.listener = listener
synced_tabs_tray_layout.tabsTrayStore = tabsTrayStore
} }
companion object { companion object {

@ -14,7 +14,7 @@ import androidx.lifecycle.LifecycleRegistry
/** /**
* Provides a [LifecycleOwner] on a given [View] for features that function on lifecycle events. * Provides a [LifecycleOwner] on a given [View] for features that function on lifecycle events.
* *
* When the [View] is attached to the window, observers will receive the [Lifecycle.Event.ON_START] event. * When the [View] is attached to the window, observers will receive the [Lifecycle.Event.ON_RESUME] event.
* When the [View] is detached to the window, observers will receive the [Lifecycle.Event.ON_STOP] event. * When the [View] is detached to the window, observers will receive the [Lifecycle.Event.ON_STOP] event.
* *
* @param view The [View] that will be observed. * @param view The [View] that will be observed.
@ -36,7 +36,7 @@ internal class ViewBinding(
private val registry: LifecycleRegistry private val registry: LifecycleRegistry
) : View.OnAttachStateChangeListener { ) : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) { override fun onViewAttachedToWindow(v: View?) {
registry.currentState = State.STARTED registry.currentState = State.RESUMED
} }
override fun onViewDetachedFromWindow(v: View?) { override fun onViewDetachedFromWindow(v: View?) {

@ -20,7 +20,6 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
class AccessibleNewTabButtonBindingTest { class AccessibleNewTabButtonBindingTest {
@ -32,7 +31,6 @@ class AccessibleNewTabButtonBindingTest {
private val settings: Settings = mockk(relaxed = true) private val settings: Settings = mockk(relaxed = true)
private val newTabButton: ImageButton = mockk(relaxed = true) private val newTabButton: ImageButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true) private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
private val syncedTabsInteractor: SyncedTabsInteractor = mockk(relaxed = true)
@Before @Before
fun setup() { fun setup() {
@ -44,7 +42,7 @@ class AccessibleNewTabButtonBindingTest {
fun `WHEN tab selected page is normal tab THEN new tab button is visible`() { fun `WHEN tab selected page is normal tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
@ -57,7 +55,7 @@ class AccessibleNewTabButtonBindingTest {
fun `WHEN tab selected page is private tab THEN new tab button is visible`() { fun `WHEN tab selected page is private tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
@ -70,7 +68,7 @@ class AccessibleNewTabButtonBindingTest {
fun `WHEN tab selected page is sync tab THEN new tab button is visible`() { fun `WHEN tab selected page is sync tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
@ -83,7 +81,7 @@ class AccessibleNewTabButtonBindingTest {
fun `WHEN accessibility is disabled THEN new tab button is not visible`() { fun `WHEN accessibility is disabled THEN new tab button is not visible`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var newTabButtonBinding = AccessibleNewTabButtonBinding( var newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false
@ -93,7 +91,7 @@ class AccessibleNewTabButtonBindingTest {
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding( newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
newTabButtonBinding.start() newTabButtonBinding.start()
@ -102,7 +100,7 @@ class AccessibleNewTabButtonBindingTest {
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding( newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
newTabButtonBinding.start() newTabButtonBinding.start()
@ -114,7 +112,7 @@ class AccessibleNewTabButtonBindingTest {
fun `WHEN selected page is updated THEN button is updated`() { fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding( val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, newTabButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, newTabButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true

@ -19,7 +19,6 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsInteractor
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
class FloatingActionButtonBindingTest { class FloatingActionButtonBindingTest {
@ -31,7 +30,6 @@ class FloatingActionButtonBindingTest {
private val settings: Settings = mockk(relaxed = true) private val settings: Settings = mockk(relaxed = true)
private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true) private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true) private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
private val syncedTabsInteractor: SyncedTabsInteractor = mockk(relaxed = true)
@Before @Before
fun setup() { fun setup() {
@ -43,7 +41,7 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is normal tab THEN shrink and show is called`() { fun `WHEN tab selected page is normal tab THEN shrink and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false
@ -59,7 +57,7 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is private tab THEN extend and show is called`() { fun `WHEN tab selected page is private tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false
@ -75,7 +73,7 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is sync tab THEN extend and show is called`() { fun `WHEN tab selected page is sync tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false
@ -91,7 +89,7 @@ class FloatingActionButtonBindingTest {
fun `WHEN accessibility is enabled THEN show is not called`() { fun `WHEN accessibility is enabled THEN show is not called`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var fabBinding = FloatingActionButtonBinding( var fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns true every { settings.accessibilityServicesEnabled } returns true
@ -102,7 +100,7 @@ class FloatingActionButtonBindingTest {
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
fabBinding = FloatingActionButtonBinding( fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
fabBinding.start() fabBinding.start()
@ -112,7 +110,7 @@ class FloatingActionButtonBindingTest {
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
fabBinding = FloatingActionButtonBinding( fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
fabBinding.start() fabBinding.start()
@ -125,7 +123,7 @@ class FloatingActionButtonBindingTest {
fun `WHEN selected page is updated THEN button is updated`() { fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor, syncedTabsInteractor tabsTrayStore, settings, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false every { settings.accessibilityServicesEnabled } returns false

@ -20,6 +20,8 @@ import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab as createStateTab import mozilla.components.browser.state.state.createTab as createStateTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.storage.sync.TabEntry
import mozilla.components.browser.storage.sync.Tab as SyncTab
import mozilla.components.concept.tabstray.Tab import mozilla.components.concept.tabstray.Tab
import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.rule.MainCoroutineRule
@ -27,6 +29,8 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.collections.CollectionsDialog import org.mozilla.fenix.collections.CollectionsDialog
import org.mozilla.fenix.collections.show import org.mozilla.fenix.collections.show
import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.TabCollectionStorage
@ -50,6 +54,7 @@ class NavigationInteractorTest {
private val context: Context = mockk(relaxed = true) private val context: Context = mockk(relaxed = true)
private val collectionStorage: TabCollectionStorage = mockk(relaxed = true) private val collectionStorage: TabCollectionStorage = mockk(relaxed = true)
private val accountManager: FxaAccountManager = mockk(relaxed = true) private val accountManager: FxaAccountManager = mockk(relaxed = true)
private val activity: HomeActivity = mockk(relaxed = true)
private val testDispatcher = TestCoroutineDispatcher() private val testDispatcher = TestCoroutineDispatcher()
@ -65,6 +70,7 @@ class NavigationInteractorTest {
tabsTrayStore = TabsTrayStore() tabsTrayStore = TabsTrayStore()
navigationInteractor = DefaultNavigationInteractor( navigationInteractor = DefaultNavigationInteractor(
context, context,
activity,
store, store,
navController, navController,
metrics, metrics,
@ -89,6 +95,7 @@ class NavigationInteractorTest {
var onShareTabs = false var onShareTabs = false
var onSaveToCollections = false var onSaveToCollections = false
var onBookmarkTabs = false var onBookmarkTabs = false
var onSyncedTabsClicked = false
class TestNavigationInteractor : NavigationInteractor { class TestNavigationInteractor : NavigationInteractor {
@ -120,6 +127,10 @@ class NavigationInteractorTest {
onBookmarkTabs = true onBookmarkTabs = true
} }
override fun onSyncedTabClicked(tab: mozilla.components.browser.storage.sync.Tab) {
onSyncedTabsClicked = true
}
override fun onShareTabsOfTypeClicked(private: Boolean) { override fun onShareTabsOfTypeClicked(private: Boolean) {
shareTabsOfTypeClicked = true shareTabsOfTypeClicked = true
} }
@ -148,6 +159,8 @@ class NavigationInteractorTest {
assertTrue(onSaveToCollections) assertTrue(onSaveToCollections)
navigationInteractor.onSaveToBookmarks(emptyList()) navigationInteractor.onSaveToBookmarks(emptyList())
assertTrue(onBookmarkTabs) assertTrue(onBookmarkTabs)
navigationInteractor.onSyncedTabClicked(mockk())
assertTrue(onSyncedTabsClicked)
} }
@Test @Test
@ -219,6 +232,7 @@ class NavigationInteractorTest {
fun `onBookmarkTabs calls navigation on DefaultNavigationInteractor`() = runBlockingTest { fun `onBookmarkTabs calls navigation on DefaultNavigationInteractor`() = runBlockingTest {
navigationInteractor = DefaultNavigationInteractor( navigationInteractor = DefaultNavigationInteractor(
context, context,
activity,
store, store,
navController, navController,
metrics, metrics,
@ -233,4 +247,24 @@ class NavigationInteractorTest {
navigationInteractor.onSaveToBookmarks(listOf(createTrayTab())) navigationInteractor.onSaveToBookmarks(listOf(createTrayTab()))
coVerify(exactly = 1) { bookmarksUseCase.addBookmark(any(), any(), any()) } coVerify(exactly = 1) { bookmarksUseCase.addBookmark(any(), any(), any()) }
} }
@Test
fun `onSyncedTabsClicked sets metrics and opens browser`() {
val tab = mockk<SyncTab>()
val entry = mockk<TabEntry>()
every { tab.active() }.answers { entry }
every { entry.url }.answers { "https://mozilla.org" }
navigationInteractor.onSyncedTabClicked(tab)
verify { metrics.track(Event.SyncedTabOpened) }
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = "https://mozilla.org",
newTab = true,
from = BrowserDirection.FromTabTray
)
}
}
} }

@ -0,0 +1,54 @@
/* 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.tabstray.syncedtabs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.tabstray.TabsTrayAction
import org.mozilla.fenix.tabstray.TabsTrayStore
class SyncButtonBindingTest {
@OptIn(ExperimentalCoroutinesApi::class)
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
@Test
fun `WHEN syncing state is true THEN invoke callback`() {
var invoked = false
val store = TabsTrayStore()
val binding = SyncButtonBinding(store) { invoked = true }
binding.start()
store.dispatch(TabsTrayAction.SyncNow)
store.waitUntilIdle()
assertTrue(invoked)
}
@Test
fun `WHEN syncing state is false THEN nothing is invoked`() {
var invoked = false
val store = TabsTrayStore()
val binding = SyncButtonBinding(store) { invoked = true }
binding.start()
store.waitUntilIdle()
assertFalse(invoked)
store.dispatch(TabsTrayAction.SyncCompleted)
store.waitUntilIdle()
assertFalse(invoked)
}
}

@ -0,0 +1,36 @@
/* 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.tabstray.syncedtabs
import io.mockk.Called
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.storage.sync.Tab
import org.junit.Test
import org.mozilla.fenix.tabstray.NavigationInteractor
class TabClickDelegateTest {
private val interactor = mockk<NavigationInteractor>(relaxed = true)
private val tab = mockk<Tab>()
@Test
fun `WHEN tab is clicked THEN invoke the interactor`() {
val delegate = TabClickDelegate(interactor)
delegate.onTabClicked(tab)
verify { interactor.onSyncedTabClicked(tab) }
}
@Test
fun `WHEN refresh is invoked THEN do nothing`() {
val delegate = TabClickDelegate(interactor)
delegate.onRefresh()
verify { interactor wasNot Called }
}
}
Loading…
Cancel
Save