1
0
Fork 0

Test sync adapter (#12810)

master
Tiger Oakes 2020-07-23 11:16:11 -07:00 committed by GitHub
parent 4e9fc88f63
commit 0d77f761e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 39 deletions

View File

@ -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) =

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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)
)
}
}