Bug 1875465 - Part 3: Add tab strip ui for tablets
parent
c91378ceb7
commit
54d7785896
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
@ -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…
Reference in New Issue