Close #13892: Remove Synced Tabs appended to tabs tray
parent
516a6a343f
commit
2f6fcbf196
@ -1,87 +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.tabtray
|
||||
|
||||
import android.view.View
|
||||
import androidx.fragment.app.FragmentManager.findFragment
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.sync.ListenerDelegate
|
||||
import org.mozilla.fenix.sync.SyncedTabsAdapter
|
||||
import org.mozilla.fenix.sync.ext.toAdapterList
|
||||
import org.mozilla.fenix.sync.ext.toAdapterItem
|
||||
import org.mozilla.fenix.sync.ext.toStringRes
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SyncedTabsController(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
private val view: View,
|
||||
store: TabTrayDialogFragmentStore,
|
||||
private val concatAdapter: ConcatAdapter,
|
||||
coroutineContext: CoroutineContext = Dispatchers.Main,
|
||||
metrics: MetricController
|
||||
) : SyncedTabsView {
|
||||
override var listener: SyncedTabsView.Listener? = null
|
||||
|
||||
val adapter = SyncedTabsAdapter(ListenerDelegate(metrics) { listener })
|
||||
|
||||
private val scope: CoroutineScope = CoroutineScope(coroutineContext)
|
||||
|
||||
init {
|
||||
store.flowScoped(lifecycleOwner) { flow ->
|
||||
flow.map { it.mode }
|
||||
.ifChanged()
|
||||
.drop(1)
|
||||
.collect { mode ->
|
||||
when (mode) {
|
||||
is TabTrayDialogFragmentState.Mode.Normal -> {
|
||||
concatAdapter.addAdapter(adapter)
|
||||
}
|
||||
is TabTrayDialogFragmentState.Mode.MultiSelect -> {
|
||||
concatAdapter.removeAdapter(adapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
|
||||
scope.launch {
|
||||
val tabsList = listOf(SyncedTabsAdapter.AdapterItem.Title) + syncedTabs.toAdapterList()
|
||||
// Reverse layout for TabTrayView which does things backwards.
|
||||
adapter.submitList(tabsList.reversed())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: SyncedTabsView.ErrorType) {
|
||||
scope.launch {
|
||||
val navController: NavController? = try {
|
||||
findFragment<TabTrayDialogFragment>(view).findNavController()
|
||||
} catch (exception: IllegalStateException) {
|
||||
null
|
||||
}
|
||||
|
||||
val descriptionResId = error.toStringRes()
|
||||
val errorItem = error.toAdapterItem(descriptionResId, navController)
|
||||
|
||||
adapter.submitList(listOf(errorItem))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
package org.mozilla.fenix.tabtray
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import io.mockk.Called
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder
|
||||
import org.mozilla.fenix.tabtray.TabTrayDialogFragmentAction.EnterMultiSelectMode
|
||||
import org.mozilla.fenix.tabtray.TabTrayDialogFragmentAction.ExitMultiSelectMode
|
||||
import org.mozilla.fenix.tabtray.TabTrayDialogFragmentState.Mode
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class SyncedTabsControllerTest {
|
||||
|
||||
private val testDispatcher = TestCoroutineDispatcher()
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
|
||||
|
||||
private lateinit var view: View
|
||||
private lateinit var controller: SyncedTabsController
|
||||
private lateinit var lifecycleOwner: LifecycleOwner
|
||||
private lateinit var lifecycle: LifecycleRegistry
|
||||
private lateinit var concatAdapter: ConcatAdapter
|
||||
private lateinit var store: TabTrayDialogFragmentStore
|
||||
|
||||
@Before
|
||||
fun setup() = runBlockingTest {
|
||||
lifecycleOwner = mockk()
|
||||
lifecycle = LifecycleRegistry(lifecycleOwner)
|
||||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
every { lifecycleOwner.lifecycle } returns lifecycle
|
||||
|
||||
concatAdapter = mockk()
|
||||
every { concatAdapter.addAdapter(any()) } returns true
|
||||
every { concatAdapter.removeAdapter(any()) } returns true
|
||||
|
||||
store = TabTrayDialogFragmentStore(
|
||||
initialState = TabTrayDialogFragmentState(
|
||||
mode = Mode.Normal,
|
||||
browserState = mockk(relaxed = true)
|
||||
)
|
||||
)
|
||||
|
||||
view = LayoutInflater.from(testContext).inflate(R.layout.about_list_item, null)
|
||||
val metrics: MetricController = mockk()
|
||||
controller =
|
||||
SyncedTabsController(lifecycleOwner, view, store, concatAdapter, coroutineContext, metrics)
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
testDispatcher.cleanupTestCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `display synced tabs in reverse`() {
|
||||
val tabs = listOf(
|
||||
SyncedDeviceTabs(
|
||||
device = mockk(relaxed = true),
|
||||
tabs = listOf(
|
||||
mockk(relaxed = true),
|
||||
mockk(relaxed = true)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
controller.displaySyncedTabs(tabs)
|
||||
|
||||
val itemCount = controller.adapter.itemCount
|
||||
|
||||
// title + device name + 2 tabs
|
||||
assertEquals(4, itemCount)
|
||||
assertEquals(
|
||||
SyncedTabsViewHolder.TitleViewHolder.LAYOUT_ID,
|
||||
controller.adapter.getItemViewType(itemCount - 1)
|
||||
)
|
||||
assertEquals(
|
||||
SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID,
|
||||
controller.adapter.getItemViewType(itemCount - 2)
|
||||
)
|
||||
assertEquals(
|
||||
SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID,
|
||||
controller.adapter.getItemViewType(itemCount - 3)
|
||||
)
|
||||
assertEquals(
|
||||
SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID,
|
||||
controller.adapter.getItemViewType(itemCount - 4)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `show error when we go kaput`() {
|
||||
controller.onError(ErrorType.SYNC_NEEDS_REAUTHENTICATION)
|
||||
|
||||
assertEquals(1, controller.adapter.itemCount)
|
||||
assertEquals(
|
||||
SyncedTabsViewHolder.ErrorViewHolder.LAYOUT_ID,
|
||||
controller.adapter.getItemViewType(0)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `do nothing on init, drop first event`() {
|
||||
verify { concatAdapter wasNot Called }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `concatAdapter updated on mode changes`() = testDispatcher.runBlockingTest {
|
||||
store.dispatch(EnterMultiSelectMode).joinBlocking()
|
||||
verify { concatAdapter.removeAdapter(any()) }
|
||||
|
||||
store.dispatch(ExitMultiSelectMode).joinBlocking()
|
||||
// When returning from Multiselect the adapter should be added at the end
|
||||
verify { concatAdapter.addAdapter(any()) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue