Bug 1875465 - Part 3: Add tab strip ui for tablets

fenix/125.0
rahulsainani 4 months ago committed by mergify[bot]
parent c91378ceb7
commit 54d7785896

@ -10,7 +10,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
@ -37,7 +39,9 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.GleanMetrics.ReaderMode
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.tabstrip.TabStrip
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.appstate.AppAction
@ -48,11 +52,13 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode
import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature
import org.mozilla.fenix.shopping.ReviewQualityCheckFeature
import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.SESSION_ID
import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.TRANSLATION_IN_PROGRESS
@ -87,6 +93,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
val context = requireContext()
val components = context.components
initTabStrip()
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
binding.gestureLayout.addGestureListener(
@ -238,6 +245,36 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
}
}
private fun initTabStrip() {
if (!resources.getBoolean(R.bool.tablet)) {
return
}
binding.tabStripView.isVisible = true
binding.tabStripView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
FirefoxTheme {
TabStrip(
onAddTabClick = {
findNavController().navigate(
NavGraphDirections.actionGlobalHome(
focusOnAddressBar = true,
),
)
},
onLastTabClose = {
findNavController().navigate(
BrowserFragmentDirections.actionGlobalHome(),
)
},
onSelectedTabClick = {},
)
}
}
}
}
private fun initTranslationsAction(context: Context, view: View) {
if (!context.settings().enableTranslations) {
return

@ -0,0 +1,414 @@
/* 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.browser.tabstrip
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.systemGestureExclusion
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.coerceIn
import androidx.compose.ui.unit.dp
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.lib.state.ext.observeAsState
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.Favicon
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
private val minTabStripItemWidth = 160.dp
private val maxTabStripItemWidth = 280.dp
private val tabStripIconSize = 24.dp
private val spaceBetweenTabs = 4.dp
private val tabStripStartPadding = 8.dp
private val addTabIconSize = 20.dp
/**
* Top level composable for the tabs strip.
*
* @param onHome Whether or not the tabs strip is in the home screen.
* @param browserStore The [BrowserStore] instance used to observe tabs state.
* @param appStore The [AppStore] instance used to observe browsing mode.
* @param tabsUseCases The [TabsUseCases] instance to perform tab actions.
* @param onAddTabClick Invoked when the add tab button is clicked.
* @param onLastTabClose Invoked when the last remaining open tab is closed.
* @param onSelectedTabClick Invoked when a tab is selected.
*/
@Composable
fun TabStrip(
onHome: Boolean = false,
browserStore: BrowserStore = components.core.store,
appStore: AppStore = components.appStore,
tabsUseCases: TabsUseCases = components.useCases.tabsUseCases,
onAddTabClick: () -> Unit,
onLastTabClose: () -> Unit,
onSelectedTabClick: () -> Unit,
) {
val isPrivateMode by appStore.observeAsState(false) { it.mode.isPrivate }
val state by browserStore.observeAsState(TabStripState.initial) {
it.toTabStripState(isSelectDisabled = onHome, isPrivateMode = isPrivateMode)
}
TabStripContent(
state = state,
onAddTabClick = onAddTabClick,
onCloseTabClick = {
if (state.tabs.size == 1) {
onLastTabClose()
}
tabsUseCases.removeTab(it)
},
onSelectedTabClick = {
tabsUseCases.selectTab(it)
onSelectedTabClick()
},
)
}
@Composable
private fun TabStripContent(
state: TabStripState,
onAddTabClick: () -> Unit,
onCloseTabClick: (id: String) -> Unit,
onSelectedTabClick: (id: String) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer1)
.systemGestureExclusion(),
verticalAlignment = Alignment.CenterVertically,
) {
TabsList(
state = state,
modifier = Modifier.weight(1f, fill = false),
onCloseTabClick = onCloseTabClick,
onSelectedTabClick = onSelectedTabClick,
)
IconButton(onClick = onAddTabClick) {
Icon(
painter = painterResource(R.drawable.mozac_ic_plus_24),
modifier = Modifier.size(addTabIconSize),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = stringResource(R.string.add_tab),
)
}
}
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
private fun TabsList(
state: TabStripState,
modifier: Modifier = Modifier,
onCloseTabClick: (id: String) -> Unit,
onSelectedTabClick: (id: String) -> Unit,
) {
BoxWithConstraints(modifier = modifier) {
val listState = rememberLazyListState()
// Calculate the width of each tab item based on available width and the number of tabs and
// taking into account the space between tabs.
val availableWidth = maxWidth - tabStripStartPadding
val tabWidth = (availableWidth / state.tabs.size) - spaceBetweenTabs
LazyRow(
modifier = Modifier,
state = listState,
contentPadding = PaddingValues(start = tabStripStartPadding),
) {
items(
items = state.tabs,
key = { it.id },
) { itemState ->
TabItem(
state = itemState,
onCloseTabClick = onCloseTabClick,
onSelectedTabClick = onSelectedTabClick,
modifier = Modifier
.padding(end = spaceBetweenTabs)
.animateItemPlacement()
.width(
tabWidth.coerceIn(
minimumValue = minTabStripItemWidth,
maximumValue = maxTabStripItemWidth,
),
),
)
}
}
if (state.tabs.isNotEmpty()) {
// When a new tab is added, scroll to the end of the list. This is done here instead of
// in onCloseTabClick so this acts on state change which can occur from any other
// place e.g. tabs tray.
LaunchedEffect(state.tabs.last().id) {
listState.scrollToItem(state.tabs.size)
}
// When a tab is selected, scroll to the selected tab. This is done here instead of
// in onSelectedTabClick so this acts on state change which can occur from any other
// place e.g. tabs tray.
val selectedTab = state.tabs.firstOrNull { it.isSelected }
LaunchedEffect(selectedTab?.id) {
if (selectedTab != null) {
val selectedItemInfo =
listState.layoutInfo.visibleItemsInfo.firstOrNull { it.key == selectedTab.id }
if (listState.isItemPartiallyVisible(selectedItemInfo) || selectedItemInfo == null) {
listState.animateScrollToItem(state.tabs.indexOf(selectedTab))
}
}
}
}
}
}
private fun LazyListState.isItemPartiallyVisible(itemInfo: LazyListItemInfo?) =
itemInfo != null &&
(itemInfo.offset + itemInfo.size > layoutInfo.viewportEndOffset || itemInfo.offset < 0)
@Composable
private fun TabItem(
state: TabStripItem,
modifier: Modifier = Modifier,
onCloseTabClick: (id: String) -> Unit,
onSelectedTabClick: (id: String) -> Unit,
) {
TabStripCard(
modifier = modifier.fillMaxSize(),
backgroundColor =
if (state.isPrivate) {
if (state.isSelected) {
FirefoxTheme.colors.layer3
} else {
FirefoxTheme.colors.layer2
}
} else {
if (state.isSelected) {
FirefoxTheme.colors.layer2
} else {
FirefoxTheme.colors.layer3
}
},
elevation = if (state.isSelected) {
selectedTabStripCardElevation
} else {
defaultTabStripCardElevation
},
) {
Row(
modifier = Modifier
.fillMaxSize()
.clickable { onSelectedTabClick(state.id) },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Row(
modifier = Modifier.weight(1f, fill = false),
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(modifier = Modifier.size(8.dp))
TabStripIcon(state.url)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = state.title,
color = FirefoxTheme.colors.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = FirefoxTheme.typography.subtitle2,
)
}
IconButton(onClick = { onCloseTabClick(state.id) }) {
Icon(
painter = painterResource(R.drawable.mozac_ic_cross_20),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = stringResource(R.string.close_tab),
)
}
}
}
}
@Composable
private fun TabStripIcon(url: String) {
Box(
modifier = Modifier
.size(tabStripIconSize)
.clip(CircleShape),
contentAlignment = Alignment.Center,
) {
Favicon(
url = url,
size = tabStripIconSize,
)
}
}
private class TabUIStateParameterProvider : PreviewParameterProvider<TabStripState> {
override val values: Sequence<TabStripState>
get() = sequenceOf(
TabStripState(
listOf(
TabStripItem(
id = "1",
title = "Tab 1",
url = "https://www.mozilla.org",
isPrivate = false,
isSelected = false,
),
TabStripItem(
id = "2",
title = "Tab 2 with a very long title that should be truncated",
url = "https://www.mozilla.org",
isPrivate = false,
isSelected = false,
),
TabStripItem(
id = "3",
title = "Selected tab",
url = "https://www.mozilla.org",
isPrivate = false,
isSelected = true,
),
TabStripItem(
id = "p1",
title = "Private tab 1",
url = "https://www.mozilla.org",
isPrivate = true,
isSelected = false,
),
TabStripItem(
id = "p2",
title = "Private selected tab",
url = "https://www.mozilla.org",
isPrivate = true,
isSelected = true,
),
),
),
)
}
@Preview(device = Devices.TABLET)
@Composable
private fun TabStripPreview(
@PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState,
) {
FirefoxTheme {
TabStripContentPreview(tabStripState.tabs.filter { !it.isPrivate })
}
}
@Preview(device = Devices.TABLET)
@Composable
private fun TabStripPreviewDarkMode(
@PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState,
) {
FirefoxTheme(theme = Theme.Dark) {
TabStripContentPreview(tabStripState.tabs.filter { !it.isPrivate })
}
}
@Preview(device = Devices.TABLET)
@Composable
private fun TabStripPreviewPrivateMode(
@PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState,
) {
FirefoxTheme(theme = Theme.Private) {
TabStripContentPreview(tabStripState.tabs.filter { it.isPrivate })
}
}
@Composable
private fun TabStripContentPreview(tabs: List<TabStripItem>) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(dimensionResource(id = R.dimen.tab_strip_height)),
contentAlignment = Alignment.Center,
) {
TabStripContent(
state = TabStripState(
tabs = tabs,
),
onAddTabClick = {},
onCloseTabClick = {},
onSelectedTabClick = {},
)
}
}
@Preview(device = Devices.TABLET)
@Composable
private fun TabStripPreview() {
val browserStore = BrowserStore()
FirefoxTheme {
Box(
modifier = Modifier
.fillMaxWidth()
.height(dimensionResource(id = R.dimen.tab_strip_height)),
contentAlignment = Alignment.Center,
) {
TabStrip(
appStore = AppStore(),
browserStore = browserStore,
tabsUseCases = TabsUseCases(browserStore),
onAddTabClick = {
val tab = createTab(
url = "www.example.com",
)
browserStore.dispatch(TabListAction.AddTabAction(tab))
},
onLastTabClose = {},
onSelectedTabClick = {},
)
}
}
}

@ -0,0 +1,71 @@
/* 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.browser.tabstrip
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme
private val cardShape = RoundedCornerShape(8.dp)
internal val defaultTabStripCardElevation = 0.dp
internal val selectedTabStripCardElevation = 1.dp
/**
* Card composable used in Tab Strip items.
*
* @param modifier The modifier to be applied to the card.
* @param backgroundColor The background color of the card.
* @param elevation The elevation of the card.
* @param content The content of the card.
*/
@Composable
fun TabStripCard(
modifier: Modifier = Modifier,
backgroundColor: Color = FirefoxTheme.colors.layer3,
elevation: Dp = defaultTabStripCardElevation,
content: @Composable () -> Unit,
) {
Card(
shape = cardShape,
backgroundColor = backgroundColor,
elevation = elevation,
modifier = modifier,
content = content,
)
}
@LightDarkPreview
@Composable
private fun TabStripCardPreview() {
FirefoxTheme {
TabStripCard {
Box(
modifier = Modifier
.height(56.dp)
.width(200.dp)
.padding(8.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = "Tab Strip Card",
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.subtitle1,
)
}
}
}
}

@ -0,0 +1,63 @@
/* 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.browser.tabstrip
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.state.BrowserState
/**
* The ui state of the tabs strip.
*
* @property tabs The list of [TabStripItem].
*/
data class TabStripState(
val tabs: List<TabStripItem>,
) {
companion object {
val initial = TabStripState(tabs = emptyList())
}
}
/**
* The ui state of a tab.
*
* @property id The id of the tab.
* @property title The title of the tab.
* @property url The url of the tab.
* @property isPrivate Whether or not the tab is private.
* @property isSelected Whether or not the tab is selected.
*/
data class TabStripItem(
val id: String,
val title: String,
val url: String,
val isPrivate: Boolean,
val isSelected: Boolean,
)
/**
* Converts [BrowserState] to [TabStripState] that contains the information needed to render the
* tabs strip.
*
* @param isSelectDisabled When true, the tabs will show as selected.
* @param isPrivateMode Whether or not the browser is in private mode.
*/
internal fun BrowserState.toTabStripState(
isSelectDisabled: Boolean,
isPrivateMode: Boolean,
): TabStripState {
return TabStripState(
tabs = getNormalOrPrivateTabs(isPrivateMode)
.map {
TabStripItem(
id = it.id,
title = it.content.title.ifBlank { it.content.url },
url = it.content.url,
isPrivate = it.content.private,
isSelected = !isSelectDisabled && it.id == selectedTabId,
)
},
)
}

@ -23,6 +23,7 @@ import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId
@ -77,6 +78,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.HomeScreen
import org.mozilla.fenix.GleanMetrics.Homepage
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr
@ -84,6 +86,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.showSnackBar
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.tabstrip.TabStrip
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.TabCollectionStorage
@ -576,6 +579,7 @@ class HomeFragment : Fragment() {
)
toolbarView?.build()
initTabStrip()
PrivateBrowsingButtonView(binding.privateBrowsingButton, browsingModeManager) { newMode ->
sessionControlInteractor.onPrivateModeButtonClicked(newMode)
@ -653,6 +657,31 @@ class HomeFragment : Fragment() {
)
}
private fun initTabStrip() {
if (!resources.getBoolean(R.bool.tablet)) {
return
}
binding.tabStripView.isVisible = true
binding.tabStripView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
FirefoxTheme {
TabStrip(
onHome = true,
onAddTabClick = {
sessionControlInteractor.onNavigateSearch()
},
onSelectedTabClick = {
(requireActivity() as HomeActivity).openToBrowser(BrowserDirection.FromHome)
},
onLastTabClose = {},
)
}
}
}
}
/**
* Method used to listen to search engine name changes and trigger a top sites update accordingly
*/

@ -0,0 +1,244 @@
package org.mozilla.fenix.browser.tabstrip
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import org.junit.Assert.assertEquals
import org.junit.Test
class TabStripStateTest {
@Test
fun `WHEN browser state tabs is empty THEN tabs strip state tabs is empty`() {
val browserState = BrowserState(tabs = emptyList())
val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false)
val expected = TabStripState(tabs = emptyList())
assertEquals(expected, actual)
}
@Test
fun `WHEN private mode is off THEN tabs strip state tabs should include only non private tabs`() {
val browserState = BrowserState(
tabs = listOf(
createTab(
url = "https://example.com",
title = "Example 1",
private = false,
id = "1",
),
createTab(
url = "https://example2.com",
title = "Example 2",
private = true,
id = "2",
),
createTab(
url = "https://example3.com",
title = "Example 3",
private = false,
id = "3",
),
),
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false)
val expected = TabStripState(
tabs = listOf(
TabStripItem(
id = "1",
title = "Example 1",
url = "https://example.com",
isSelected = false,
isPrivate = false,
),
TabStripItem(
id = "3",
title = "Example 3",
url = "https://example3.com",
isSelected = false,
isPrivate = false,
),
),
)
assertEquals(expected, actual)
}
@Test
fun `WHEN private mode is on THEN tabs strip state tabs should include only private tabs`() {
val browserState = BrowserState(
tabs = listOf(
createTab(
url = "https://example.com",
title = "Example",
private = false,
id = "1",
),
createTab(
url = "https://example2.com",
title = "Private Example",
private = true,
id = "2",
),
createTab(
url = "https://example3.com",
title = "Example 3",
private = true,
id = "3",
),
),
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = true)
val expected = TabStripState(
tabs = listOf(
TabStripItem(
id = "2",
title = "Private Example",
url = "https://example2.com",
isSelected = false,
isPrivate = true,
),
TabStripItem(
id = "3",
title = "Example 3",
url = "https://example3.com",
isSelected = false,
isPrivate = true,
),
),
)
assertEquals(expected, actual)
}
@Test
fun `WHEN isSelectDisabled is false THEN tabs strip state tabs should have a selected tab`() {
val browserState = BrowserState(
tabs = listOf(
createTab(
url = "https://example.com",
title = "Example 1",
private = false,
id = "1",
),
createTab(
url = "https://example2.com",
title = "Example 2",
private = false,
id = "2",
),
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false)
val expected = TabStripState(
tabs = listOf(
TabStripItem(
id = "1",
title = "Example 1",
url = "https://example.com",
isSelected = false,
isPrivate = false,
),
TabStripItem(
id = "2",
title = "Example 2",
url = "https://example2.com",
isSelected = true,
isPrivate = false,
),
),
)
assertEquals(expected, actual)
}
@Test
fun `WHEN isSelectDisabled is true THEN tabs strip state tabs should not have a selected tab`() {
val browserState = BrowserState(
tabs = listOf(
createTab(
url = "https://example.com",
title = "Example 1",
private = false,
id = "1",
),
createTab(
url = "https://example2.com",
title = "Example 2",
private = false,
id = "2",
),
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = true, isPrivateMode = false)
val expected = TabStripState(
tabs = listOf(
TabStripItem(
id = "1",
title = "Example 1",
url = "https://example.com",
isSelected = false,
isPrivate = false,
),
TabStripItem(
id = "2",
title = "Example 2",
url = "https://example2.com",
isSelected = false,
isPrivate = false,
),
),
)
assertEquals(expected, actual)
}
@Test
fun `WHEN a tab does not have a title THEN tabs strip should display the url`() {
val browserState = BrowserState(
tabs = listOf(
createTab(
url = "https://example.com",
title = "Example 1",
private = false,
id = "1",
),
createTab(
url = "https://example2.com",
title = "",
private = false,
id = "2",
),
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false)
val expected = TabStripState(
tabs = listOf(
TabStripItem(
id = "1",
title = "Example 1",
url = "https://example.com",
isSelected = false,
isPrivate = false,
),
TabStripItem(
id = "2",
title = "https://example2.com",
url = "https://example2.com",
isSelected = true,
isPrivate = false,
),
),
)
assertEquals(expected, actual)
}
}
Loading…
Cancel
Save