Unverified Commit 7e8f0792 authored by Tiger Oakes's avatar Tiger Oakes Committed by GitHub
Browse files

Use ShareData with ShareFragment (#6698)

parent d69db509
......@@ -23,6 +23,7 @@ import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
......@@ -32,8 +33,8 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
......@@ -144,11 +145,11 @@ class DefaultBrowserToolbarController(
}
}
ToolbarMenu.Item.Share -> {
val currentUrl = currentSession?.url
currentUrl?.apply {
val directions = NavGraphDirections.actionGlobalShareFragment(this)
navController.navigate(directions)
}
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = currentSession?.url, title = currentSession?.title)),
showPage = true
)
navController.navigate(directions)
}
ToolbarMenu.Item.NewTab -> {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
......
......@@ -37,7 +37,6 @@ import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay
......@@ -47,6 +46,7 @@ import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
......@@ -95,7 +95,6 @@ import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.share.ShareTab
import org.mozilla.fenix.utils.FragmentPreDrawManager
import org.mozilla.fenix.utils.allowUndo
import org.mozilla.fenix.whatsnew.WhatsNew
......@@ -230,14 +229,14 @@ class HomeFragment : Fragment() {
setupHomeMenu()
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
viewLifecycleOwner.lifecycleScope.launch(IO) {
val iconSize = resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size)
val searchEngine = requireComponents.search.provider.getDefaultEngine(requireContext())
val searchIcon = BitmapDrawable(resources, searchEngine.icon)
searchIcon.setBounds(0, 0, iconSize, iconSize)
withContext(Dispatchers.Main) {
withContext(Main) {
search_engine_icon?.setImageDrawable(searchIcon)
}
}
......@@ -409,7 +408,7 @@ class HomeFragment : Fragment() {
is TabAction.Share -> {
invokePendingDeleteJobs()
sessionManager.findSessionById(action.sessionId)?.let { session ->
share(session.url)
share(listOf(ShareData(url = session.url)))
}
}
is TabAction.PauseMedia -> {
......@@ -449,11 +448,11 @@ class HomeFragment : Fragment() {
is TabAction.ShareTabs -> {
invokePendingDeleteJobs()
val shareTabs = sessionManager
val shareData = sessionManager
.sessionsOfType(private = browsingModeManager.mode.isPrivate)
.map { ShareTab(it.url, it.title) }
.map { ShareData(url = it.url, title = it.title) }
.toList()
share(tabs = shareTabs)
share(shareData)
}
}
}
......@@ -583,8 +582,7 @@ class HomeFragment : Fragment() {
components.analytics.metrics.track(Event.CollectionAllTabsRestored)
}
is CollectionAction.ShareTabs -> {
val shareTabs = action.collection.tabs.map { ShareTab(it.url, it.title) }
share(tabs = shareTabs)
share(action.collection.tabs.map { ShareData(url = it.url, title = it.title) })
requireComponents.analytics.metrics.track(Event.CollectionShared)
}
is CollectionAction.RemoveTab -> {
......@@ -853,12 +851,10 @@ class HomeFragment : Fragment() {
showCollectionCreationFragment(step, selectedTabId?.let { arrayOf(it) })
}
private fun share(url: String? = null, tabs: List<ShareTab>? = null) {
val directions =
HomeFragmentDirections.actionHomeFragmentToShareFragment(
url = url,
tabs = tabs?.toTypedArray()
)
private fun share(data: List<ShareData>) {
val directions = HomeFragmentDirections.actionHomeFragmentToShareFragment(
data = data.toTypedArray()
)
nav(R.id.homeFragment, directions)
}
......
......@@ -11,6 +11,7 @@ import android.content.res.Resources
import androidx.core.content.getSystemService
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
......@@ -21,7 +22,6 @@ import org.mozilla.fenix.components.Services
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.share.ShareTab
/**
* [BookmarkFragment] controller.
......@@ -84,9 +84,7 @@ class DefaultBookmarkController(
override fun handleBookmarkSharing(item: BookmarkNode) {
navigate(
BookmarkFragmentDirections.actionBookmarkFragmentToShareFragment(
url = item.url!!,
title = item.title,
tabs = arrayOf(ShareTab(item.url!!, item.title!!))
data = arrayOf(ShareData(url = item.url, title = item.title))
)
)
}
......
......@@ -29,6 +29,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.concept.sync.AccountObserver
......@@ -46,7 +47,6 @@ import org.mozilla.fenix.ext.minus
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.share.ShareTab
import org.mozilla.fenix.utils.allowUndo
@Suppress("TooManyFunctions", "LargeClass")
......@@ -196,9 +196,7 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), BackHandler {
val bookmark = bookmarkStore.state.mode.selectedItems.first()
navigate(
BookmarkFragmentDirections.actionBookmarkFragmentToShareFragment(
url = bookmark.url,
title = bookmark.title,
tabs = arrayOf(ShareTab(bookmark.url.orEmpty(), bookmark.title.orEmpty()))
data = arrayOf(ShareData(url = bookmark.url, title = bookmark.title))
)
)
true
......
......@@ -21,6 +21,7 @@ import kotlinx.android.synthetic.main.fragment_history.view.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.BrowserDirection
......@@ -35,7 +36,6 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.share.ShareTab
@SuppressWarnings("TooManyFunctions", "LargeClass")
class HistoryFragment : LibraryPageFragment<HistoryItem>(), BackHandler {
......@@ -143,18 +143,8 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), BackHandler {
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.share_history_multi_select -> {
val selectedHistory = historyStore.state.mode.selectedItems
val shareTabs = selectedHistory.map { ShareTab(it.url, it.title) }
when {
selectedHistory.size == 1 ->
share(
url = selectedHistory.first().url,
title = selectedHistory.first().title,
tabs = shareTabs
)
selectedHistory.size > 1 -> {
share(tabs = shareTabs)
}
}
val shareTabs = selectedHistory.map { ShareData(url = it.url, title = it.title) }
share(shareTabs)
true
}
R.id.delete_history_multi_select -> {
......@@ -243,14 +233,11 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), BackHandler {
}
}
private fun share(url: String? = null, title: String? = null, tabs: List<ShareTab>? = null) {
private fun share(data: List<ShareData>) {
requireComponents.analytics.metrics.track(Event.HistoryItemShared)
val directions =
HistoryFragmentDirections.actionHistoryFragmentToShareFragment(
url = url,
title = title,
tabs = tabs?.toTypedArray()
)
val directions = HistoryFragmentDirections.actionHistoryFragmentToShareFragment(
data = data.toTypedArray()
)
nav(R.id.historyFragment, directions)
}
}
......@@ -9,6 +9,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.share_close.*
import mozilla.components.concept.engine.prompt.ShareData
import org.mozilla.fenix.R
import org.mozilla.fenix.share.listadapters.ShareTabsAdapter
......@@ -36,7 +37,7 @@ class ShareCloseView(
shared_site_list.adapter = adapter
}
fun setTabs(tabs: List<ShareTab>) {
fun setTabs(tabs: List<ShareData>) {
adapter.submitList(tabs)
}
}
......@@ -16,6 +16,7 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.TabData
import mozilla.components.feature.sendtab.SendTabUseCases
......@@ -47,7 +48,7 @@ interface ShareController {
* Default behavior of [ShareController]. Other implementations are possible.
*
* @param context [Context] used for various Android interactions.
* @param sharedTabs the list of [ShareTab]s that can be shared.
* @param shareData the list of [ShareData]s that can be shared.
* @param sendTabUseCases instance of [SendTabUseCases] which allows sending tabs to account devices.
* @param snackbarPresenter - instance of [FenixSnackbarPresenter] for displaying styled snackbars
* @param navController - [NavController] used for navigation.
......@@ -56,12 +57,13 @@ interface ShareController {
@Suppress("TooManyFunctions")
class DefaultShareController(
private val context: Context,
private val sharedTabs: List<ShareTab>,
private val shareData: List<ShareData>,
private val sendTabUseCases: SendTabUseCases,
private val snackbarPresenter: FenixSnackbarPresenter,
private val navController: NavController,
private val dismiss: () -> Unit
) : ShareController {
override fun handleReauth() {
val directions = ShareFragmentDirections.actionShareFragmentToAccountProblemFragment()
navController.nav(R.id.shareFragment, directions)
......@@ -99,11 +101,11 @@ class DefaultShareController(
override fun handleShareToDevice(device: Device) {
context.metrics.track(Event.SendTab)
shareToDevicesWithRetry { sendTabUseCases.sendToDeviceAsync(device.id, sharedTabs.toTabData()) }
shareToDevicesWithRetry { sendTabUseCases.sendToDeviceAsync(device.id, shareData.toTabData()) }
}
override fun handleShareToAllDevices(devices: List<Device>) {
shareToDevicesWithRetry { sendTabUseCases.sendToAllAsync(sharedTabs.toTabData()) }
shareToDevicesWithRetry { sendTabUseCases.sendToAllAsync(shareData.toTabData()) }
}
override fun handleSignIn() {
......@@ -146,19 +148,20 @@ class DefaultShareController(
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun getSuccessMessage(): String = with(context) {
when (sharedTabs.size) {
when (shareData.size) {
1 -> getString(R.string.sync_sent_tab_snackbar)
else -> getString(R.string.sync_sent_tabs_snackbar)
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun getShareText() = sharedTabs.joinToString("\n") { tab -> tab.url }
@VisibleForTesting
fun getShareText() = shareData.joinToString("\n") { data ->
listOfNotNull(data.url, data.text).joinToString(" ")
}
// Navigation between app fragments uses ShareTab as arguments. SendTabUseCases uses TabData.
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun ShareTab.toTabData() = TabData(title, url)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun List<ShareTab>.toTabData() = map { it.toTabData() }
@VisibleForTesting
fun List<ShareData>.toTabData() = map { data ->
TabData(data.title.orEmpty(), data.url.orEmpty())
}
}
......@@ -6,7 +6,6 @@ package org.mozilla.fenix.share
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......@@ -16,7 +15,6 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.observe
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_share.view.*
import mozilla.components.feature.sendtab.SendTabUseCases
import org.mozilla.fenix.R
......@@ -51,15 +49,14 @@ class ShareFragment : AppCompatDialogFragment() {
): View? {
val view = inflater.inflate(R.layout.fragment_share, container, false)
val args by navArgs<ShareFragmentArgs>()
check(!(args.url == null && args.tabs.isNullOrEmpty())) { "URL and tabs cannot both be null." }
val shareData = args.data.toList()
val tabs = args.tabs?.toList() ?: listOf(ShareTab(args.url!!, args.title.orEmpty()))
val accountManager = requireComponents.backgroundServices.accountManager
shareInteractor = ShareInteractor(
DefaultShareController(
context = requireContext(),
sharedTabs = tabs,
shareData = shareData,
snackbarPresenter = FenixSnackbarPresenter(activity!!.getRootView()!!),
navController = findNavController(),
sendTabUseCases = SendTabUseCases(accountManager),
......@@ -71,17 +68,16 @@ class ShareFragment : AppCompatDialogFragment() {
shareToAccountDevicesView =
ShareToAccountDevicesView(view.devicesShareLayout, shareInteractor)
if (args.url != null && args.tabs == null) {
// If sharing one tab from the browser fragment, show it.
// If URL is set and tabs is null, we assume the browser is visible, since navigation
// does not tell us the back stack state.
if (args.showPage) {
// Show the previous fragment underneath the share background scrim
// by making it translucent.
view.closeSharingScrim.alpha = SHOW_PAGE_ALPHA
view.shareWrapper.setOnClickListener { shareInteractor.onShareClosed() }
} else {
// Otherwise, show a list of tabs to share.
view.closeSharingScrim.alpha = 1.0f
shareCloseView = ShareCloseView(view.closeSharingContent, shareInteractor)
shareCloseView.setTabs(tabs)
shareCloseView.setTabs(shareData)
}
shareToAppsView = ShareToAppsView(view.appsShareLayout, shareInteractor)
......@@ -102,6 +98,3 @@ class ShareFragment : AppCompatDialogFragment() {
const val SHOW_PAGE_ALPHA = 0.6f
}
}
@Parcelize
data class ShareTab(val url: String, val title: String) : Parcelable
......@@ -11,16 +11,16 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.share_tab_item.view.*
import mozilla.components.concept.engine.prompt.ShareData
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.share.ShareTab
/**
* Adapter for a list of tabs to be shared.
*/
class ShareTabsAdapter :
ListAdapter<ShareTab, ShareTabsAdapter.ShareTabViewHolder>(ShareTabDiffCallback) {
ListAdapter<ShareData, ShareTabsAdapter.ShareTabViewHolder>(ShareTabDiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ShareTabViewHolder(
LayoutInflater.from(parent.context)
......@@ -32,18 +32,18 @@ class ShareTabsAdapter :
class ShareTabViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: ShareTab) = with(itemView) {
context.components.core.icons.loadIntoView(itemView.share_tab_favicon, item.url)
fun bind(item: ShareData) = with(itemView) {
context.components.core.icons.loadIntoView(itemView.share_tab_favicon, item.url.orEmpty())
itemView.share_tab_title.text = item.title
itemView.share_tab_url.text = item.url
}
}
private object ShareTabDiffCallback : DiffUtil.ItemCallback<ShareTab>() {
override fun areItemsTheSame(oldItem: ShareTab, newItem: ShareTab) =
private object ShareTabDiffCallback : DiffUtil.ItemCallback<ShareData>() {
override fun areItemsTheSame(oldItem: ShareData, newItem: ShareData) =
oldItem.url == newItem.url
override fun areContentsTheSame(oldItem: ShareTab, newItem: ShareTab) =
override fun areContentsTheSame(oldItem: ShareData, newItem: ShareData) =
oldItem == newItem
}
}
......@@ -542,20 +542,12 @@
android:name="org.mozilla.fenix.share.ShareFragment"
tools:layout="@layout/fragment_share">
<argument
android:name="url"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
android:name="data"
app:argType="mozilla.components.concept.engine.prompt.ShareData[]" />
<argument
android:name="tabs"
android:defaultValue="@null"
app:argType="org.mozilla.fenix.share.ShareTab[]"
app:nullable="true" />
android:name="showPage"
app:argType="boolean"
android:defaultValue="false" />
<action
android:id="@+id/action_shareFragment_to_turnOnSyncFragment"
app:destination="@+id/turnOnSyncFragment"
......
......@@ -34,7 +34,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.browser.BrowserFragmentDirections
......@@ -306,12 +305,11 @@ class DefaultBrowserToolbarControllerTest {
val item = ToolbarMenu.Item.Share
every { currentSession.url } returns "https://mozilla.org"
val directions = NavGraphDirections.actionGlobalShareFragment(currentSession.url)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) }
verify { navController.navigate(directions) }
verify { navController.navigate(any<NavDirections>()) }
}
@Test
......
......@@ -10,7 +10,6 @@ import android.content.Intent
import androidx.navigation.NavController
import assertk.assertAll
import assertk.assertThat
import assertk.assertions.isDataClassEqualTo
import assertk.assertions.isEqualTo
import assertk.assertions.isNotEqualTo
import assertk.assertions.isSameAs
......@@ -25,6 +24,7 @@ import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.sync.Device
import mozilla.components.concept.sync.DeviceType
import mozilla.components.concept.sync.TabData
......@@ -50,22 +50,22 @@ class ShareControllerTest {
// Need a valid context to retrieve Strings for example, but we also need it to return our "metrics"
private val context: Context = spyk(testContext)
private val metrics: MetricController = mockk(relaxed = true)
private val shareTabs = listOf(
ShareTab("url0", "title0"),
ShareTab("url1", "title1")
private val shareData = listOf(
ShareData(url = "url0", title = "title0"),
ShareData(url = "url1", title = "title1")
)
// Navigation between app fragments uses ShareTab as arguments. SendTabUseCases uses TabData.
private val tabsData = listOf(
TabData("title0", "url0"),
TabData("title1", "url1")
)
private val textToShare = "${shareTabs[0].url}\n${shareTabs[1].url}"
private val textToShare = "${shareData[0].url}\n${shareData[1].url}"
private val sendTabUseCases = mockk<SendTabUseCases>(relaxed = true)
private val snackbarPresenter = mockk<FenixSnackbarPresenter>(relaxed = true)
private val navController = mockk<NavController>(relaxed = true)
private val dismiss = mockk<() -> Unit>(relaxed = true)
private val controller = DefaultShareController(
context, shareTabs, sendTabUseCases, snackbarPresenter, navController, dismiss
context, shareData, sendTabUseCases, snackbarPresenter, navController, dismiss
)
@Before
......@@ -90,7 +90,7 @@ class ShareControllerTest {
// needed for capturing the actual Intent used the `slot` one doesn't have this flag so we
// need to use an Activity Context.
val activityContext: Context = mockk<Activity>()
val testController = DefaultShareController(activityContext, shareTabs, mockk(), mockk(), mockk(), dismiss)
val testController = DefaultShareController(activityContext, shareData, mockk(), mockk(), mockk(), dismiss)
every { activityContext.startActivity(capture(shareIntent)) } just Runs
testController.handleShareToApp(appShareOption)
......@@ -245,7 +245,7 @@ class ShareControllerTest {
@Test
fun `getSuccessMessage should return different strings depending on the number of shared tabs`() {
val controllerWithOneSharedTab = DefaultShareController(
context, listOf(ShareTab("url0", "title0")), mockk(), mockk(), mockk(), mockk()
context, listOf(ShareData(url = "url0", title = "title0")), mockk(), mockk(), mockk(), mockk()
)
val controllerWithMoreSharedTabs = controller
val expectedTabSharedMessage = context.getString(R.string.sync_sent_tab_snackbar)
......@@ -266,23 +266,12 @@ class ShareControllerTest {
assertThat(controller.getShareText()).isEqualTo(textToShare)
}
@Test
fun `ShareTab#toTabData maps a ShareTab to a TabData`() {
var tabData: TabData
with(controller) {
tabData = shareTabs[0].toTabData()
}
assertThat(tabData).isDataClassEqualTo(tabsData[0])
}
@Test
fun `ShareTab#toTabData maps a list of ShareTab to a TabData list`() {
var tabData: List<TabData>
with(controller) {
tabData = shareTabs.toTabData()
tabData = shareData.toTabData()
}
assertThat(tabData).isEqualTo(tabsData)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment