Test sync adapter (#12810)
parent
4e9fc88f63
commit
0d77f761e9
|
@ -8,16 +8,15 @@ import android.view.LayoutInflater
|
|||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import mozilla.components.concept.sync.Device as SyncDevice
|
||||
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
||||
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
||||
import mozilla.components.concept.sync.Device as SyncDevice
|
||||
|
||||
class SyncedTabsAdapter(
|
||||
private val listener: (SyncTab) -> Unit
|
||||
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(
|
||||
DiffCallback
|
||||
) {
|
||||
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(DiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SyncedTabsViewHolder {
|
||||
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
@ -30,23 +29,35 @@ class SyncedTabsAdapter(
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) {
|
||||
val item = when (holder) {
|
||||
is DeviceViewHolder -> getItem(position) as AdapterItem.Device
|
||||
is TabViewHolder -> getItem(position) as AdapterItem.Tab
|
||||
}
|
||||
holder.bind(item, listener)
|
||||
holder.bind(getItem(position), listener)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (getItem(position)) {
|
||||
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
||||
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
||||
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
||||
}
|
||||
|
||||
fun updateData(syncedTabs: List<SyncedDeviceTabs>) {
|
||||
val allDeviceTabs = mutableListOf<AdapterItem>()
|
||||
|
||||
syncedTabs.forEach { (device, tabs) ->
|
||||
if (tabs.isNotEmpty()) {
|
||||
allDeviceTabs.add(AdapterItem.Device(device))
|
||||
tabs.mapTo(allDeviceTabs) { AdapterItem.Tab(it) }
|
||||
}
|
||||
}
|
||||
|
||||
submitList(allDeviceTabs)
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||
areContentsTheSame(oldItem, newItem)
|
||||
when (oldItem) {
|
||||
is AdapterItem.Device ->
|
||||
newItem is AdapterItem.Device && oldItem.device.id == newItem.device.id
|
||||
is AdapterItem.Tab ->
|
||||
oldItem == newItem
|
||||
}
|
||||
|
||||
@Suppress("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||
|
|
|
@ -10,10 +10,10 @@ import android.view.View
|
|||
import android.widget.FrameLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -43,15 +43,7 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
|||
// We may still be displaying a "loading" spinner, hide it.
|
||||
stopLoading()
|
||||
|
||||
val stringResId = when (error) {
|
||||
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
|
||||
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
|
||||
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
|
||||
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
|
||||
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs
|
||||
}
|
||||
|
||||
sync_tabs_status.text = context.getText(stringResId)
|
||||
sync_tabs_status.text = context.getText(stringResourceForError(error))
|
||||
|
||||
synced_tabs_list.visibility = View.GONE
|
||||
sync_tabs_status.visibility = View.VISIBLE
|
||||
|
@ -65,19 +57,7 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
|||
synced_tabs_list.visibility = View.VISIBLE
|
||||
sync_tabs_status.visibility = View.GONE
|
||||
|
||||
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
|
||||
|
||||
syncedTabs.forEach { (device, tabs) ->
|
||||
if (tabs.isEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val deviceTabs = tabs.map { SyncedTabsAdapter.AdapterItem.Tab(it) }
|
||||
|
||||
allDeviceTabs += listOf(SyncedTabsAdapter.AdapterItem.Device(device)) + deviceTabs
|
||||
}
|
||||
|
||||
adapter.submitList(allDeviceTabs)
|
||||
adapter.updateData(syncedTabs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,5 +90,13 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
|||
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE,
|
||||
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> true
|
||||
}
|
||||
|
||||
internal fun stringResourceForError(error: SyncedTabsView.ErrorType) = when (error) {
|
||||
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
|
||||
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
|
||||
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
|
||||
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
|
||||
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import mozilla.components.browser.storage.sync.Tab
|
||||
import mozilla.components.browser.storage.sync.TabEntry
|
||||
import mozilla.components.concept.sync.DeviceType
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class SyncedTabsAdapterTest {
|
||||
|
||||
private lateinit var listener: (Tab) -> Unit
|
||||
private lateinit var adapter: SyncedTabsAdapter
|
||||
|
||||
private val oneTabDevice = SyncedDeviceTabs(
|
||||
device = mockk {
|
||||
every { displayName } returns "Charcoal"
|
||||
every { deviceType } returns DeviceType.DESKTOP
|
||||
},
|
||||
tabs = listOf(Tab(
|
||||
history = listOf(TabEntry(
|
||||
title = "Mozilla",
|
||||
url = "https://mozilla.org",
|
||||
iconUrl = null
|
||||
)),
|
||||
active = 0,
|
||||
lastUsed = 0L
|
||||
))
|
||||
)
|
||||
|
||||
private val threeTabDevice = SyncedDeviceTabs(
|
||||
device = mockk {
|
||||
every { displayName } returns "Emerald"
|
||||
every { deviceType } returns DeviceType.MOBILE
|
||||
},
|
||||
tabs = listOf(
|
||||
Tab(
|
||||
history = listOf(TabEntry(
|
||||
title = "Mozilla",
|
||||
url = "https://mozilla.org",
|
||||
iconUrl = null
|
||||
)),
|
||||
active = 0,
|
||||
lastUsed = 0L
|
||||
),
|
||||
Tab(
|
||||
history = listOf(TabEntry(
|
||||
title = "Firefox",
|
||||
url = "https://firefox.com",
|
||||
iconUrl = null
|
||||
)),
|
||||
active = 0,
|
||||
lastUsed = 0L
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
listener = mockk(relaxed = true)
|
||||
adapter = SyncedTabsAdapter(listener)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateData() adds items for each device and tab`() {
|
||||
assertEquals(0, adapter.itemCount)
|
||||
|
||||
adapter.updateData(listOf(
|
||||
oneTabDevice,
|
||||
threeTabDevice
|
||||
))
|
||||
|
||||
assertEquals(5, adapter.itemCount)
|
||||
assertEquals(SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID, adapter.getItemViewType(0))
|
||||
assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(1))
|
||||
assertEquals(SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID, adapter.getItemViewType(2))
|
||||
assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(3))
|
||||
assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(4))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adapter can create and bind viewholders for SyncedDeviceTabs`() {
|
||||
val parent = FrameLayout(testContext)
|
||||
adapter.updateData(listOf(oneTabDevice))
|
||||
|
||||
val deviceHolder = adapter.createViewHolder(parent, SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID)
|
||||
val tabHolder = adapter.createViewHolder(parent, SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID)
|
||||
|
||||
// Should not throw
|
||||
adapter.bindViewHolder(deviceHolder, 0)
|
||||
adapter.bindViewHolder(tabHolder, 1)
|
||||
}
|
||||
}
|
|
@ -5,11 +5,14 @@
|
|||
package org.mozilla.fenix.sync
|
||||
|
||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class SyncedTabsLayoutTest {
|
||||
|
||||
@Test
|
||||
fun `pull to refresh state`() {
|
||||
assertTrue(SyncedTabsLayout.pullToRefreshEnableState(ErrorType.MULTIPLE_DEVICES_UNAVAILABLE))
|
||||
|
@ -18,4 +21,28 @@ class SyncedTabsLayoutTest {
|
|||
assertFalse(SyncedTabsLayout.pullToRefreshEnableState(ErrorType.SYNC_NEEDS_REAUTHENTICATION))
|
||||
assertFalse(SyncedTabsLayout.pullToRefreshEnableState(ErrorType.SYNC_UNAVAILABLE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `string resource for error`() {
|
||||
assertEquals(
|
||||
R.string.synced_tabs_connect_another_device,
|
||||
SyncedTabsLayout.stringResourceForError(ErrorType.MULTIPLE_DEVICES_UNAVAILABLE)
|
||||
)
|
||||
assertEquals(
|
||||
R.string.synced_tabs_enable_tab_syncing,
|
||||
SyncedTabsLayout.stringResourceForError(ErrorType.SYNC_ENGINE_UNAVAILABLE)
|
||||
)
|
||||
assertEquals(
|
||||
R.string.synced_tabs_connect_to_sync_account,
|
||||
SyncedTabsLayout.stringResourceForError(ErrorType.SYNC_UNAVAILABLE)
|
||||
)
|
||||
assertEquals(
|
||||
R.string.synced_tabs_reauth,
|
||||
SyncedTabsLayout.stringResourceForError(ErrorType.SYNC_NEEDS_REAUTHENTICATION)
|
||||
)
|
||||
assertEquals(
|
||||
R.string.synced_tabs_no_tabs,
|
||||
SyncedTabsLayout.stringResourceForError(ErrorType.NO_TABS_AVAILABLE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue