For #19916 - Add last viewed tab to home screen
Co-authored-by: Jonathan Almeida <jalmeida@mozilla.com>upstream-sync
parent
42d87d1a7a
commit
9d3cf79051
@ -0,0 +1,24 @@
|
|||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import mozilla.components.browser.state.selector.selectedTab
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.TabSessionState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently selected tab if there's one as a list.
|
||||||
|
*
|
||||||
|
* @return A list of the currently selected tab or an empty list.
|
||||||
|
*/
|
||||||
|
fun BrowserState.asRecentTabs(): List<TabSessionState> {
|
||||||
|
val tab = selectedTab
|
||||||
|
|
||||||
|
return if (tab != null && !tab.content.private) {
|
||||||
|
listOfNotNull(tab)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* 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.home.recenttabs.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.feature.tabs.TabsUseCases.SelectTabUseCase
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.nav
|
||||||
|
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||||
|
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||||
|
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that handles the view manipulation of the recent tabs in the Home screen.
|
||||||
|
*/
|
||||||
|
interface RecentTabController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [RecentTabInteractor.onRecentTabClicked]
|
||||||
|
*/
|
||||||
|
fun handleRecentTabClicked(tabId: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see [RecentTabInteractor.onRecentTabShowAllClicked]
|
||||||
|
*/
|
||||||
|
fun handleRecentTabShowAllClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of [RecentTabController].
|
||||||
|
*
|
||||||
|
* @param selectTabUseCase [SelectTabUseCase] used selecting a tab.
|
||||||
|
* @param navController [NavController] used for navigation.
|
||||||
|
*/
|
||||||
|
class DefaultRecentTabsController(
|
||||||
|
private val selectTabUseCase: SelectTabUseCase,
|
||||||
|
private val navController: NavController
|
||||||
|
) : RecentTabController {
|
||||||
|
|
||||||
|
override fun handleRecentTabClicked(tabId: String) {
|
||||||
|
selectTabUseCase.invoke(tabId)
|
||||||
|
navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleRecentTabShowAllClicked() {
|
||||||
|
navController.nav(
|
||||||
|
R.id.homeFragment,
|
||||||
|
HomeFragmentDirections.actionGlobalTabsTrayFragment()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/* 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.home.recenttabs.interactor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for recent tab related actions in the Home screen.
|
||||||
|
*/
|
||||||
|
interface RecentTabInteractor {
|
||||||
|
/**
|
||||||
|
* Opens the given tab. Called when a user clicks on a recent tab.
|
||||||
|
*
|
||||||
|
* @param tabId The ID of the tab to open.
|
||||||
|
*/
|
||||||
|
fun onRecentTabClicked(tabId: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the tabs tray. Called when a user clicks on the "Show all" button besides the recent
|
||||||
|
* tabs.
|
||||||
|
*/
|
||||||
|
fun onRecentTabShowAllClicked()
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/* 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.home.recenttabs.view
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.recent_tabs_list_row.*
|
||||||
|
import mozilla.components.browser.state.state.TabSessionState
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
|
||||||
|
import org.mozilla.fenix.utils.view.ViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for a recent tab item.
|
||||||
|
*
|
||||||
|
* @param interactor [RecentTabInteractor] which will have delegated to all user interactions.
|
||||||
|
*/
|
||||||
|
class RecentTabViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val interactor: RecentTabInteractor
|
||||||
|
) : ViewHolder(view) {
|
||||||
|
|
||||||
|
fun bindTab(tab: TabSessionState) {
|
||||||
|
recent_tab_title.text = tab.content.title
|
||||||
|
recent_tab_icon.setImageBitmap(tab.content.icon)
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
interactor.onRecentTabClicked(tab.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.recent_tabs_list_row
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/* 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.home.recenttabs.view
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.recent_tabs_header.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
|
||||||
|
import org.mozilla.fenix.utils.view.ViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for the recent tabs header and "Show all" button.
|
||||||
|
*
|
||||||
|
* @param interactor [RecentTabInteractor] which will have delegated to all user interactions.
|
||||||
|
*/
|
||||||
|
class RecentTabsHeaderViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val interactor: RecentTabInteractor
|
||||||
|
) : ViewHolder(view) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
show_all_button.setOnClickListener {
|
||||||
|
interactor.onRecentTabShowAllClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.recent_tabs_header
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/* 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.home.sessioncontrol
|
||||||
|
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import mozilla.components.browser.state.selector.normalTabs
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.lib.state.helpers.AbstractBinding
|
||||||
|
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
||||||
|
import org.mozilla.fenix.home.HomeFragmentAction
|
||||||
|
import org.mozilla.fenix.home.HomeFragmentStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View-bound feature that dispatches recent tab changes to the [HomeFragmentStore] when the
|
||||||
|
* [BrowserStore] is updated.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class RecentTabsListFeature(
|
||||||
|
private val browserStore: BrowserStore,
|
||||||
|
private val homeStore: HomeFragmentStore
|
||||||
|
) : AbstractBinding<BrowserState>(browserStore) {
|
||||||
|
|
||||||
|
override suspend fun onState(flow: Flow<BrowserState>) {
|
||||||
|
flow.map { it.selectedTabId }
|
||||||
|
.ifChanged()
|
||||||
|
.collect { selectedTabId ->
|
||||||
|
// Attempt to get the selected normal tab since here may not be a selected tab or
|
||||||
|
// the selected tab may be a private tab.
|
||||||
|
val selectedTab = browserStore.state.normalTabs.firstOrNull {
|
||||||
|
it.id == selectedTabId
|
||||||
|
}
|
||||||
|
val recentTabsList = if (selectedTab != null) {
|
||||||
|
listOf(selectedTab)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
homeStore.dispatch(HomeFragmentAction.RecentTabsChange(recentTabsList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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/. -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginTop="40dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="@style/Header20TextStyle"
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/recent_tabs_header"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:maxLines="2"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/show_all_button"
|
||||||
|
style="@style/Button12TextStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:text="@string/recent_tabs_show_all"
|
||||||
|
android:textColor="@color/home_show_all_button_text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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/. -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/home_list_row_background"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:elevation="@dimen/home_collection_elevation"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/recent_tab_icon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recent_tab_title"
|
||||||
|
style="@style/Body16TextStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minLines="1"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/recent_tab_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/recent_tab_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/recent_tab_icon"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,64 @@
|
|||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class BrowserStateTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN there is a selected tab THEN asRecentTabs returns the selected tab as a list`() {
|
||||||
|
val tab = createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1"
|
||||||
|
)
|
||||||
|
val tabs = listOf(tab)
|
||||||
|
val state = BrowserState(
|
||||||
|
tabs = tabs,
|
||||||
|
selectedTabId = tab.id
|
||||||
|
)
|
||||||
|
val recentTabs = state.asRecentTabs()
|
||||||
|
|
||||||
|
assertEquals(tabs, recentTabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN there is no selected tab THEN asRecentTabs returns an empty list`() {
|
||||||
|
val state = BrowserState(
|
||||||
|
tabs = listOf(
|
||||||
|
createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val recentTabs = state.asRecentTabs()
|
||||||
|
|
||||||
|
assertEquals(0, recentTabs.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the selected tab is private THEN asRecentTabs returns an empty list`() {
|
||||||
|
val tab = createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1",
|
||||||
|
private = true
|
||||||
|
)
|
||||||
|
val tabs = listOf(tab)
|
||||||
|
val state = BrowserState(
|
||||||
|
tabs = tabs,
|
||||||
|
selectedTabId = tab.id
|
||||||
|
)
|
||||||
|
val recentTabs = state.asRecentTabs()
|
||||||
|
|
||||||
|
assertEquals(0, recentTabs.size)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
/* 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.home
|
||||||
|
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||||
|
import mozilla.components.browser.state.action.TabListAction
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.support.test.ext.joinBlocking
|
||||||
|
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||||
|
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.home.sessioncontrol.RecentTabsListFeature
|
||||||
|
|
||||||
|
class RecentTabsListFeatureTest {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@get:Rule
|
||||||
|
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no selected tab WHEN the feature starts THEN dispatch an empty list`() {
|
||||||
|
val browserStore = BrowserStore()
|
||||||
|
val homeStore = HomeFragmentStore()
|
||||||
|
val feature = RecentTabsListFeature(
|
||||||
|
browserStore = browserStore,
|
||||||
|
homeStore = homeStore
|
||||||
|
)
|
||||||
|
|
||||||
|
feature.start()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(0, homeStore.state.recentTabs.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN a selected tab WHEN the feature starts THEN dispatch the selected tab as a recent tab list`() {
|
||||||
|
val tab = createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1"
|
||||||
|
)
|
||||||
|
val tabs = listOf(tab)
|
||||||
|
val browserStore = BrowserStore(
|
||||||
|
BrowserState(
|
||||||
|
tabs = tabs,
|
||||||
|
selectedTabId = "1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val homeStore = HomeFragmentStore()
|
||||||
|
val feature = RecentTabsListFeature(
|
||||||
|
browserStore = browserStore,
|
||||||
|
homeStore = homeStore
|
||||||
|
)
|
||||||
|
|
||||||
|
feature.start()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(1, homeStore.state.recentTabs.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the browser state has an updated select tab THEN dispatch the new recent tab list`() {
|
||||||
|
val tab1 = createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1"
|
||||||
|
)
|
||||||
|
val tab2 = createTab(
|
||||||
|
url = "https://www.firefox.com",
|
||||||
|
id = "2"
|
||||||
|
)
|
||||||
|
val tabs = listOf(tab1, tab2)
|
||||||
|
val browserStore = BrowserStore(
|
||||||
|
BrowserState(
|
||||||
|
tabs = tabs,
|
||||||
|
selectedTabId = "1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val homeStore = HomeFragmentStore()
|
||||||
|
val feature = RecentTabsListFeature(
|
||||||
|
browserStore = browserStore,
|
||||||
|
homeStore = homeStore
|
||||||
|
)
|
||||||
|
|
||||||
|
feature.start()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(1, homeStore.state.recentTabs.size)
|
||||||
|
assertEquals(tab1, homeStore.state.recentTabs[0])
|
||||||
|
|
||||||
|
browserStore.dispatch(TabListAction.SelectTabAction(tab2.id)).joinBlocking()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(1, homeStore.state.recentTabs.size)
|
||||||
|
assertEquals(tab2, homeStore.state.recentTabs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN the browser state selects a private tab THEN dispatch an empty list`() {
|
||||||
|
val normalTab = createTab(
|
||||||
|
url = "https://www.mozilla.org",
|
||||||
|
id = "1"
|
||||||
|
)
|
||||||
|
val privateTab = createTab(
|
||||||
|
url = "https://www.firefox.com",
|
||||||
|
id = "2",
|
||||||
|
private = true
|
||||||
|
)
|
||||||
|
val tabs = listOf(normalTab, privateTab)
|
||||||
|
val browserStore = BrowserStore(
|
||||||
|
BrowserState(
|
||||||
|
tabs = tabs,
|
||||||
|
selectedTabId = "1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val homeStore = HomeFragmentStore()
|
||||||
|
val feature = RecentTabsListFeature(
|
||||||
|
browserStore = browserStore,
|
||||||
|
homeStore = homeStore
|
||||||
|
)
|
||||||
|
|
||||||
|
feature.start()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(1, homeStore.state.recentTabs.size)
|
||||||
|
assertEquals(normalTab, homeStore.state.recentTabs[0])
|
||||||
|
|
||||||
|
browserStore.dispatch(TabListAction.SelectTabAction(privateTab.id)).joinBlocking()
|
||||||
|
|
||||||
|
homeStore.waitUntilIdle()
|
||||||
|
|
||||||
|
assertEquals(0, homeStore.state.recentTabs.size)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/* 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.home.recenttabs.controller
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||||
|
import mozilla.components.browser.state.action.TabListAction
|
||||||
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.feature.tabs.TabsUseCases
|
||||||
|
import mozilla.components.support.test.ext.joinBlocking
|
||||||
|
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
|
||||||
|
import org.mozilla.fenix.helpers.DisableNavGraphProviderAssertionRule
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class RecentTabControllerTest {
|
||||||
|
|
||||||
|
private val testDispatcher = TestCoroutineDispatcher()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val disableNavGraphProviderAssertionRule = DisableNavGraphProviderAssertionRule()
|
||||||
|
|
||||||
|
private val navController: NavController = mockk(relaxed = true)
|
||||||
|
private val selectTabUseCase: TabsUseCases = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var store: BrowserStore
|
||||||
|
private lateinit var controller: RecentTabController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
store = BrowserStore(
|
||||||
|
BrowserState()
|
||||||
|
)
|
||||||
|
controller = spyk(DefaultRecentTabsController(
|
||||||
|
selectTabUseCase = selectTabUseCase.selectTab,
|
||||||
|
navController = navController
|
||||||
|
))
|
||||||
|
|
||||||
|
every { navController.currentDestination } returns mockk {
|
||||||
|
every { id } returns R.id.homeFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleRecentTabClicked() {
|
||||||
|
val tab = createTab(
|
||||||
|
url = "https://mozilla.org",
|
||||||
|
title = "Mozilla"
|
||||||
|
)
|
||||||
|
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
|
||||||
|
store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking()
|
||||||
|
|
||||||
|
controller.handleRecentTabClicked(tab.id)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
selectTabUseCase.selectTab.invoke(tab.id)
|
||||||
|
navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleRecentTabShowAllClicked() {
|
||||||
|
controller.handleRecentTabShowAllClicked()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
navController.navigate(
|
||||||
|
match<NavDirections> { it.actionId == R.id.action_global_tabsTrayFragment },
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* 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.home.recenttabs.view
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.android.synthetic.main.recent_tabs_list_row.view.*
|
||||||
|
import mozilla.components.browser.state.state.createTab
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class RecentTabViewHolderTest {
|
||||||
|
|
||||||
|
private lateinit var view: View
|
||||||
|
private lateinit var interactor: SessionControlInteractor
|
||||||
|
|
||||||
|
private val tab = createTab(
|
||||||
|
url = "https://mozilla.org",
|
||||||
|
title = "Mozilla"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
view = LayoutInflater.from(testContext).inflate(RecentTabViewHolder.LAYOUT_ID, null)
|
||||||
|
interactor = mockk(relaxed = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN a new recent tab on bind THEN set the title text`() {
|
||||||
|
RecentTabViewHolder(view, interactor).bindTab(tab)
|
||||||
|
|
||||||
|
assertEquals(tab.content.title, view.recent_tab_title.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN a recent tab item is clicked THEN interactor iis called`() {
|
||||||
|
RecentTabViewHolder(view, interactor).bindTab(tab)
|
||||||
|
|
||||||
|
view.performClick()
|
||||||
|
|
||||||
|
verify { interactor.onRecentTabClicked(tab.id) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/* 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.home.recenttabs.view
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.android.synthetic.main.recent_tabs_header.view.*
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class RecentTabsHeaderViewHolderTest {
|
||||||
|
|
||||||
|
private lateinit var view: View
|
||||||
|
private lateinit var interactor: SessionControlInteractor
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
view = LayoutInflater.from(testContext).inflate(RecentTabsHeaderViewHolder.LAYOUT_ID, null)
|
||||||
|
interactor = mockk(relaxed = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN show all button is clicked THEN interactor iis called`() {
|
||||||
|
RecentTabsHeaderViewHolder(view, interactor)
|
||||||
|
|
||||||
|
view.show_all_button.performClick()
|
||||||
|
|
||||||
|
verify { interactor.onRecentTabShowAllClicked() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue