Test sync adapter (#12810)
parent
4e9fc88f63
commit
0d77f761e9
|
@ -8,16 +8,15 @@ import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import mozilla.components.concept.sync.Device as SyncDevice
|
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||||
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
|
||||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
||||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
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(
|
class SyncedTabsAdapter(
|
||||||
private val listener: (SyncTab) -> Unit
|
private val listener: (SyncTab) -> Unit
|
||||||
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(
|
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(DiffCallback) {
|
||||||
DiffCallback
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SyncedTabsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SyncedTabsViewHolder {
|
||||||
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
@ -30,23 +29,35 @@ class SyncedTabsAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) {
|
||||||
val item = when (holder) {
|
holder.bind(getItem(position), listener)
|
||||||
is DeviceViewHolder -> getItem(position) as AdapterItem.Device
|
|
||||||
is TabViewHolder -> getItem(position) as AdapterItem.Tab
|
|
||||||
}
|
|
||||||
holder.bind(item, listener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
||||||
return when (getItem(position)) {
|
|
||||||
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||||
is AdapterItem.Tab -> TabViewHolder.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>() {
|
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
override fun areItemsTheSame(oldItem: AdapterItem, newItem: 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")
|
@Suppress("DiffUtilEquals")
|
||||||
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
|
|
@ -10,10 +10,10 @@ import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
@ -43,15 +43,7 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
||||||
// We may still be displaying a "loading" spinner, hide it.
|
// We may still be displaying a "loading" spinner, hide it.
|
||||||
stopLoading()
|
stopLoading()
|
||||||
|
|
||||||
val stringResId = when (error) {
|
sync_tabs_status.text = context.getText(stringResourceForError(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)
|
|
||||||
|
|
||||||
synced_tabs_list.visibility = View.GONE
|
synced_tabs_list.visibility = View.GONE
|
||||||
sync_tabs_status.visibility = View.VISIBLE
|
sync_tabs_status.visibility = View.VISIBLE
|
||||||
|
@ -65,19 +57,7 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
||||||
synced_tabs_list.visibility = View.VISIBLE
|
synced_tabs_list.visibility = View.VISIBLE
|
||||||
sync_tabs_status.visibility = View.GONE
|
sync_tabs_status.visibility = View.GONE
|
||||||
|
|
||||||
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
|
adapter.updateData(syncedTabs)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,5 +90,13 @@ class SyncedTabsLayout @JvmOverloads constructor(
|
||||||
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE,
|
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE,
|
||||||
SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> true
|
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
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType
|
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.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
class SyncedTabsLayoutTest {
|
class SyncedTabsLayoutTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `pull to refresh state`() {
|
fun `pull to refresh state`() {
|
||||||
assertTrue(SyncedTabsLayout.pullToRefreshEnableState(ErrorType.MULTIPLE_DEVICES_UNAVAILABLE))
|
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_NEEDS_REAUTHENTICATION))
|
||||||
assertFalse(SyncedTabsLayout.pullToRefreshEnableState(ErrorType.SYNC_UNAVAILABLE))
|
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