Bug 1923855 - Move tab counter to tab strip r=android-reviewers,007

Differential Revision: https://phabricator.services.mozilla.com/D225654
This commit is contained in:
rahulsainani 2024-11-05 10:33:12 +00:00
parent 18295d9284
commit aa1fa29243
9 changed files with 633 additions and 91 deletions

View File

@ -508,16 +508,7 @@ abstract class BaseBrowserFragment :
customTabSessionId = customTabSessionId,
browserAnimator = browserAnimator,
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabsTrayFragment(
page = when (activity.browsingModeManager.mode) {
BrowsingMode.Normal -> Page.NormalTabs
BrowsingMode.Private -> Page.PrivateTabs
},
),
)
onTabCounterClicked(activity.browsingModeManager.mode)
},
onCloseTab = { closedSession ->
val closedTab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
@ -593,6 +584,7 @@ abstract class BaseBrowserFragment :
BrowserFragmentDirections.actionGlobalHome(),
)
},
onTabCounterClick = { onTabCounterClicked(activity.browsingModeManager.mode) },
)
}
},
@ -1688,16 +1680,7 @@ abstract class BaseBrowserFragment :
},
onTabsButtonClick = {
NavigationBar.browserTabTrayTapped.record(NoExtras())
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabsTrayFragment(
page = when (activity.browsingModeManager.mode) {
BrowsingMode.Normal -> Page.NormalTabs
BrowsingMode.Private -> Page.PrivateTabs
},
),
)
onTabCounterClicked(activity.browsingModeManager.mode)
},
onTabsButtonLongPress = {
NavigationBar.browserTabTrayLongTapped.record(NoExtras())
@ -1717,6 +1700,19 @@ abstract class BaseBrowserFragment :
}
}
private fun onTabCounterClicked(browsingMode: BrowsingMode) {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabsTrayFragment(
page = when (browsingMode) {
BrowsingMode.Normal -> Page.NormalTabs
BrowsingMode.Private -> Page.PrivateTabs
},
),
)
}
@VisibleForTesting
internal fun initializeMicrosurveyFeature(context: Context) {
if (context.settings().isExperimentationEnabled && context.settings().microsurveyFeatureEnabled) {

View File

@ -0,0 +1,88 @@
/* 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.annotation.DrawableRes
import androidx.annotation.StringRes
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.menu.MenuItem
import org.mozilla.fenix.compose.text.Text
/**
* Model representing different tab strip tab counter menu items.
*/
sealed interface TabCounterMenuItem {
/**
* Model representing menu items with an icon.
*
* @property textResource The text resource to be displayed.
* @property drawableRes The drawable resource to be displayed.
* @property onClick Invoked when the item is clicked.
*/
sealed class IconItem(
@StringRes val textResource: Int,
@DrawableRes val drawableRes: Int,
open val onClick: () -> Unit,
) : TabCounterMenuItem {
/**
* Model representing a new tab menu item.
*
* @property onClick Invoked when the item is clicked.
*/
data class NewTab(
override val onClick: () -> Unit,
) : IconItem(
textResource = R.string.add_tab,
drawableRes = R.drawable.mozac_ic_plus_24,
onClick = onClick,
)
/**
* Model representing a new private tab menu item.
*
* @property onClick Invoked when the item is clicked.
*/
data class NewPrivateTab(
override val onClick: () -> Unit,
) : IconItem(
textResource = R.string.add_private_tab,
drawableRes = R.drawable.mozac_ic_private_mode_24,
onClick = onClick,
)
/**
* Model representing a close tab menu item.
*
* @property onClick Invoked when the item is clicked.
*/
data class CloseTab(
override val onClick: () -> Unit,
) : IconItem(
textResource = R.string.close_tab,
drawableRes = R.drawable.mozac_ic_cross_24,
onClick = onClick,
)
}
/**
* Model representing a divider.
*/
data object Divider : TabCounterMenuItem
/**
* Maps [TabCounterMenuItem] to a [MenuItem].
*/
fun toMenuItem(): MenuItem =
when (this) {
is Divider -> MenuItem.Divider
is IconItem -> MenuItem.IconItem(
text = Text.Resource(textResource),
drawableRes = drawableRes,
onClick = onClick,
)
}
}

View File

@ -86,8 +86,9 @@ private val maxTabStripItemWidth = 280.dp
private val tabItemHeight = 40.dp
private val tabStripIconSize = 24.dp
private val spaceBetweenTabs = 4.dp
private val tabStripStartPadding = 8.dp
private val tabStripListContentStartPadding = 8.dp
private val titleFadeWidth = 16.dp
private val tabStripHorizontalPadding = 16.dp
/**
* Top level composable for the tabs strip.
@ -101,6 +102,7 @@ private val titleFadeWidth = 16.dp
* @param onLastTabClose Invoked when the last remaining open tab is closed.
* @param onSelectedTabClick Invoked when a tab is selected.
* @param onPrivateModeToggleClick Invoked when the private mode toggle button is clicked.
* @param onTabCounterClick Invoked when tab counter is clicked.
*/
@Composable
fun TabStrip(
@ -113,26 +115,47 @@ fun TabStrip(
onLastTabClose: (isPrivate: Boolean) -> Unit,
onSelectedTabClick: () -> Unit,
onPrivateModeToggleClick: (mode: BrowsingMode) -> Unit,
onTabCounterClick: () -> Unit,
) {
val isPossiblyPrivateMode by appStore.observeAsState(false) { it.mode.isPrivate }
val state by browserStore.observeAsState(TabStripState.initial) {
it.toTabStripState(isSelectDisabled = onHome, isPossiblyPrivateMode = isPossiblyPrivateMode)
it.toTabStripState(
isSelectDisabled = onHome,
isPossiblyPrivateMode = isPossiblyPrivateMode,
addTab = onAddTabClick,
toggleBrowsingMode = { isPrivate ->
toggleBrowsingMode(isPrivate, onPrivateModeToggleClick, appStore)
},
closeTab = { isPrivate, numberOfTabs ->
it.selectedTabId?.let { selectedTabId ->
closeTab(
numberOfTabs = numberOfTabs,
isPrivate = isPrivate,
tabsUseCases = tabsUseCases,
tabId = selectedTabId,
onLastTabClose = onLastTabClose,
onCloseTabClick = onCloseTabClick,
)
}
},
)
}
TabStripContent(
state = state,
onAddTabClick = onAddTabClick,
onPrivateModeToggleClick = {
val newMode = BrowsingMode.fromBoolean(!state.isPrivateMode)
onPrivateModeToggleClick(newMode)
appStore.dispatch(AppAction.ModeChange(newMode))
toggleBrowsingMode(state.isPrivateMode, onPrivateModeToggleClick, appStore)
},
onCloseTabClick = { id, isPrivate ->
if (state.tabs.size == 1) {
onLastTabClose(isPrivate)
}
tabsUseCases.removeTab(id)
onCloseTabClick(isPrivate)
onCloseTabClick = { tabId, isPrivate ->
closeTab(
numberOfTabs = state.tabs.size,
isPrivate = isPrivate,
tabsUseCases = tabsUseCases,
tabId = tabId,
onLastTabClose = onLastTabClose,
onCloseTabClick = onCloseTabClick,
)
},
onSelectedTabClick = {
tabsUseCases.selectTab(it)
@ -143,6 +166,7 @@ fun TabStrip(
tabsUseCases.moveTabs(listOf(tabId), targetId, placeAfter)
}
},
onTabCounterClick = onTabCounterClick,
)
}
@ -154,44 +178,58 @@ private fun TabStripContent(
onCloseTabClick: (id: String, isPrivate: Boolean) -> Unit,
onSelectedTabClick: (id: String) -> Unit,
onMove: (tabId: String, targetId: String, placeAfter: Boolean) -> Unit,
onTabCounterClick: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer3)
.systemGestureExclusion(),
.systemGestureExclusion()
.padding(horizontal = tabStripHorizontalPadding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
IconButton(
onClick = onPrivateModeToggleClick,
modifier = Modifier.padding(start = tabStripStartPadding),
) {
Icon(
painter = painterResource(R.drawable.mozac_ic_private_mode_24),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = if (state.isPrivateMode) {
stringResource(R.string.content_description_disable_private_browsing_button)
} else {
stringResource(R.string.content_description_private_browsing_button)
},
)
}
TabsList(
state = state,
Row(
modifier = Modifier.weight(1f, fill = false),
onCloseTabClick = onCloseTabClick,
onSelectedTabClick = onSelectedTabClick,
onMove = onMove,
)
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
onClick = onPrivateModeToggleClick,
) {
Icon(
painter = painterResource(R.drawable.mozac_ic_private_mode_24),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = if (state.isPrivateMode) {
stringResource(R.string.content_description_disable_private_browsing_button)
} else {
stringResource(R.string.content_description_private_browsing_button)
},
)
}
IconButton(onClick = onAddTabClick) {
Icon(
painter = painterResource(R.drawable.mozac_ic_plus_24),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = stringResource(R.string.add_tab),
TabsList(
state = state,
modifier = Modifier.weight(1f, fill = false),
onCloseTabClick = onCloseTabClick,
onSelectedTabClick = onSelectedTabClick,
onMove = onMove,
)
IconButton(onClick = onAddTabClick) {
Icon(
painter = painterResource(R.drawable.mozac_ic_plus_24),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = stringResource(R.string.add_tab),
)
}
}
TabStripTabCounterButton(
tabCount = state.tabs.size,
size = dimensionResource(R.dimen.tab_strip_height),
menuItems = state.menuItems,
onClick = onTabCounterClick,
)
}
}
@ -208,7 +246,7 @@ private fun TabsList(
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 availableWidth = maxWidth - tabStripListContentStartPadding
val tabWidth = (availableWidth / state.tabs.size) - spaceBetweenTabs
val reorderState = createListReorderState(
@ -232,7 +270,7 @@ private fun TabsList(
)
.selectableGroup(),
state = listState,
contentPadding = PaddingValues(start = tabStripStartPadding),
contentPadding = PaddingValues(start = tabStripListContentStartPadding),
) {
itemsIndexed(
items = state.tabs,
@ -428,6 +466,36 @@ private fun TabStripIcon(
}
}
private fun closeTab(
numberOfTabs: Int,
isPrivate: Boolean,
tabsUseCases: TabsUseCases,
tabId: String,
onLastTabClose: (isPrivate: Boolean) -> Unit,
onCloseTabClick: (isPrivate: Boolean) -> Unit,
) {
if (numberOfTabs == 1) {
onLastTabClose(isPrivate)
}
tabsUseCases.removeTab(tabId)
onCloseTabClick(isPrivate)
}
/**
* Invoking the callback is required so the caller can update the browsing mode in cases where
* appStore.dispatch(AppAction.ModeChange(newMode)) is not enough. This bug is tracked here:
* https://bugzilla.mozilla.org/show_bug.cgi?id=1923650
*/
private fun toggleBrowsingMode(
isCurrentModePrivate: Boolean,
onPrivateModeToggleClick: (mode: BrowsingMode) -> Unit,
appStore: AppStore,
) {
val newMode = BrowsingMode.fromBoolean(!isCurrentModePrivate)
onPrivateModeToggleClick(newMode)
appStore.dispatch(AppAction.ModeChange(newMode))
}
private class TabUIStateParameterProvider : PreviewParameterProvider<TabStripState> {
override val values: Sequence<TabStripState>
get() = sequenceOf(
@ -470,6 +538,7 @@ private class TabUIStateParameterProvider : PreviewParameterProvider<TabStripSta
),
),
isPrivateMode = false,
tabCounterMenuItems = emptyList(),
),
)
}
@ -516,12 +585,14 @@ private fun TabStripContentPreview(tabs: List<TabStripItem>) {
state = TabStripState(
tabs = tabs,
isPrivateMode = false,
tabCounterMenuItems = emptyList(),
),
onAddTabClick = {},
onPrivateModeToggleClick = {},
onCloseTabClick = { _, _ -> },
onSelectedTabClick = {},
onMove = { _, _, _ -> },
onTabCounterClick = {},
)
}
}
@ -559,6 +630,7 @@ private fun TabStripPreview() {
onCloseTabClick = {},
onSelectedTabClick = {},
onPrivateModeToggleClick = {},
onTabCounterClick = {},
)
}
}

View File

@ -15,13 +15,24 @@ import mozilla.components.browser.state.state.TabSessionState
*
* @property tabs The list of [TabStripItem].
* @property isPrivateMode Whether or not the browser is in private mode.
* @property tabCounterMenuItems The list of [TabCounterMenuItem]s to be displayed in the tab
* counter menu.
*/
data class TabStripState(
val tabs: List<TabStripItem>,
val isPrivateMode: Boolean,
val tabCounterMenuItems: List<TabCounterMenuItem>,
) {
val menuItems
get() = tabCounterMenuItems.map { it.toMenuItem() }
companion object {
val initial = TabStripState(tabs = emptyList(), isPrivateMode = false)
val initial = TabStripState(
tabs = emptyList(),
isPrivateMode = false,
tabCounterMenuItems = emptyList(),
)
}
}
@ -51,10 +62,16 @@ data class TabStripItem(
*
* @param isSelectDisabled When true, the tabs will show as unselected.
* @param isPossiblyPrivateMode Whether or not the browser is in private mode.
* @param addTab Invoked when conditions are met for adding a new normal browsing mode tab.
* @param toggleBrowsingMode Invoked when conditions are met for toggling the browsing mode.
* @param closeTab Invoked when close tab is clicked.
*/
internal fun BrowserState.toTabStripState(
isSelectDisabled: Boolean,
isPossiblyPrivateMode: Boolean,
addTab: () -> Unit,
toggleBrowsingMode: (isCurrentlyPrivate: Boolean) -> Unit,
closeTab: (isPrivate: Boolean, numberOfTabs: Int) -> Unit,
): TabStripState {
val isPrivateMode = if (isSelectDisabled) {
isPossiblyPrivateMode
@ -62,8 +79,10 @@ internal fun BrowserState.toTabStripState(
selectedTab?.content?.private == true
}
val tabs = getNormalOrPrivateTabs(private = isPrivateMode)
return TabStripState(
tabs = getNormalOrPrivateTabs(private = isPrivateMode)
tabs = tabs
.map {
it.toTabStripItem(
isSelectDisabled = isSelectDisabled,
@ -71,9 +90,57 @@ internal fun BrowserState.toTabStripState(
)
},
isPrivateMode = isPrivateMode,
tabCounterMenuItems = mapToMenuItems(
isSelectEnabled = !isSelectDisabled,
isPrivateMode = isPrivateMode,
addTab = addTab,
toggleBrowsingMode = toggleBrowsingMode,
closeTab = closeTab,
numberOfTabs = tabs.size,
),
)
}
private fun mapToMenuItems(
isSelectEnabled: Boolean,
isPrivateMode: Boolean,
toggleBrowsingMode: (isCurrentlyPrivate: Boolean) -> Unit,
addTab: () -> Unit,
closeTab: (isPrivate: Boolean, numberOfTabs: Int) -> Unit,
numberOfTabs: Int,
): List<TabCounterMenuItem> = buildList {
if (isSelectEnabled || isPrivateMode) {
val onClick = {
if (isPrivateMode) {
toggleBrowsingMode(true)
} else {
addTab()
}
}
add(TabCounterMenuItem.IconItem.NewTab(onClick = onClick))
}
if (isSelectEnabled || !isPrivateMode) {
val onClick = {
if (isPrivateMode) {
addTab()
} else {
toggleBrowsingMode(false)
}
}
add(TabCounterMenuItem.IconItem.NewPrivateTab(onClick = onClick))
}
if (isSelectEnabled) {
add(TabCounterMenuItem.Divider)
add(
TabCounterMenuItem.IconItem.CloseTab(
onClick = { closeTab(isPrivateMode, numberOfTabs) },
),
)
}
}
private fun TabSessionState.toTabStripItem(
isSelectDisabled: Boolean,
selectedTabId: String?,

View File

@ -0,0 +1,129 @@
/* 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.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.compose.TabCounter
import org.mozilla.fenix.compose.menu.DropdownMenu
import org.mozilla.fenix.compose.menu.MenuItem
import org.mozilla.fenix.theme.FirefoxTheme
/**
* A button showing number of tabs in the tab strip, encapsulating [TabCounter] and [DropdownMenu].
* When long pressed, the [DropdownMenu] will appear.
*
* @param tabCount The number of tabs to display in the counter.
* @param size The size of the button.
* @param menuItems The list of [MenuItem] to display in the dropdown menu.
* @param modifier The [modifier] applied to the composable.
* @param onClick Invoked when the user clicks the button.
*/
@Composable
@OptIn(ExperimentalFoundationApi::class)
fun TabStripTabCounterButton(
tabCount: Int,
size: Dp,
menuItems: List<MenuItem>,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
var menuExpanded by remember { mutableStateOf(false) }
Box(
modifier = modifier
.size(size)
.clip(CircleShape)
.combinedClickable(
onClick = onClick,
role = Role.Button,
onLongClick = {
menuExpanded = true
},
),
contentAlignment = Alignment.Center,
) {
TabCounter(
tabCount = tabCount,
)
DropdownMenu(
menuItems = menuItems,
expanded = menuExpanded,
offset = DpOffset(
x = 0.dp,
y = -size,
),
onDismissRequest = { menuExpanded = false },
)
}
}
@PreviewLightDark
@Composable
private fun TabStripTabCounterButtonPreview() {
FirefoxTheme {
Column(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer1)
.padding(FirefoxTheme.space.baseContentEqualPadding),
verticalArrangement = Arrangement.spacedBy(FirefoxTheme.space.baseContentEqualPadding),
) {
Text(
text = "TabStripTabCounterButton",
style = FirefoxTheme.typography.body1,
color = FirefoxTheme.colors.textPrimary,
)
Text(
text = """
Clicking the button will increment the tab count. Long press the button to open the dropdown menu.
""".trimIndent(),
style = FirefoxTheme.typography.caption,
color = FirefoxTheme.colors.textPrimary,
)
var tabCount by remember { mutableIntStateOf(1) }
TabStripTabCounterButton(
tabCount = tabCount,
size = 56.dp,
menuItems = listOf(
TabCounterMenuItem.IconItem.NewTab { },
TabCounterMenuItem.IconItem.NewPrivateTab { },
TabCounterMenuItem.Divider,
TabCounterMenuItem.IconItem.CloseTab { },
).map { it.toMenuItem() },
modifier = Modifier
.align(Alignment.End)
.background(FirefoxTheme.colors.layer2),
onClick = { tabCount++ },
)
}
}
}

View File

@ -165,9 +165,8 @@ class DefaultToolbarIntegration(
addShareBrowserAction()
} else {
addNewTabBrowserAction()
addTabCounterBrowserAction()
}
addTabCounterBrowserAction()
}
private fun addNewTabBrowserAction() {

View File

@ -1230,6 +1230,7 @@ class HomeFragment : Fragment() {
onPrivateModeToggleClick = { mode ->
browsingModeManager.mode = mode
},
onTabCounterClick = { openTabsTray() },
)
}
}
@ -1558,7 +1559,12 @@ class HomeFragment : Fragment() {
private fun openTabsTray() {
findNavController().nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalTabsTrayFragment(),
HomeFragmentDirections.actionGlobalTabsTrayFragment(
page = when (browsingModeManager.mode) {
BrowsingMode.Normal -> Page.NormalTabs
BrowsingMode.Private -> Page.PrivateTabs
},
),
)
}

View File

@ -88,17 +88,24 @@ class ToolbarView(
* @param browserState [BrowserState] is used to update tab counter's state.
*/
fun updateButtonVisibility(browserState: BrowserState) {
val showTabCounterAndMenu = !context.shouldAddNavigationBar()
binding.menuButton.isVisible = showTabCounterAndMenu
binding.tabButton.isVisible = showTabCounterAndMenu
val shouldAddNavigationBar = context.shouldAddNavigationBar()
val showMenu = !shouldAddNavigationBar
val showTabCounter = !(shouldAddNavigationBar || context.isTabStripEnabled())
binding.menuButton.isVisible = showMenu
binding.tabButton.isVisible = showTabCounter
if (showTabCounterAndMenu) {
homeMenuView = buildHomeMenu()
tabCounterView = buildTabCounter()
tabCounterView?.update(browserState)
tabCounterView = if (showTabCounter) {
buildTabCounter().also {
it.update(browserState)
}
} else {
homeMenuView = null
tabCounterView = null
null
}
homeMenuView = if (showMenu) {
buildHomeMenu()
} else {
null
}
}

View File

@ -1,3 +1,7 @@
/* 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.state.BrowserState
@ -10,11 +14,21 @@ 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, isPossiblyPrivateMode = false)
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(tabs = emptyList(), false)
val expected = TabStripState(
tabs = emptyList(),
isPrivateMode = false,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -42,7 +56,14 @@ class TabStripStateTest {
),
selectedTabId = "1",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPossiblyPrivateMode = false)
val actual =
browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -62,9 +83,10 @@ class TabStripStateTest {
),
),
isPrivateMode = false,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -91,7 +113,13 @@ class TabStripStateTest {
),
),
)
val actual = browserState.toTabStripState(isSelectDisabled = true, isPossiblyPrivateMode = true)
val actual = browserState.toTabStripState(
isSelectDisabled = true,
isPossiblyPrivateMode = true,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -111,9 +139,10 @@ class TabStripStateTest {
),
),
isPrivateMode = true,
tabCounterMenuItems = noTabSelectedPrivateModeMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -141,7 +170,13 @@ class TabStripStateTest {
),
selectedTabId = "1",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPossiblyPrivateMode = true)
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = true,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -154,9 +189,10 @@ class TabStripStateTest {
),
),
isPrivateMode = false,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -178,7 +214,13 @@ class TabStripStateTest {
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPossiblyPrivateMode = false)
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -198,9 +240,10 @@ class TabStripStateTest {
),
),
isPrivateMode = false,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -228,7 +271,13 @@ class TabStripStateTest {
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPossiblyPrivateMode = false)
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -248,9 +297,10 @@ class TabStripStateTest {
),
),
isPrivateMode = true,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -272,7 +322,13 @@ class TabStripStateTest {
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = true, isPossiblyPrivateMode = false)
val actual = browserState.toTabStripState(
isSelectDisabled = true,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -292,9 +348,10 @@ class TabStripStateTest {
),
),
isPrivateMode = false,
tabCounterMenuItems = noTabSelectedNormalModeMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
@ -316,7 +373,13 @@ class TabStripStateTest {
),
selectedTabId = "2",
)
val actual = browserState.toTabStripState(isSelectDisabled = false, isPossiblyPrivateMode = false)
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = {},
toggleBrowsingMode = {},
closeTab = { _, _ -> },
)
val expected = TabStripState(
tabs = listOf(
@ -336,8 +399,123 @@ class TabStripStateTest {
),
),
isPrivateMode = false,
tabCounterMenuItems = allMenuItems,
)
assertEquals(expected, actual)
expected isSameAs actual
}
@Test
fun `WHEN menu items are clicked THEN the correct action is performed`() {
var addTabClicked = false
var shouldOpenPrivateTab: Boolean? = null
var toggleBrowsingModeClicked = false
var closeTabClicked = false
var closTabParams: Pair<Boolean, Int>? = null
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 addTab = {
addTabClicked = true
}
val toggleBrowsingMode: (isPrivate: Boolean) -> Unit = {
toggleBrowsingModeClicked = true
shouldOpenPrivateTab = it
}
val closeTab: (isPrivate: Boolean, numberOfTabs: Int) -> Unit = { isPrivate, numberOfTabs ->
closeTabClicked = true
closTabParams = Pair(isPrivate, numberOfTabs)
}
val actual = browserState.toTabStripState(
isSelectDisabled = false,
isPossiblyPrivateMode = false,
addTab = addTab,
toggleBrowsingMode = toggleBrowsingMode,
closeTab = closeTab,
)
val newTab = TabCounterMenuItem.IconItem.NewTab(onClick = addTab)
val newPrivateTab =
TabCounterMenuItem.IconItem.NewPrivateTab(onClick = { toggleBrowsingMode(true) })
val closeTabItem = TabCounterMenuItem.IconItem.CloseTab(onClick = { closeTab(false, 2) })
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,
),
),
isPrivateMode = false,
tabCounterMenuItems = listOf(
newTab,
newPrivateTab,
TabCounterMenuItem.Divider,
closeTabItem,
),
)
expected isSameAs actual
newTab.onClick()
assertEquals(true, addTabClicked)
newPrivateTab.onClick()
assertEquals(true, shouldOpenPrivateTab)
assertEquals(true, toggleBrowsingModeClicked)
closeTabItem.onClick()
assertEquals(true, closeTabClicked)
assertEquals(Pair(false, 2), closTabParams)
}
/**
* Asserts that the [TabStripState] is the same as the [other] [TabStripState] by comparing
* their properties as assertEquals does. This ignores the lambda references in the
* [TabCounterMenuItem.IconItem]s as asserting them is not straightforward.
*/
private infix fun TabStripState.isSameAs(other: TabStripState) {
assertEquals(tabs, other.tabs)
assertEquals(isPrivateMode, other.isPrivateMode)
assertEquals(
tabCounterMenuItems.map { it.javaClass },
other.tabCounterMenuItems.map { it.javaClass },
)
}
private val allMenuItems = listOf(
TabCounterMenuItem.IconItem.NewTab(onClick = {}),
TabCounterMenuItem.IconItem.NewPrivateTab(onClick = {}),
TabCounterMenuItem.Divider,
TabCounterMenuItem.IconItem.CloseTab(onClick = {}),
)
private val noTabSelectedNormalModeMenuItems = listOf(
TabCounterMenuItem.IconItem.NewPrivateTab(onClick = {}),
)
private val noTabSelectedPrivateModeMenuItems = listOf(
TabCounterMenuItem.IconItem.NewTab(onClick = {}),
)
}